From 4f02a57f653a684139fec7e276e3a5e0b67cd224 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 02:34:39 -0700 Subject: [PATCH] fix(auto-reply): keep docking in direct chats --- CHANGELOG.md | 1 + src/auto-reply/reply/commands-dock.test.ts | 20 ++++++++++++++++++++ src/auto-reply/reply/commands-dock.ts | 12 ++++++++++++ 3 files changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 820342f87a1..719b934a322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai - Slack/slash commands: send block-only slash command replies instead of dropping Slack block payloads with no plain-text fallback. Thanks @vincentkoc. - Telegram/messages: derive fallback text from interactive button/select labels before sending button-only payloads, so Telegram replies are not rejected as empty messages. Thanks @vincentkoc. - LINE/messages: send quick-reply-only payloads with fallback option text instead of accepting the payload and returning an empty delivery. Thanks @vincentkoc. +- Auto-reply/docking: require `/dock-*` route switches to start from direct chats, so group or channel participants cannot reroute a shared session's future replies into a linked DM. Thanks @vincentkoc. - Gateway/agent: reject strict `openclaw agent --deliver` requests with missing delivery targets before starting the agent run, so users do not wait for a completed turn that cannot send anywhere. Thanks @vincentkoc. - Setup/import: honor non-interactive `--import-from` onboarding flags by running the migration import path instead of silently completing normal setup without importing anything. Thanks @vincentkoc. - Discord/voice: run voice-channel turns under a voice-output policy that hides the agent `tts` tool and asks for spoken reply text, so `/vc join` sessions synthesize and play agent replies instead of ending with `NO_REPLY`. Fixes #61536. Thanks @aounakram. diff --git a/src/auto-reply/reply/commands-dock.test.ts b/src/auto-reply/reply/commands-dock.test.ts index 5f8c7cc9ea0..28fcb4c058d 100644 --- a/src/auto-reply/reply/commands-dock.test.ts +++ b/src/auto-reply/reply/commands-dock.test.ts @@ -55,6 +55,7 @@ function buildDockParams(commandBody: string, ctxOverrides?: Partial Provider: "telegram", Surface: "telegram", OriginatingChannel: "telegram", + ChatType: "direct", SenderId: "42", From: "42", ...ctxOverrides, @@ -121,6 +122,25 @@ describe("handleDockCommand", () => { expect(params.sessionEntry?.lastChannel).toBe("telegram"); }); + it("rejects group-session docking before it can reroute replies to a linked DM", async () => { + const params = buildDockParams("/dock-discord", { + ChatType: "group", + From: "telegram:group:-100123", + To: "telegram:-100123", + OriginatingTo: "telegram:-100123", + SenderId: "42", + }); + + const result = await handleDockCommand(params, true); + + expect(result).toEqual({ + shouldContinue: false, + reply: { text: "Cannot dock to discord: docking is only available from direct chats." }, + }); + expect(params.sessionEntry?.lastChannel).toBe("telegram"); + expect(params.sessionEntry?.lastTo).toBe("42"); + }); + it("fails closed when no session entry can be persisted", async () => { const params = buildDockParams("/dock-discord"); params.sessionEntry = undefined; diff --git a/src/auto-reply/reply/commands-dock.ts b/src/auto-reply/reply/commands-dock.ts index ae972d8dca5..1d50d03d7dc 100644 --- a/src/auto-reply/reply/commands-dock.ts +++ b/src/auto-reply/reply/commands-dock.ts @@ -38,6 +38,10 @@ function resolveTargetChannelAccountId( return normalizeOptionalString(plugin?.config.defaultAccountId?.(params.cfg)) || "default"; } +function isDirectDockSource(params: HandleCommandsParams): boolean { + return normalizeLowercaseStringOrEmpty(params.ctx.ChatType) === "direct"; +} + function collectSourcePeerCandidates(params: HandleCommandsParams): string[] { return [ params.ctx.NativeDirectUserId, @@ -126,6 +130,14 @@ export const handleDockCommand: CommandHandler = async (params, allowTextCommand reply: { text: `Already docked to ${targetChannel}.` }, }; } + if (!isDirectDockSource(params)) { + return { + shouldContinue: false, + reply: { + text: `Cannot dock to ${targetChannel}: docking is only available from direct chats.`, + }, + }; + } const sourceCandidates = buildSourceIdentityCandidates(params, sourceChannel); if (sourceCandidates.size === 0) {