feat(webchat): reply-to a message with hydrated reply context#110791
Conversation
Control UI replies now carry the target transcript id as replyToId on chat.send. The Gateway resolves the replied-to message from session history and hydrates the channel-agnostic ReplyToId/ReplyToBody/ ReplyToSender envelope fields, so agents receive reply_to_id, has_reply_context, and the untrusted reply-target block exactly like Discord replies (mirrors #90263). Reply targets without a persisted transcript id keep the inline-quote fallback. Co-Authored-By: Claude Fable 5 <[email protected]>
|
Codex review: needs maintainer review before merge. Reviewed July 18, 2026, 4:05 PM ET / 20:05 UTC. Summary PR surface: Source +216, Tests +218, Docs +1, Other +6. Total +441 across 14 files. Reproducibility: not applicable. This PR adds a new reply workflow rather than reporting broken existing behavior; focused tests describe the intended source path, but they are not after-fix runtime proof. Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Accept one additive Do we have a high-confidence way to reproduce the issue? Not applicable: this PR adds a new reply workflow rather than reporting broken existing behavior; focused tests describe the intended source path, but they are not after-fix runtime proof. Is this the best way to solve the issue? Unclear pending maintainer direction: reusing the established channel-agnostic reply envelope is the narrowest implementation path, but the new public Gateway and agent-context contract still needs explicit acceptance. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 1626a1696c60. Label changesLabel justifications:
Evidence reviewedPR surface: Source +216, Tests +218, Docs +1, Other +6. Total +441 across 14 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
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
Review history (4 earlier review cycles)
|
|
Merged via squash.
|
…aw#110791) * feat(webchat): reply-to a message with hydrated reply context Control UI replies now carry the target transcript id as replyToId on chat.send. The Gateway resolves the replied-to message from session history and hydrates the channel-agnostic ReplyToId/ReplyToBody/ ReplyToSender envelope fields, so agents receive reply_to_id, has_reply_context, and the untrusted reply-target block exactly like Discord replies (mirrors openclaw#90263). Reply targets without a persisted transcript id keep the inline-quote fallback. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(webchat): keep non-reply chat.send dispatch ordering and satisfy CI gates * docs(webchat): match reply-context doc to webchat conversation-info policy * fix(webchat): hydrate reply bodies from display-visible content only --------- Co-authored-by: openclaw-clawsweeper[bot] <openclaw-clawsweeper[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <[email protected]> Co-authored-by: fuller-stack-dev <[email protected]>
Summary
Adds reply-to support to the Control UI (webchat) chat, requested by maintainer Json in #maintainers: right-click a message → Reply sets a "Replying to …" chip above the composer (cancel with × or Escape), and the send now delivers the replied-to message to the agent as structured reply context instead of only an inline markdown quote.
This mirrors the Discord reply-context path from #90263 so agent-side handling stays channel-agnostic:
ui/src/pages/chat/...): reply targets picked from persisted transcript messages now carry the transcript id (sourceMessageId).chat.sendincludes it asreplyToIdand skips the inline-quote fallback; targets without a persisted id (e.g. pending sends) keep the quote fallback. The id survives the durable offline outbox (composer-persistence).packages/gateway-protocol):ChatSendParamsSchemagains optionalreplyToId; regenerated Swift protocol models.src/gateway/server-methods/chat-send-reply-context.ts, wired inchat.ts): resolves the target message from session history viareadSessionMessageByIdAsyncand hydrates the channel-agnosticReplyToId/ReplyToBody/ReplyToSenderenvelope fields in parallel with dispatch prep. Downstream this produces the same agent-facing metadata as Discord replies:reply_to_idandhas_reply_contextin "Conversation info", plus the untrusted "Reply target of current user message" block with sender label and body. Missing/unresolvable targets degrade to id-only, mirroring Discord's tolerance for omitted referenced messages; bodies are bounded to 2000 chars.docs/web/webchat.mddocuments the flow.Verification
node scripts/run-vitest.mjs src/gateway/server-methods/chat-send-reply-context.test.ts(new, 8 tests)node scripts/run-vitest.mjs ui/src/pages/chat/chat-send.test.ts ui/src/pages/chat/composer-persistence.test.ts(262 tests, incl. new replyToId payload test)node scripts/run-vitest.mjson related suites: chat-send-request/user-turn/reply-dispatch, gateway-protocol index, chat.directive-tags, chat-send-dispatch-errors, chat.send-deleted-agent, chat-webchat-media, chat-send-command-replies, chat-thread/view/composer/pane/state (all green)pnpm tsgo:coreandpnpm tsgo:uiclean; oxlint on touched paths clean;pnpm protocol:gen/gen:swift/gen:kotlinregenerated; docs format check clean🤖 Generated with Claude Code