Skip to content

fix(reply): derive source-reply explicit-command bypass from authorized + control-command body#86874

Merged
steipete merged 1 commit into
mainfrom
ak/canonical-source-reply-command-bypass
May 27, 2026
Merged

fix(reply): derive source-reply explicit-command bypass from authorized + control-command body#86874
steipete merged 1 commit into
mainfrom
ak/canonical-source-reply-command-bypass

Conversation

@amknight

Copy link
Copy Markdown
Member

Summary

Pushes the /new, /reset, /abort (and friends) acknowledgement-visibility fix from #86825 / #86664 down one layer, into the canonical source-reply decision in isExplicitSourceReplyCommand. The result is one core file change that covers every channel — current and future — instead of repeating the same authorized-control-command formula at every ingress.

Supersedes PR #86863 (the per-channel sweep), which I am closing in favour of this.

Why

Every per-channel "fix" I added in #86863 was just:

CommandSource: commandAuthorized && hasControlCommand ? "text" : undefined

— a universal formula whose only purpose was to make isExplicitCommandTurn return true on the inbound context so the message_tool_only suppression bypass would fire. The downstream check at source-reply-delivery-mode.ts:54 already has cfg and CommandAuthorized and CommandBody in scope; it can derive the same signal itself without ever asking channels to tag it.

Change

// src/auto-reply/reply/source-reply-delivery-mode.ts
export function isExplicitSourceReplyCommand(
  ctx: SourceReplyDeliveryModeContext,
  cfg?: OpenClawConfig,
): boolean {
  if (isExplicitCommandTurn(resolveCommandTurnContext(ctx))) {
    return true;
  }
  if (ctx.CommandAuthorized === true && isControlCommandMessage(ctx.CommandBody, cfg)) {
    return true;
  }
  return false;
}

Two callers updated to pass cfg (both already had it in scope):

  • src/auto-reply/reply/source-reply-delivery-mode.ts:54 (inside resolveSourceReplyDeliveryMode, has params.cfg)
  • src/auto-reply/reply/dispatch-from-config.ts:1325 (inside dispatchReplyFromConfig, has destructured cfg)

That's it. No channel changes, no SDK changes, no new optional fields on inbound entries, no copy-pasted comment blocks.

Behaviour equivalence

The only user-visible effect of CommandSource: "text" in the current codebase is making isExplicitCommandTurn return true on isExplicitSourceReplyCommand. There are two consumers:

Consumer Effect Behaviour with this change
source-reply-delivery-mode.ts:54 (the suppression bypass) When true, returns "automatic" delivery Derived path covers it: any channel that marked the turn authorized for a control-command body wins the bypass
dispatch-from-config.ts:1325 (prefersMessageToolDelivery) When true, does NOT prefer message-tool delivery Same isExplicitSourceReplyCommand call, same derived behaviour

The third consumer of kind: "text-slash"isUnauthorizedTextSlashCommand at source-reply-delivery-mode.ts:60-64 — is intentionally untouched. That defense fires when CommandSource is "text" AND CommandAuthorized is false; channels that want the unauthorized-text-slash defense to activate on typed /foo from non-allowed users still need explicit tagging. My superseded sweep PR did not activate this defense either (it gated on commandAuthorized &&), so this change is strictly equivalent to it.

Inline-token discrimination

The derived bypass uses isControlCommandMessage(body, cfg), not the looser shouldComputeCommandAuthorized(body, cfg). Bodies like "hey can you /status please" correctly do NOT take the bypass even if the channel marked the turn authorized — only bodies that are themselves a configured control command qualify. Captured as an explicit assertion in the new test.

What gets fixed

Channel Before After
Mattermost Fixed via #86825 (sets CommandSource: "text" explicitly) Same — derived path is also reached but the explicit tag wins first
Feishu, IRC, Line, MS Teams, Nextcloud Talk, Signal, ClickClack, QA Channel, Nostr Broken — channels never tag CommandSource Fixed — derived path activates the bypass
direct-dm SDK consumers Broken — helper does not propagate commandSource Fixed — derived path activates the bypass without needing a new SDK param
Any future channel that follows the existing inbound-context pattern Would have been broken until tagged Just works

Tests

Added one focused regression case in src/auto-reply/reply/source-reply-delivery-mode.test.ts:

it("treats authorized control-command bodies as explicit replies even when CommandSource is missing", () => {
  expect(
    resolveSourceReplyDeliveryMode({
      cfg: globalToolOnlyReplyConfig,
      ctx: { ChatType: "direct", CommandAuthorized: true, CommandBody: "/reset" },
    }),
  ).toBe("automatic");
  expect(
    resolveSourceReplyDeliveryMode({
      cfg: globalToolOnlyReplyConfig,
      ctx: { ChatType: "direct", CommandAuthorized: true, CommandBody: "hey can you /status please" },
    }),
  ).toBe("message_tool_only");
});
  • Suite result: 25/25 pass (was 22; +1 test case asserts 2 expectations + an inline-token negative case = 3 expectations).
  • Negative-control proof: reverted both source files via git stash and reran the new case → fails as expected. With the fix restored, passes.

What I'm still validating

I asked you to land the PR first, then validate broader; tracking these:

  • Broader src/auto-reply/ suite shows pre-existing flake patterns; need to disambiguate which (if any) are related to this change vs. environment.
  • check:changed lanes (lint, import-cycles, typecheck on changed files).
  • scripts/check-changelog confirming no entry is required for a fix landing.

I'll report back with proof or fixes shortly.

PR cross-references

@clawsweeper

clawsweeper Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed May 27, 2026, 12:45 AM ET / 04:45 UTC.

Summary
The branch adds config-aware explicit command-turn detection for authorized control-command bodies and uses it in source-reply delivery plus plugin-bound dispatch fallback decisions.

PR surface: Source +60, Tests +315. Total +375 across 6 files.

Reproducibility: yes. source inspection gives a high-confidence reproduction path: current main treats missing CommandSource as a normal non-explicit turn even when CommandAuthorized is true and CommandBody is a control command. I did not run tests because this review was constrained to read-only inspection.

Review metrics: 1 noteworthy metric.

  • Runtime delivery decisions: 2 consumers changed. Both source-reply suppression and plugin-bound fallback routing now depend on the new config-aware explicit-command predicate.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Run current-head checks after the force-push.
  • Add or attach a Telegram-visible proof for the authorized control-command acknowledgement path.

Mantis proof suggestion
A native Telegram recording would directly show whether an authorized control command without CommandSource gets a visible acknowledgement instead of remaining suppressed. A maintainer can ask Mantis to capture proof by posting a new PR comment that starts with the OpenClaw Mantis account mention, followed by:

telegram desktop proof: verify that an authorized /reset@openclaw in a plugin-bound Telegram topic gets a visible acknowledgement and inline text with /status does not.

Risk before merge

  • The new predicate changes visible reply delivery and plugin-bound fallback routing for every channel that sets CommandAuthorized without CommandSource; a false positive could surface replies that should remain message-tool-only, while a false negative keeps acknowledgements suppressed.
  • The latest head was force-pushed after the previous ClawSweeper review, so maintainers should require current-head checks and preferably one real Telegram-visible proof before merge.

Maintainer options:

  1. Prove current-head delivery before merge (recommended)
    Run current-head checks and a Telegram visible acknowledgement proof for an authorized control command without CommandSource before landing the cross-channel predicate.
  2. Accept unit-level proof
    Maintainers can choose to land based on the source-level fix and targeted regression tests, accepting that live channel routing proof is deferred.

Next step before merge
Protected maintainer/member PR with cross-channel message-delivery risk; the next action is maintainer review plus current-head CI and live/transport proof, not an automated repair.

Security
Cleared: The diff touches core TypeScript reply-routing logic and tests only; no dependency, workflow, credential, package, or supply-chain surface is changed.

Review details

Best possible solution:

Land the central helper after maintainer review, current-head checks, and visible transport proof confirm the inline-token and structured-normal safeguards hold across the message-delivery path.

Do we have a high-confidence way to reproduce the issue?

Yes, source inspection gives a high-confidence reproduction path: current main treats missing CommandSource as a normal non-explicit turn even when CommandAuthorized is true and CommandBody is a control command. I did not run tests because this review was constrained to read-only inspection.

Is this the best way to solve the issue?

Yes, with maintainer proof: centralizing the authorized-control-command decision is narrower than repeating per-channel tags and the patch preserves inline-token and structured-normal safeguards. Because it affects visible message delivery across channels, current-head checks and a live Telegram-style proof are still the right merge gate.

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against bf1a5c330397.

Label changes

Label justifications:

  • P1: The PR targets a regression where state-changing control commands can complete without visible acknowledgements in real channel workflows.
  • merge-risk: 🚨 message-delivery: The central predicate can change whether replies are delivered visibly, suppressed, or routed through plugin-bound fallback across channels.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Not applicable: The external contributor proof gate does not apply to this member/maintainer-labeled PR; the PR body has targeted unit and negative-control proof, but live transport proof would still reduce message-delivery risk.
  • mantis: telegram-visible-proof: Mantis should capture Telegram visible proof. The PR changes visible Telegram-topic acknowledgement behavior in the plugin-bound fallback path, which is suitable for a short Telegram Desktop proof.
Evidence reviewed

PR surface:

Source +60, Tests +315. Total +375 across 6 files.

View PR surface stats
Area Files Added Removed Net
Source 3 76 16 +60
Tests 3 315 0 +315
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 6 391 16 +375

What I checked:

  • Repository policy read: Root AGENTS.md was read fully and its ClawSweeper guidance applies because this PR changes channel/message-delivery behavior and asks for whole-surface review plus visible Telegram proof when feasible. (AGENTS.md:1)
  • Current main still requires explicit command source metadata: On current main, source reply delivery only treats a turn as explicit through isExplicitCommandTurn(resolveCommandTurnContext(ctx)), so CommandAuthorized plus CommandBody alone is not enough. (src/auto-reply/reply/source-reply-delivery-mode.ts:25, bf1a5c330397)
  • Current main defaults missing CommandSource to a normal non-authorized turn: resolveCommandTurnContext maps missing CommandSource to source "message" and forces normal turns to authorized false, which explains why channels that only set CommandAuthorized miss the explicit-command bypass. (src/auto-reply/command-turn-context.ts:160, bf1a5c330397)
  • Current main has affected ingress shapes: The direct-dm helper writes CommandAuthorized into the finalized inbound context without CommandSource, and rg found the same CommandAuthorized-only pattern across several channel ingresses named in the PR body. (src/plugin-sdk/direct-dm.ts:124, bf1a5c330397)
  • PR centralizes the derived explicit-command decision: The branch adds isExplicitCommandTurnContext, preserving existing explicit native/text handling and then accepting only authorized control-command bodies via isControlCommandMessage. (src/auto-reply/command-turn-detection.ts:39, 3e9199f3ad2e)
  • PR updates both runtime consumers: The branch passes cfg into isExplicitSourceReplyCommand from source-reply mode selection and from dispatch fallback logic, covering both message_tool_only suppression and plugin-bound fallback routing. (src/auto-reply/reply/dispatch-from-config.ts:1446, 3e9199f3ad2e)

Likely related people:

  • amknight: Authored the merged Mattermost-specific CommandSource fix that this PR generalizes, and authored the current branch's central reply-delivery change. (role: recent area contributor; confidence: high; commits: 21aebd5fbc14, 3e9199f3ad2e; files: extensions/mattermost/src/mattermost/monitor.ts, src/auto-reply/command-turn-detection.ts, src/auto-reply/reply/source-reply-delivery-mode.ts)
  • Peter Steinberger: Recent current-main snapshot/blame and local history show repeated work in the central auto-reply dispatch and source-reply files touched by this PR. (role: recent area contributor; confidence: medium; commits: b74cd69c6f87, a374c3a5bfd5, f1d381507779; files: src/auto-reply/reply/dispatch-from-config.ts, src/auto-reply/reply/source-reply-delivery-mode.ts, src/auto-reply/command-turn-context.ts)
  • Vincent Koc: Local history shows multiple recent central auto-reply dispatch and contract refactors in the same files, making him a plausible reviewer for dispatch boundary behavior. (role: adjacent owner; confidence: medium; commits: 7c91d0dbc985, a88fbf0f6476, 74e7b8d47b; files: src/auto-reply/reply/dispatch-from-config.ts, src/auto-reply/reply/source-reply-delivery-mode.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels May 26, 2026
@clawsweeper

clawsweeper Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

✨ Hatched: 🥚 common Pearl Review Wisp

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.

Rarity: 🥚 common.
Trait: guards the happy path.
Image traits: location merge queue dock; accessory review stamp; palette sunrise gold and clean white; mood bright-eyed; pose holding its accessory up for inspection; shell matte ceramic shell; lighting tiny status-light glow; background subtle branch markers.
Share on X: post this hatch
Copy: My PR egg hatched a 🥚 common Pearl Review Wisp in ClawSweeper.

What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

@steipete steipete self-assigned this May 27, 2026
@steipete
steipete force-pushed the ak/canonical-source-reply-command-bypass branch from 3128f07 to 6cf4120 Compare May 27, 2026 04:13
@openclaw-barnacle openclaw-barnacle Bot added size: M maintainer Maintainer-authored PR labels May 27, 2026
@clawsweeper clawsweeper Bot added the mantis: telegram-visible-proof Mantis should capture Telegram visible proof. label May 27, 2026
@steipete
steipete force-pushed the ak/canonical-source-reply-command-bypass branch from 6cf4120 to 3e9199f Compare May 27, 2026 04:38
@steipete
steipete force-pushed the ak/canonical-source-reply-command-bypass branch from 3e9199f to 991fd3f Compare May 27, 2026 04:53

Copy link
Copy Markdown
Contributor

Verification for rewritten head 991fd3f655e3b0aa6a20117d1b8ef934cffcac89:

Behavior addressed: authorized control-command bodies without CommandSource now use the same explicit-command decision for source-reply delivery and plugin-bound fallback escape, while structured normal turns and unauthorized native commands stay on the plugin-owned path.

Real environment tested: local source checkout on current origin/main after rebase.

Exact steps or command run after this patch:

  • 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/dispatch-from-config.test.ts
  • pnpm check:test-types
  • /Users/steipete/Projects/agent-scripts/skills/autoreview/scripts/autoreview --mode branch --base origin/main

Evidence after fix: focused Vitest passed 3 files / 208 tests; check:test-types passed core and extensions test tsgo; autoreview clean with no accepted/actionable findings.

Observed result after fix: /reset@openclaw without CommandSource bypasses plugin-owned fallback and produces the core reply, inline hey can you /status please remains message-tool-only, unauthorized native commands remain plugin-bound, and injected structured-normal /think ... bodies remain plugin-bound.

What was not tested: no live Telegram/Desktop proof in this pass; waiting on current-head GitHub Actions after the final force-push.

@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR mantis: telegram-visible-proof Mantis should capture Telegram visible proof. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Beta blocker: Mattermost - /new can silently reset session without visible acknowledgement

2 participants