Skip to content

fix(message-tool): apply messages.responsePrefix to outbound sends#93639

Merged
vincentkoc merged 2 commits into
openclaw:mainfrom
ZengWen-DT:fix/39913-responseprefix-message-tool
Jul 1, 2026
Merged

fix(message-tool): apply messages.responsePrefix to outbound sends#93639
vincentkoc merged 2 commits into
openclaw:mainfrom
ZengWen-DT:fix/39913-responseprefix-message-tool

Conversation

@ZengWen-DT

@ZengWen-DT ZengWen-DT commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fixes messages.responsePrefix being silently dropped on message(action=send) tool sends, so the disambiguation prefix now shows on tool-initiated messages the same way it already does on direct replies.
  • Resolves the configured prefix in handleSendAction (via the existing resolveResponsePrefix, honoring account/channel/global precedence and auto), interpolates it through the same resolveResponsePrefixTemplate as the reply path so identity tokens like [{identity.name}] render, and prepends it to the outbound text, guarded by a startsWith check so re-runs stay idempotent.
  • Leaves the internal source-reply path (handleInternalSourceReplySendAction) unchanged — it keeps flowing through the normal reply normalization, whose own startsWith guard already prevents any double prefix. broadcast re-enters this same send path, so it inherits the prefix without extra code.

Linked context

Refs #39913

This PR lands the static + identity-token portion of the fix: message(action=send) now applies a literal messages.responsePrefix and interpolates identity tokens like [{identity.name}] (via the same resolveResponsePrefixTemplate the reply path uses). Prefixes containing {provider} / {model} / {full-model} / {thinking} are intentionally skipped on tool sends: a tool send performs no live model selection, so resolving those tokens is out of scope here and emitting them raw would leak a literal {model} into the delivered text. That selected-model template parity stays tracked by #39913, so this PR uses Refs rather than Closes.

Re-do of the closed, unmerged #92299 (the author closed it to free PR slots after the ClawSweeper run failed to complete — the rating was an infra non-completion, not a rejection of the fix). The June 15 Codex review on the issue confirms it is still reproducible on main and canonical: the normal reply path applies the prefix, but the shared message(action="send") builder returns text without it. This PR scopes the fix to the genuine cross-target send path rather than the shared builder, to avoid touching source-reply semantics.

AI-assisted: drafted and verified with Claude Code; all evidence below was run locally by me.

Real behavior proof

  • Behavior or issue addressed: messages.responsePrefix applied to direct replies but not to message(action=send) tool sends, so the prefix was intermittently "forgotten" on cross-chat sends.
  • Real environment tested: live Telegram bot (@my_zw_openclaw_bot) on a local gateway, Node 22, WSL2, sending to a real private chat. The CLI openclaw message send routes through the exact patched path (runMessageAction then handleSendAction), which is the same code the agent message tool uses.
  • Root cause / premise evidence: src/infra/outbound/message-action-runner.ts buildSendPayloadParts builds the send payload text without resolving/prepending responsePrefix; only the reply path (normalize-reply.ts:131-138) prepends it.
  • Before evidence (bug reproduced on main): with messages.responsePrefix: "[Nexus]", restoring message-action-runner.ts to upstream/main and running the new test reproduces the drop (terminal output):
× prepends messages.responsePrefix to message-tool sends
AssertionError: expected 'hello world' to be '[Nexus] hello world'
Tests  1 failed | 12 passed (13)
  • Exact steps or command run after this patch:
# config: messages.responsePrefix = "[Nexus]", messages.tts.auto = "off"
pnpm openclaw gateway run            # live telegram bot connects
pnpm openclaw message send --channel telegram --target <chat> --message "responsePrefix live test 39913 TEXT" --json
  • Evidence after fix: live console output from openclaw message send (real delivery, not a mock), the gateway runtime log line for the send, and Telegram's own returned Message object read back from the local telegram message-cache. Command console output:
{ "action": "send", "channel": "telegram", "dryRun": false,
  "handledBy": "plugin", "messageId": "13", "payload": { "ok": true, "messageId": "13" } }

Gateway runtime log:

[telegram] outbound send ok accountId=default chatId=<chat> messageId=13 operation=sendMessage deliveryKind=text chunkCount=1

Delivered message text as Telegram returned it (I sent responsePrefix live test 39913 TEXT; it arrived prefixed):

DELIVERED TEXT: "[Nexus] responsePrefix live test 39913 TEXT"
  • Observed result after fix: the live openclaw message send console output and the gateway runtime log confirm a real Telegram delivery whose text arrived as [Nexus] responsePrefix live test 39913 TEXT — the configured prefix is now applied to message-tool sends.
  • What was not tested: per-channel/account responsePrefix overrides and {provider}/{model}-style selected-model templates were exercised only via the helper unit (resolveResponsePrefix) and existing identity tests, not live — those model templates are intentionally skipped on tool sends (see Linked context). Only Telegram was sent live; other channels share the same channel-agnostic handleSendAction path. An earlier live attempt with the account default messages.tts.auto: "always" was delivered as a TTS voice message (the prefix is then spoken, not visible), so I set tts.auto: "off" for the text-visible run.

Tests and validation

  • pnpm tsgo:core (clean)
  • node scripts/run-vitest.mjs run src/infra/outbound/message-action-runner.core-send.test.ts (13 passed, incl. 3 new)
  • Related outbound + identity suite: 121 passed across 5 files.

Risk checklist

Did user-visible behavior change? Yesmessage(action=send) outbound text now carries messages.responsePrefix. This is the documented intent of the prefix (AI/human disambiguation) and matches the existing reply path; sends with no configured prefix are unaffected.

Did config, environment, or migration behavior change? No — reuses the existing messages.responsePrefix config and its account/channel/global precedence; no new keys, defaults, or migrations.

@openclaw-barnacle openclaw-barnacle Bot added size: S triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels Jun 16, 2026
@ZengWen-DT
ZengWen-DT marked this pull request as draft June 16, 2026 13:46
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels Jun 16, 2026
@ZengWen-DT
ZengWen-DT marked this pull request as ready for review June 16, 2026 14:53
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 16, 2026
@clawsweeper

clawsweeper Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 26, 2026, 1:01 PM ET / 17:01 UTC.

Summary
The PR updates the shared outbound message send path to resolve and prepend static or identity messages.responsePrefix values and adds core-send regression tests.

PR surface: Source +37, Tests +140. Total +177 across 2 files.

Reproducibility: yes. Source inspection shows normal replies prepend messages.responsePrefix, while current main's message-tool send path builds and dispatches raw message text; inspected Mantis baseline proof shows the same visible Telegram omission.

Review metrics: 1 noteworthy metric.

  • Existing Config Behavior: 1 changed, 0 added, 0 removed. The PR broadens the effect of the existing messages.responsePrefix config to visible message-tool sends, which maintainers should explicitly accept before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #39913
Summary: This PR is the active candidate partial fix for the canonical message-tool responsePrefix omission, while selected-model template parity remains tracked by the open issue.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🐚 platinum hermit
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:

  • Get maintainer acceptance that this PR lands static/identity prefixing while selected-model template parity remains tracked separately.

Risk before merge

  • [P1] Existing users with static or identity messages.responsePrefix configured will start seeing that prefix on visible message(action="send") deliveries after upgrade; this matches the intended disambiguation behavior but is still a user-visible output change.
  • [P1] Prefixes that depend on provider, model, full-model, or thinking context are intentionally skipped on tool sends, so maintainers should only land this as a partial fix while keeping the canonical issue open for selected-model parity.

Maintainer options:

  1. Accept Partial Static/Identity Support (recommended)
    Land this PR with the canonical issue open so static and identity prefixes are fixed now while selected-model context remains explicitly tracked.
  2. Thread Selected-Model Context First
    Before merge, pass provider, model, full-model, and thinking context into agent-originated message-tool sends if maintainers want complete template parity in one change.
  3. Pause For Scope Decision
    Hold or close this PR if maintainers decide partial prefixing creates more user confusion than leaving the send path unchanged until full parity exists.

Next step before merge

  • [P2] The remaining action is maintainer acceptance of the output-change and partial-template scope, not an automatable code repair.

Security
Cleared: No concrete security or supply-chain concern was found; the diff only changes TypeScript outbound-send logic and focused tests.

Review details

Best possible solution:

Land this partial send-path fix only if maintainers accept the upgrade-visible output change and keep #39913 open for selected-model template parity.

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

Yes. Source inspection shows normal replies prepend messages.responsePrefix, while current main's message-tool send path builds and dispatches raw message text; inspected Mantis baseline proof shows the same visible Telegram omission.

Is this the best way to solve the issue?

Yes for the scoped partial fix. The shared outbound send layer is the right owner boundary for static and identity prefixes, but full provider/model/thinking template parity needs a separate maintainer scope decision because tool sends do not currently carry selected-model context.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 1cd6f81a46ae.

Label changes

Label justifications:

  • P2: This is a bounded user-visible outbound formatting bugfix with limited blast radius and an open canonical tracker for the remaining template case.
  • merge-risk: 🚨 compatibility: Merging changes visible output for existing static or identity messages.responsePrefix setups during upgrades.
  • merge-risk: 🚨 message-delivery: The shared send path changes delivered message text and intentionally still suppresses templated prefixes when selected-model context is unavailable.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (screenshot): The PR body includes live Telegram delivery output, and inspected Mantis screenshots show baseline unprefixed delivery versus candidate prefixed delivery for the scoped behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes live Telegram delivery output, and inspected Mantis screenshots show baseline unprefixed delivery versus candidate prefixed delivery for the scoped behavior.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The PR body includes live Telegram delivery output, and inspected Mantis screenshots show baseline unprefixed delivery versus candidate prefixed delivery for the scoped behavior.
  • mantis: telegram-visible-proof: Mantis should capture Telegram visible proof. The PR changes visible Telegram outbound message text through the shared send path, which is exactly the kind of behavior a short Telegram Desktop proof can show.
Evidence reviewed

PR surface:

Source +37, Tests +140. Total +177 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 37 0 +37
Tests 1 140 0 +140
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 177 0 +177

What I checked:

Likely related people:

  • mudrii: Commit history shows per-channel/account responsePrefix precedence and resolveResponsePrefix were added in the resolver this PR reuses. (role: introduced responsePrefix cascade; confidence: high; commits: 5d82c82313a1; files: src/agents/identity.ts, src/channels/reply-prefix.ts)
  • sebslight: History shows the response-prefix template resolver and normal reply interpolation were added in the dynamic template variables work. (role: introduced dynamic prefix behavior; confidence: high; commits: d0a4cce41e6d; files: src/auto-reply/reply/response-prefix-template.ts, src/auto-reply/reply/normalize-reply.ts, src/config/types.messages.ts)
  • steipete: History shows substantial adjacent work in message-tool unification, cross-context messaging, and reply-prefix context sharing around this path. (role: recent adjacent area contributor; confidence: medium; commits: 3636a2bf51d9, 46015a3dd8b1, 1113f17d4c75; files: src/infra/outbound/message-action-runner.ts, src/channels/reply-prefix.ts)
  • vincentkoc: Recent history includes outbound delivery and message-action runner changes in the same shared send surface. (role: recent outbound area contributor; confidence: medium; commits: a22a1edc8faa, 0ea08076c3b5; files: src/infra/outbound/message-action-runner.ts)
  • Ayaan Zaidi: Current blame for the narrowed send payload and related resolver lines points to a recent outbound type-preservation refactor, making this a useful routing candidate for current shape questions. (role: recent current-main refactor contributor; confidence: medium; commits: 8bc069f76f62; files: src/infra/outbound/message-action-runner.ts, src/agents/identity.ts, src/auto-reply/reply/response-prefix-template.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.

@ZengWen-DT

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@ZengWen-DT
ZengWen-DT force-pushed the fix/39913-responseprefix-message-tool branch from 9160d4d to 1eca756 Compare June 18, 2026 07:52
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. mantis: telegram-visible-proof Mantis should capture Telegram visible proof. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jun 19, 2026
@clawsweeper
clawsweeper Bot temporarily deployed to qa-live-shared June 19, 2026 20:36 Inactive
@openclaw-mantis

Copy link
Copy Markdown
Contributor

Mantis Telegram Desktop Proof

Summary: Mantis captured native Telegram Desktop before/after GIFs showing the outbound Telegram message without and with the configured response prefix.

Main screenshot This PR screenshot
Baseline native Telegram Desktop screenshot Candidate native Telegram Desktop screenshot
Main This PR
Baseline native Telegram Desktop proof GIF Candidate native Telegram Desktop proof GIF

Motion-trimmed clips:

Raw QA files: https://artifacts.openclaw.ai/mantis/telegram-desktop/pr-93639/run-27847236857-1/index.json

@clawsweeper clawsweeper Bot added the proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. label Jun 20, 2026
@ZengWen-DT
ZengWen-DT force-pushed the fix/39913-responseprefix-message-tool branch from 1eca756 to 4ad95d8 Compare June 20, 2026 14:58
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 🛠️ actively grinding The PR author has acted after the latest ClawSweeper review and work remains. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 20, 2026
@clawsweeper clawsweeper Bot added status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed status: 🛠️ actively grinding The PR author has acted after the latest ClawSweeper review and work remains. labels Jun 21, 2026
@ZengWen-DT
ZengWen-DT force-pushed the fix/39913-responseprefix-message-tool branch from 4ad95d8 to d656910 Compare June 21, 2026 03:13
@ZengWen-DT

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main (the prior red CI — build-artifacts, core-tooling, control-plane-startup, boundaries — came from a ~1000-commit-stale base, not the diff) and addressed the remaining template-prefix blocker.

What changed

  • message(action=send) now interpolates messages.responsePrefix through resolveResponsePrefixTemplate (same path as normalize-reply.ts), so identity tokens like [{identity.name}] render instead of leaking literal braces.
  • A tool send performs no live model selection, so {provider}/{model}/{thinkingLevel} cannot be resolved here. When any placeholder stays unresolved we skip prefixing rather than emit a literal [{provider}/{model}] — directly closing the P1 risk Codex flagged. Full model-context parity would require plumbing the run's selected model through the message-tool execution contract, which is out of this PR's scope and noted as follow-up.

Tests (message-action-runner.core-send.test.ts, 15 pass):

  • identity template resolves on send → [Nexus] hello world
  • unresolved model template is dropped, not leaked → hello world

Local: tsgo:core + tsgo:test:src clean.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@ZengWen-DT

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Updated the linked context: this PR now uses Refs #39913 instead of Closes, so the canonical issue stays open to track selected-model ({provider}/{model}/{thinking}) template parity. The PR lands the static + identity-token prefix behavior; model-template prefixes are intentionally skipped on tool sends (no live model selection in that path) rather than leaking a literal {model}.

@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. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 23, 2026
@vincentkoc vincentkoc self-assigned this Jul 1, 2026
@vincentkoc
vincentkoc merged commit 2af2eb2 into openclaw:main Jul 1, 2026
190 of 193 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mantis: telegram-visible-proof Mantis should capture Telegram visible proof. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal backlog priority with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S 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.

2 participants