fix(channels): tag typed text-slash control commands with CommandSource across all channel ingresses#86863
Closed
amknight wants to merge 1 commit into
Closed
fix(channels): tag typed text-slash control commands with CommandSource across all channel ingresses#86863amknight wants to merge 1 commit into
amknight wants to merge 1 commit into
Conversation
amknight
force-pushed
the
ak/channel-ingress-command-source-sweep
branch
from
May 26, 2026 11:11
081c1b0 to
1403b84
Compare
…ce across all channel ingresses Sweeps the same root-cause fix as PR #86825 (Mattermost) across the remaining channels that exhibited the latent gap: Feishu, IRC, Line, MS Teams, Nextcloud Talk, Signal, ClickClack, QA Channel, and the public 'direct-dm' plugin SDK helper (with Nostr passing through it as the first internal consumer). Each channel inbound handler already computed CommandAuthorized for typed slash commands but never set CommandSource. Without CommandSource: 'text', resolveCommandTurnContext defaults the turn kind to 'normal', so the explicit- command exception in source-reply-delivery-mode.ts:54-56 never fires and acknowledgements ('Session reset.', 'New session started.', 'Stopped N subagents', etc.) are silently dropped under message_tool_only delivery modes — most commonly the Codex harness default for DMs that #83571 introduced. For each channel, this PR sets: CommandSource: commandAuthorized && hasControlCommand ? 'text' : undefined where 'hasControlCommand' uses the channel's existing isControlCommandMessage helper (not shouldComputeCommandAuthorized, which would be too permissive and incorrectly tag inline command tokens within ordinary text). For ClickClack and QA Channel (CommandAuthorized hardcoded true), the tag only depends on the body being a control command. For Signal and Line, a new optional 'hasControlCommand' field on the entry/params struct carries the signal from the access-policy layer where it is already computed. The public direct-dm SDK helper grows an optional commandSource parameter so third-party plugins built on it can pass through their own control-command detection; Nostr is updated as the first consumer. Adds focused regression tests (Signal, MS Teams, direct-dm SDK, plus a negative-control assertion on the existing MS Teams inline-command test) that fail without the fix and pass with it. Fixes #86664.
amknight
force-pushed
the
ak/channel-ingress-command-source-sweep
branch
from
May 26, 2026 11:18
1403b84 to
36861b3
Compare
Member
Author
|
Superseded by #86874 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Sweeps the same root-cause fix as PR #86825 (Mattermost) across the remaining channel ingresses that exhibit the identical latent gap: typed text-slash control commands set
CommandAuthorizedbut notCommandSource, so the explicit-command turn exception insrc/auto-reply/reply/source-reply-delivery-mode.ts:54-56never fires and acknowledgements get silently dropped undermessage_tool_onlydelivery (most commonly the Codex harness default for DMs, introduced by #83571).Closes the rest of issue #86664 across the surface: Feishu, IRC, Line, MS Teams, Nextcloud Talk, Signal, ClickClack, QA Channel, and the public
direct-dmplugin SDK helper (with Nostr passing through it as the first internal consumer).Why one PR
Class-A audit conclusion from the post-mortem of #86664 / #86767 / #86825: the same latent gap was present in 8 channels + 1 SDK helper. The shape of the fix is identical at each site — one ternary
commandAuthorized && hasControlCommand ? "text" : undefined— and the affected channel families are owned across overlapping CODEOWNERS sets. Bundling them keeps the changelog story coherent, prevents the next bug report from re-opening this thread per-channel, and lets the negative-control assertions land together.The fix per file
extensions/irc/src/inbound.ts:396commandAuthorized && hasControlCommand ? "text" : undefinedextensions/nextcloud-talk/src/inbound.ts:350extensions/msteams/src/monitor-handler/message-handler.ts:798commandAuthorized === true && isControlCommand ? "text" : undefinedcommandAuthorizedisboolean | undefinedhereextensions/feishu/src/bot.ts:1302isControlCommandinlineisControlCommandMessage(notshouldComputeCommandAuthorized) to avoid tagging inline-token textextensions/signal/src/monitor/event-handler.ts:227hasControlCommandtoSignalInboundEntry; signal already computeshasControlCommandInMessageat line 579extensions/line/src/bot-message-context.ts:359hasControlCommandtoBuildLineMessageContextParams;bot-handlers.ts:489computes viaisControlCommandMessagefrom the plugin SDKextensions/clickclack/src/inbound.ts:153isControlCommand ? "text" : undefinedCommandAuthorizedis hardcodedtrueon this channelextensions/qa-channel/src/inbound.ts:216CommandAuthorizedis hardcodedtruesrc/plugin-sdk/direct-dm.tscommandSource?: "text" | "native"parameter, piped through to the inbound contextextensions/nostr/src/gateway.ts:179commandSource: "text"whencommandAuthorizedANDruntime.channel.commands.isControlCommandMessage(text, cfg)The narrow
isControlCommandMessagepredicate (notshouldComputeCommandAuthorized) is deliberate: only true typed control commands should take the source-suppression bypass; inline-token text like "hello /status" must NOT trigger the bypass because the model owns that turn's reply.Regression tests added
The bypass tag is the user-visible contract — these tests assert it explicitly, mirroring the established pattern from
agent-runner-payloads.test.ts:86,agent-runner-direct-runtime-config.test.ts:284-285, etc.src/plugin-sdk/direct-dm.test.ts: new"propagates commandSource: text onto the inbound context for authorized control commands"test + aCommandSource: undefinedback-compat assertion on the existing pipeline test.extensions/signal/src/monitor/event-handler.inbound-context.test.ts:494: extended the existing "authorizes group control commands" test to also assertCommandSource === "text".extensions/msteams/src/monitor-handler/message-handler.authz.test.ts: extended both the existing inline-command test (assertsCommandSource === undefined) and the bare-abort test (assertsCommandSource === "text").All three new assertions fail without the corresponding fix and pass with it (negative-control proof captured in the Real behavior proof section below).
Channels without an existing ctxPayload-shape test harness (ClickClack, QA Channel, Nextcloud Talk, IRC, Feishu, Line at the bot-message-context level) are covered by the full per-channel test sweeps remaining green (each is a one-line ternary using already-in-scope locals; the structural change is equivalent to what the SDK + Signal + MS Teams tests already prove).
Mock surfaces updated where TypeScript identified missing fields:
extensions/feishu/src/bot.test.ts,extensions/feishu/src/bot.broadcast.test.ts,extensions/nostr/src/channel.inbound.test.ts(all addisControlCommandMessage: vi.fn(() => false)to the runtimecommandsmock).Verification
node scripts/run-vitest.mjs extensions/clickclack/ extensions/qa-channel/ extensions/irc/ extensions/nextcloud-talk/ extensions/signal/ extensions/msteams/ extensions/feishu/ extensions/nostr/ extensions/line/ src/plugin-sdk/direct-dm.test.ts→ 231 files, 2745 tests, all pass.node scripts/run-vitest.mjs src/auto-reply/command-turn-context.test.ts src/auto-reply/reply/source-reply-delivery-mode.test.ts src/auto-reply/reply/commands-reset-hooks.test.ts→ 42/42 pass (the upstream contract this fix depends on).npx oxfmt --check --threads=1 <touched files>→ all clean.node scripts/run-oxlint-shards.mjs→ all shards finished cleanly.node --import tsx scripts/check-import-cycles.ts→ 0 runtime value cycles.node scripts/check-changed.mjs --base origin/main --staged-too→ all change-impact lanes pass; thetypecheck alllane stops on a pre-existing-on-origin/mainerror inextensions/codex/src/app-server/run-attempt.test.ts:6830(Argument of type '180000' is not assignable to parameter of type '120000'). Verified viagit diff --stat origin/main -- extensions/codex/returning empty — that file is unchanged fromorigin/main. Not introduced or worsened by this PR.Real behavior proof
Behavior addressed: Typed
/new,/reset,abort,stop(and any other text-slash control command recognised byisControlCommandMessage) on any of the listed channels undermessage_tool_onlysource-reply mode silently change session state without a visible acknowledgement. This is the channel-wide companion to the Mattermost-specific fix in fix(mattermost): tag typed text-slash control commands so /new and /reset acks survive message_tool_only suppression #86825 and resolves the rest of Beta blocker: Mattermost - /new can silently reset session without visible acknowledgement #86664's blast radius.Real environment tested: Unit-harness reproduction and end-to-end ctxPayload assertion on macOS 26 arm64 / Node 25, exercising the actual
monitorXxx/dispatchInboundDirectDmWithRuntimeentry points each channel runs in production. No live Crabbox session was run — each fix is the same one-line ingress tag the Mattermost precedent (fix(mattermost): tag typed text-slash control commands so /new and /reset acks survive message_tool_only suppression #86825) validated, and the negative-control assertions exercise the exact ctxPayload field that the downstream suppression check (dispatch-from-config.ts:2400-2414) consumes.Exact steps or command run after this patch:
Evidence after fix: All three pass.
Negative-control proof (each channel's fix reverted via
git stash, regression tests rerun):Each assertion fails on the exact line that reads
expect(ctxPayload.CommandSource).toBe("text")(or the SDKcommandSource: "text"round-trip), proving the test exercises the bug path. With the fix restored, all three pass.Observed result after fix: Typed authorized control commands on Feishu, IRC, Line, MS Teams, Nextcloud Talk, Signal, ClickClack, QA Channel, and Nostr (plus any plugin built on
direct-dm) post the appropriate acknowledgement (✅ Session reset.,✅ New session started.,⚠️ Stopped N subagents, etc.) instead of silently mutating session state undermessage_tool_onlymode.What was not tested: Live channel deployments under the Codex harness for each of the nine surfaces. Each fix is structurally identical to the Mattermost precedent (fix(mattermost): tag typed text-slash control commands so /new and /reset acks survive message_tool_only suppression #86825) which was validated end-to-end, and the negative-control unit assertions exercise the contract the downstream suppression check reads. The full test sweep above covers every channel module.