Skip to content

fix(anthropic): surface reasoning and pre-tool commentary on Discord#96106

Merged
obviyus merged 26 commits into
openclaw:mainfrom
Marvinthebored:pr/anthropic-min
Jul 1, 2026
Merged

fix(anthropic): surface reasoning and pre-tool commentary on Discord#96106
obviyus merged 26 commits into
openclaw:mainfrom
Marvinthebored:pr/anthropic-min

Conversation

@Marvinthebored

@Marvinthebored Marvinthebored commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Current revision (head b078781489) — review-responded & Discord-scoped. Addresses the ClawSweeper findings without changing any other channel:

  • Commentary and thinking progress now default off (explicit opt-in); progress.thinking is contract-wired into the type, zod schema, and the generated channel-config metadata (validates at config-load).
  • Message-tool-only reasoning-privacy invariant restored (trailing reasoning suppressed uniformly); opaque tool-call id keying restored.
  • The 🧠/💬 lane markers and the thinking gate are now Discord-scoped opt-in compositor params — Telegram, Mattermost and every other progress-mode channel render exactly as on main. This PR touches no other channel.
  • Verified live on Discord (anthropic/claude-sonnet-4-6), plain (_p_) + verbose/reasoning (_rv_). See the review-response comment for the per-finding walkthrough.

What Problem This Solves

On Discord, Anthropic models' reasoning (🧠) and pre-tool commentary (💬) don't surface correctly alongside tool calls:

  • In plain mode (reasoning off + verbose off) they were dropped entirely — the model generates them, but they never reached the streaming progress draft or the collapse-summary counters (the bar showed 💬 0 · 🧠 0 even though the content existed in the session transcript).
  • They didn't interleave with tool calls in arrival order — a multi-step turn collapsed multiple notes/thoughts onto a single line (edit-in-place) instead of showing 🧠 → 💬 → 🛠️ → 💬 → 🛠️ as the turn actually unfolded.

Why This Change Was Made

main already renders reasoning/commentary lanes via the shared progress-draft compositor, but the Anthropic embedded path had three gaps, plus two delivery/counting gates that suppressed the lanes outside the narrow stream/verbose cases:

  1. Anthropic tool-turn narration isn't tagged as commentary, so it's never recognized/routed.
  2. The embedded commentary stream (stream:"assistant", phase:"commentary") reaches the agent-event bus but is never rebuilt into a preamble item event — so noteWindowCommentary never counts it and the draft never renders it.
  3. Window thinking was gated on reasoningMode === "stream", so plain mode (reasoning off) dropped reasoning from the window draft and counters. Window thinking is presentation — it should stream for any mode except "on" (which uses durable 🧠 persistence instead).

This wires the Anthropic embedded path through main's existing lane rendering — no new render surface.

User Impact

Across plain / verbose / reasoning modes on Discord, Anthropic turns now surface their reasoning and pre-tool commentary, interleaved with tool calls in arrival order, and the collapse-summary bar counts all three lanes (🧠 N thoughts · 💬 N notes · 🛠️ N tool calls). No config changes; no new render surface.

Evidence

Live, on a build carrying these commits (Discord, anthropic/claude-sonnet-4-6), verified by reading the bot's actual channel messages:

Plain mode (reasoning off + verbose off):

  • Streaming draft: 🧠 _…reasoning…_ → 💬 _First, the time:_ → 🛠️ date → 💬 _Next, the two largest files — sorting by size:_ → 🛠️ list files — reasoning + commentary interleaved with tools in arrival order.
  • Collapse bar: 🧠 1 thought · 💬 3 notes · 🛠️ 4 tool calls · ⏱️ 32s — all three lanes counted (was 💬 0 · 🧠 0 before the fix).

Verbose + reasoning mode: commentary renders as durable messages interleaved with tools: 💬 → 🛠️ → 💬 → 🛠️ → 💬 → 🛠️. No regression.

After-fix evidence (plain mode, reasoning off + verbose off):

Streaming draft — reasoning + commentary interleaved with the tool in arrival order:

plain streaming interleave

Collapse summary — all three lanes counted:

plain collapse counts

Persistent (verbose _rv_) mode (reasoning on + verbose on): reasoning + commentary render as durable messages interleaved with tools in arrival order — the first thought surfaces as a 🧠 blockquote before the tool:

rv persistent interleave

Validation: tsgo (core + test-types) pass; lane suites green (message-handler.process.test.ts, embedded-agent-subscribe.handlers.messages.test.ts, progress-draft-compositor.test.ts including the "interleaves reasoning bursts with tool calls in arrival order" assertion); build-all exit 0.

Linked context

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jun 23, 2026
@Marvinthebored
Marvinthebored marked this pull request as ready for review June 23, 2026 15:56
@openclaw-barnacle openclaw-barnacle Bot added size: M and removed triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. size: S labels Jun 23, 2026
@clawsweeper

clawsweeper Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed June 30, 2026, 8:13 PM ET / 00:13 UTC.

Summary
The branch tags Anthropic pre-tool text as commentary, forwards reasoning/commentary lanes through shared dispatch, and renders opted-in Discord progress/durable lanes with docs and regression tests.

PR surface: Source +579, Tests +682, Docs +17, Generated 0, Other 0. Total +1278 across 42 files.

Reproducibility: yes. The linked issue and inspected screenshots show Anthropic reasoning/commentary present in the run but missing from Discord before the fix, and source inspection shows the pre-tool text classification and delivery gates this branch changes.

Review metrics: 3 noteworthy metrics.

  • Plugin-facing reply contract: 1 payload marker, 1 reply option added. isCommentary and commentaryPayloadsEnabled affect channel/plugin delivery expectations beyond the Discord implementation.
  • Invalid test config references: 14 references. The final config contract has no progress.thinking key, so these test setups should be converted before merge.
  • Hidden-content gates reviewed: 2 opt-in lane gates. Reasoning and commentary payloads are user-visible only through explicit channel opt-in gates.

Stored data model
Persistent data-model change detected: serialized state: src/agents/transport-stream-shared.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #96079
Summary: This PR is the candidate fix for the Discord/Anthropic reasoning and pre-tool commentary delivery issue; related reasoning/commentary stream work overlaps but does not replace the remaining Discord/Anthropic behavior.

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:

  • Remove the stale progress.thinking test setup.
  • Get maintainer acceptance for isCommentary/commentaryPayloadsEnabled as the supported commentary lane.

Risk before merge

  • [P1] The PR adds plugin-facing isCommentary and commentaryPayloadsEnabled surfaces, so maintainers should explicitly accept that as the supported commentary sibling to the merged reasoning payload seam.
  • [P1] Anthropic text phasing and subscriber withholding decide whether leading text becomes final answer text, commentary, or suppressed output; that remains message-delivery and hidden-content sensitive even with green CI.
  • [P1] Discord reasoning-progress tests still set the removed progress.thinking key, so part of the test coverage can pass with an invalid config shape instead of the session-level /reasoning stream gate.

Maintainer options:

  1. Clean the stale reasoning tests (recommended)
    Remove progress.thinking from Discord tests or set up the session /reasoning stream state so the tests protect the runtime gate that now owns reasoning display.
  2. Accept commentary as a plugin contract
    Maintainers can accept isCommentary/commentaryPayloadsEnabled as the opt-in commentary sibling to the merged reasoning seam before landing the broader branch.
  3. Keep commentary internal for now
    If the plugin-facing API is not ready, pause or narrow this PR so commentary classification stays internal until the broader stream contract is accepted.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Remove invalid streaming.progress.thinking entries from extensions/discord/src/monitor/message-handler.process.test.ts and make the affected reasoning-progress cases exercise the session-level /reasoning stream gate instead; keep the PR scoped to test setup and run git diff --check plus node scripts/run-vitest.mjs extensions/discord/src/monitor/message-handler.process.test.ts.

Next step before merge

  • [P2] A narrow automated repair can remove invalid test-only progress.thinking setup; maintainer API acceptance remains manual.

Security
Cleared: No supply-chain, secrets, permissions, or code-execution concern was found; hidden reasoning/commentary exposure remains a merge-risk boundary rather than a separate security defect.

Review findings

  • [P3] Remove the deleted thinking config from Discord tests — extensions/discord/src/monitor/message-handler.process.test.ts:2136
Review details

Best possible solution:

Land after converting the stale progress.thinking tests to the session-level reasoning gate and after maintainers accept isCommentary/commentaryPayloadsEnabled as the plugin-facing commentary contract.

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

Yes. The linked issue and inspected screenshots show Anthropic reasoning/commentary present in the run but missing from Discord before the fix, and source inspection shows the pre-tool text classification and delivery gates this branch changes.

Is this the best way to solve the issue?

Yes, with the API acceptance and test cleanup noted above. Classification belongs in the Anthropic transport/subscriber path, shared dispatch keeps hidden payloads default-suppressed, and Discord owns visible presentation.

Full review comments:

  • [P3] Remove the deleted thinking config from Discord tests — extensions/discord/src/monitor/message-handler.process.test.ts:2136
    The progress config contract no longer includes thinking, but these tests still pass it while manually invoking onReasoningStream. That lets the reasoning-progress cases pass with an invalid config shape instead of exercising the session /reasoning stream gate that now owns the behavior.
    Confidence: 0.88

Overall correctness: patch is correct
Overall confidence: 0.86

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 5e572dcf781a.

Label changes

Label justifications:

  • P1: The PR addresses a broken Discord/Anthropic agent-channel workflow where opted-in reasoning and verbose commentary are produced but not surfaced correctly.
  • merge-risk: 🚨 compatibility: The diff adds plugin-facing reply payload and option fields that can become a supported contract for channel/plugin authors.
  • merge-risk: 🚨 message-delivery: The diff changes Anthropic text phasing, durable append timing, and Discord reasoning/commentary delivery paths.
  • merge-risk: 🚨 security-boundary: The diff changes gates around hidden reasoning and commentary content reaching user-visible channels.
  • 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 discussion includes after-fix Discord screenshots, and inspected captures show reasoning stream, durable reasoning, durable commentary, and Opus summary behavior in a live Discord setup.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR discussion includes after-fix Discord screenshots, and inspected captures show reasoning stream, durable reasoning, durable commentary, and Opus summary behavior in a live Discord setup.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The PR discussion includes after-fix Discord screenshots, and inspected captures show reasoning stream, durable reasoning, durable commentary, and Opus summary behavior in a live Discord setup.
Evidence reviewed

PR surface:

Source +579, Tests +682, Docs +17, Generated 0, Other 0. Total +1278 across 42 files.

View PR surface stats
Area Files Added Removed Net
Source 28 887 308 +579
Tests 9 988 306 +682
Docs 2 17 0 +17
Config 0 0 0 0
Generated 1 3 3 0
Other 2 8 8 0
Total 42 1903 625 +1278

Acceptance criteria:

  • [P1] git diff --check.
  • [P1] node scripts/run-vitest.mjs extensions/discord/src/monitor/message-handler.process.test.ts.

What I checked:

  • Repository policy read: Root AGENTS.md and scoped agents, channels, plugin SDK, extensions, docs, and scripts guides were read fully; plugin API, channel delivery, config, hidden-content, and dependency-contract guidance affected this review. (AGENTS.md:1, 5e572dcf781a)
  • Live PR state: GitHub reports head bf0d31e, clean mergeability, all relevant checks completed successfully, and an old CHANGES_REQUESTED review still present from earlier review activity. (bf0d31ef7de9)
  • Merge-result surface checked: The potential merge commit keeps the PR focused to Discord, Anthropic/embedded-agent delivery, dispatch, progress compositor, docs, and tests; stale-base Slack drift is not in the actual merge result. (36d3c21bb6c3)
  • Linked issue remains canonical: The linked issue reports Anthropic reasoning and pre-tool narration produced in the session log but not surfaced in Discord; it remains open and this PR uses closing syntax for it.
  • Anthropic transport classification: The merge result tags pending text blocks as commentary and applies that at the tool_use boundary plus a message_delta/finalize backstop for proxied Anthropic routes. (src/agents/anthropic-transport-stream.ts:585, 36d3c21bb6c3)
  • Default suppression for commentary payloads: Shared dispatch suppresses isCommentary payloads unless commentaryPayloadsEnabled is true and excludes commentary from TTS, matching the hidden-content boundary for non-opt-in channels. (src/auto-reply/reply/dispatch-from-config.ts:3336, 36d3c21bb6c3)

Likely related people:

  • fuller-stack-dev: Authored the merged shared reasoningPayloadsEnabled dispatch seam that this PR mirrors for commentary payload routing. (role: recent area contributor; confidence: high; commits: 455f813d6ee6; files: src/auto-reply/reply/dispatch-from-config.ts, src/auto-reply/get-reply-options.types.ts, extensions/telegram/src/bot-message-dispatch.ts)
  • obviyus: Authored the merged durable commentary lane wiring and identified the Anthropic streaming edge in this PR discussion; also pushed the latest branch repair commit. (role: recent area contributor and reviewer; confidence: high; commits: bd96e4d22daf, bf0d31ef7de9; files: src/auto-reply/reply/dispatch-from-config.ts, src/auto-reply/reply/agent-runner-execution.ts, src/agents/embedded-agent-subscribe.handlers.messages.ts)
  • vincentkoc: Authored the merged Discord reasoning delivery fix across the same Discord message-handler and reply-delivery boundary. (role: adjacent Discord reasoning contributor; confidence: high; commits: 0c210e5e524d; files: extensions/discord/src/monitor/message-handler.process.ts, extensions/discord/src/monitor/reply-delivery.ts)
  • Masato Hoshino: Recent current-main work touched the same embedded-agent and Discord delivery surfaces that this PR builds on, including reply dispatch and message-handler boundaries. (role: recent adjacent contributor; confidence: medium; commits: a75431c586ce; files: src/auto-reply/reply/dispatch-from-config.ts, extensions/discord/src/monitor/message-handler.process.ts, src/agents/embedded-agent-subscribe.handlers.messages.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: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jun 23, 2026
Marvinthebored pushed a commit to Marvinthebored/openclaw that referenced this pull request Jun 24, 2026
@Marvinthebored

Copy link
Copy Markdown
Contributor Author

Real behavior proof — live Discord captures

Complementing the boundary-injection test in the PR body, here are redacted live Discord captures on two Anthropic models with /reasoning on and /verbose on over a tool-using turn.

Build disclosure: captured on the 2026.6.9 line — before = 60fd9895, after = 848073d627. The "after" build carries the identical Anthropic emit/transport changes this PR makes (unconditional display:"summarized" for adaptive thinking, commentary tagging on tool turns, durable-lane forwarding to both the agent-events bus and the runner callback, and discarding signature-only thinking). This PR ports that same logic onto current main, where main's shared progress-draft compositor renders the lanes — so the surfacing behaviour shown here is what the PR produces. Sidebars and usernames cropped out.

Before — opus-4-8, /reasoning on: reasoning is enabled, but it never reaches the channel; only the final answer appears.

before

After — opus-4-8, /reasoning on + /verbose on: the model's reasoning and its pre-tool commentary now appear as separate blocks, ahead of the tool call and final answer.

after-opus

After — sonnet-4-6, /reasoning on + /verbose on: the same on a second Anthropic model — pre-tool commentary, reasoning, and parallel tool calls, then the answer.

after-sonnet

@clawsweeper clawsweeper Bot added proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 24, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. 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: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 24, 2026
@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord size: XL and removed size: M labels Jun 25, 2026
@Marvinthebored

Copy link
Copy Markdown
Contributor Author

Refreshed this PR — the earlier revision was built/captured against a misread local build state, so the diff and proof didn't reflect the intended behavior. This revision is the correct implementation, verified live on Discord (anthropic/claude-sonnet-4-6) in plain and verbose+reasoning modes: reasoning 🧠 and pre-tool commentary 💬 now surface and interleave with tool calls in arrival order, and the collapse bar counts all three lanes. Visual capture (GIF) of the plain-mode interleave to follow shortly.

Peetiegonzalez and others added 19 commits June 30, 2026 16:42
The streamReasoning gate in the shared embedded-agent-subscribe was widened
from reasoningMode === "stream" to !== "on", which also fires the
onReasoningStream hook in "off" mode for EVERY channel. Sibling channels
(Slack when status/tool-progress is on, Mattermost in progress mode) install
that hook and render it on their own progress surfaces, so an operator with
reasoning display off would see thinking text leak into the progress draft —
a regression vs main.

Restore main's stream-only behavior by default and gate the widening behind a
new explicit opt-in param streamReasoningInNonStreamModes, threaded alongside
onReasoningStream through the reply/runner chain. Only the Discord handler
sets it true (where it is further gated by channels.discord.streaming.progress
.thinking). Slack/Mattermost/Telegram/Feishu/msteams/codex are byte-identical
to main.

Adds table-driven regression coverage asserting the hook is NOT invoked in
off mode without the opt-in, preserved in stream mode, and invoked under the
Discord opt-in.

Addresses the ClawSweeper [P1] 'keep non-stream reasoning dispatch
Discord-scoped' finding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t render

The previous commit opted Discord into non-stream reasoning unconditionally
(streamReasoningInNonStreamModes: true). With progress.thinking off (default),
the onReasoningStream callback still fired in plain mode and ran side effects —
statusReactions.setThinking() and windowReasoningOpen — before the compositor's
render gate, so a thinking status reaction leaked despite the config being off.
(The compositor render + 🧠 counter were already gated; the reaction was not.)

Make channels.discord.streaming.progress.thinking the single source of truth:
the draft-preview controller now exposes reasoningProgressEnabled (the resolved
flag it already used for the compositor reasoningGate), and the Discord handler
passes it as streamReasoningInNonStreamModes. In plain mode with the config off
the callback no longer fires at all; stream mode keeps main's behavior.

Adds resolver coverage asserting progress.thinking defaults off (unset, explicit
false, missing entry, and non-progress stream mode) and is true only when
explicitly enabled in progress mode.

Also the first time the Discord extension typecheck (against built dist) was run
for this change set; it passes.

Addresses the ClawSweeper [P1] 'gate the Discord reasoning opt-in before side
effects' finding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…c placeholder

extractAssistantThinking substituted a diagnostic string ("Native reasoning was
produced; no summary text was returned.") onto the reasoning lane whenever a
thinking block carried a signature but no summary text. That fires on GPT
constantly via the codex harness and about half the time via the openclaw
harness, and on Anthropic when display:"summarized" still returns empty —
surfacing a placeholder bubble on every channel. It is diagnostic, not reasoning
content; return "" so the downstream .filter(Boolean) drops the bubble. The
signed block stays on the message for API replay; this only governs display.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…alize

Anthropic has no native commentary channel; the only signal that leading text is
pre-tool narration is that a tool_use block follows it. The finalize loop tagged
it only once stop_reason:tool_use landed at message end — after the unphased text
already streamed. Tag it the moment the first tool_use content block opens, so
the commentary phase rides that partial and the subscriber can route it to the
narration lane far earlier. Finalize stays as an idempotent backstop for
replay/non-streaming assembly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…is known

On block-reply surfaces (onBlockReply + blockReplyBreak "text_end" — the default)
the subscriber durably sends a text block at its text_end. For a tool turn with
leading narration, that fired before the commentary tag was applied at the next
event (tool_use content_block_start), leaking pre-tool narration as a final reply
that the later tag could not retract. Progress mode was unaffected (no mid-stream
durable send), which is why it looked clean.

Hold each leading text block's text_end in the transport until the disposition is
known: flush it right after tagPendingCommentaryText at the tool_use boundary (so
it carries the commentary phase and the subscriber suppresses it from the durable
reply), or at message_delta / finalize for a non-tool turn (where the text is the
answer and is emitted unphased). Cleared on error/abort. Text deltas still stream
live, so the preview is unchanged; only the text_end (and thus the durable commit)
waits one event. Subscriber and all channels are untouched — they now simply see a
correctly-phased text_end, so every streaming-capable channel inherits the fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The transport now defers a pre-tool text block's text_end until it is tagged
commentary, but the earlier text_delta chunks were still appended to the
block-reply buffer (handleMessageUpdate). On block-reply channels
(blockReplyBreak "text_end" — Feishu/SMS/IRC and other append-style surfaces)
that buffer is drained mid-stream by the chunker and flushed before tool
execution (handleToolExecutionStart), so the narration could still post as a
durable answer that the later commentary tag cannot retract.

Gate the durable block-reply append for phase-pending Anthropic text: while an
anthropic-messages assistant block is unphased mid-stream (no deliveryPhase, no
streamItemId), do not append it to the block-reply buffer. A tool turn's
deferred commentary-tagged text_end then suppresses it; a non-tool answer is
delivered in full from handleMessageEnd (lastBlockReplyText stays null, so the
message_end safety-send fires). Unlike the OpenAI phase-pending guard this does
not early-return — the live preview keeps streaming; only the durable append is
withheld. Discord progress mode and other providers are unaffected.

Unit tests reproduce the leak (narration posted without the gate) and prove the
non-tool answer is still delivered in full.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… payloads

Mirror json's reasoningPayloadsEnabled seam (openclaw#97875) for the 💬 commentary
lane: add GetReplyOptions.commentaryPayloadsEnabled and an isCommentary payload
tag next to isReasoning across the payload types; default-suppress isCommentary
in dispatch-from-config (block stream + final loops) unless the channel opts in;
exclude commentary from TTS exactly like reasoning; thread the tag through the
block-reply coalescer, reply-delivery direct-lane, and realtime consult so it
cannot be dropped mid-pipeline. Discord opts in when its commentary lane is
active. No durable producer is wired yet (preserves the obviyus-approved 3-part
Anthropic withhold invariant; handlers.messages.ts unchanged).

Also regenerates docs/.generated/config-baseline.sha256 and bumps the plugin-SDK
surface budgets to actual post-openclaw#97875-rebase values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…unts only streamed items

Reasoning durable/window selection now follows the session /reasoning level
(off|on|stream) instead of the streaming.progress.thinking config flag:
on -> durable blockquote, stream -> live window lane, off -> nothing.
reasoningPayloadsEnabled is opted in only for 'on'; the window opt-in only
for 'stream'. Collapse-bar counters increment only when a line is actually
streamed to the window (post-yield), so durable 🧠/💬 lanes are never counted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Anthropic commentary has no native item id, so the transport re-emits the full
commentary text under an empty itemId on every later stream event. The verbose
standalone commentary lane dedups only on a defined matching itemId, so each
snapshot posted a duplicate persistent message (observed 8x). Skip re-emitting
an unchanged commentary snapshot per item key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…italic

The progress-draft compositor wraps reasoning and commentary window lines in
_..._ for italics. Telegram renders that as italics; Discord shows a literal
underscore on commentary. Add a commentaryItalics compositor flag (default on)
and opt Discord out: streamed thinking stays italic, commentary renders plain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…in live transport (opus-4-8 was sent no display → blank summary); mirror llm/providers/anthropic.ts

(cherry picked from commit 378a06da3a6061a867213148e7f0de7d216b0618)
…rs (pioneer)

Restores 363f544c4e (dropped in the min-rebase), re-applied onto the
tag-at-tool-boundary structure:
- stream-resolution: route anthropic-messages models with a non-"anthropic"
  provider onto the boundary-aware managed transport (the only place commentary
  gets tagged); the base SDK stream never tags it. Scoped to provider!=anthropic
  so direct-anthropic paths are unchanged.
- transport backstops: tag when the turn CONTAINS a toolCall, not only when
  stop_reason maps to toolUse — Bedrock/Vertex routes report end_turn on tool
  turns. No-op for direct Anthropic (already toolUse when a tool call is present).
- regression test for the stop_reason-mislabel case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aligns the two assertions with the commentaryItalics:false opt-out; reasoning
window lines stay italic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restores e0c838314b (dropped in the min-rebase), adapted to the count-on-stream
bar: when /reasoning on posts durable 🧠 messages, they land after the early
progress draft, so editing the draft in place strands the summary at the top
above the thoughts. Track persistentReasoningDelivered and, when set, clear the
early draft and post the summary fresh below the thoughts (thoughts → summary →
answer). Durable 🧠 stays uncounted in the bar (no progressReasoningSteps bump).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… non-work tools out of bar

[P2] reasoning-level default now resolves the agent's own reasoningDefault first
(mirrors core get-reply-directives + Telegram), then global, then off — an
agent-scoped default no longer runs reasoning on while Discord drops the durable
🧠 lane.

[P3] exclude non-work tools (message/react/typing) from the collapse-bar tool
count via the canonical isChannelProgressDraftWorkToolName predicate, so they no
longer inflate the bar. (The earlier render-result approach was wrong — the
compositor returns false for stored-but-deferred lines, which undercounts.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…surface

Reasoning display is gated by the session /reasoning level after the rework, so the
streaming.progress.thinking config key/resolver/docs were a no-op (ClawSweeper P2).
Remove the key, type, zod, generated metadata, resolver, tests, and docs mention;
regenerate the docs map. progress.commentary and session-level reasoning unchanged.

Re-pin the plugin-sdk public-surface budgets (10406->10401 / 5224->5220) to match
the source counts after removing the exported resolver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@fuller-stack-dev fuller-stack-dev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prior Discord summary leak is fixed on the current head, and the Anthropic commentary classification path is covered by focused tests. One shared-channel merge blocker remains.

LOC: +1903/-625 across 42 files (production +894/-315, tests/QA +989/-307, docs +20/-3).

Best-fix verdict: this is not landable as a Discord-scoped patch until the shared compositor behavior is either scoped to Discord or explicitly adopted and proved for the sibling channels that use it.

Verification on exact head bf0d31ef7de9a3c89b82765023e49a478dcae035 against upstream/main 1289abddcb966cfa1535c7b82a08f555d9848445:

  • 670 focused tests passed across the shared compositor, Discord, Anthropic transport, embedded runner, auto-reply dispatch, Telegram, and Mattermost.
  • git diff --check passed.
  • Exact-head GitHub checks are green.

params.active && resolveChannelStreamingPreviewToolProgress(params.entry);
const commentaryProgressEnabled =
params.active && resolveChannelStreamingProgressCommentary(params.entry);
const thinkingProgressEnabled = params.active && (params.reasoningGate ?? true);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Preserve the existing default for non-Discord callers

reasoningGate defaults to true, and this change also removes the previewToolProgressEnabled guard around storing reasoning. Telegram and Mattermost both construct this shared compositor without the new parameters, so streaming.progress.toolProgress: false plus reasoning streaming now renders reasoning text where main intentionally kept the draft label-only; resetting reasoningRawText on each stored tool line also changes their burst interleaving. The replacement shared test explicitly reverses main's "keeps reasoning details hidden when tool progress lines are hidden" assertion, while neither sibling has changed coverage. This contradicts the PR's Discord-scoped claim. Please preserve current-main defaults and opt Discord into the independent/interleaved reasoning lane, or expand the scope and add Telegram/Mattermost behavior tests, proof, and docs.

@obviyus

obviyus commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Landed via squash onto main.

  • Scoped tests: git diff --check; node scripts/run-vitest.mjs src/channels/progress-draft-compositor.test.ts extensions/discord/src/monitor/message-handler.process.test.ts extensions/mattermost/src/mattermost/monitor.test.ts extensions/telegram/src/bot-message-dispatch.test.ts -t 'window thinking|tool lines are hidden|streamed reasoning visible|reasoning|progress draft|composes reasoning|interleaves reasoning|queued turn|commentary'; node scripts/run-vitest.mjs src/agents/embedded-agent-subscribe.handlers.messages.test.ts src/agents/anthropic-transport-stream.test.ts src/agents/embedded-agent-subscribe.subscribe-embedded-agent-session.withholds-anthropic-pretool-narration.test.ts extensions/discord/src/monitor/message-handler.process.test.ts src/auto-reply/reply/agent-runner-execution.test.ts -t 'classified after deltas|defers a pre-tool|withholds pre-tool|commentary progress|thinking bursts|non-stream reasoning progress|accumulates reasoning deltas|does not emit CLI commentary|Inter-tool commentary'; .agents/skills/autoreview/scripts/autoreview --mode local
  • Changelog: release-owned, not edited
  • Land commit: 899c921
  • Merge commit: b3b51b0

Thanks @Marvinthebored!

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

Labels

agents Agent runtime and tooling channel: discord Channel integration: discord channel: telegram Channel integration: telegram docs Improvements or additions to documentation extensions: qa-lab 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. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P1 High-priority user-facing bug, regression, or broken workflow. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. scripts Repository scripts size: XL 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.

Anthropic models on Discord: reasoning (/reasoning on) and pre-tool narration (/verbose on) are produced but never surfaced in the channel

4 participants