Skip to content

fix: strip redundant thinking blocks from visible WS stream replies (#187) - #188

Open
singleGanghood wants to merge 1 commit into
sunnoy:mainfrom
singleGanghood:fix/leak-thinking-content-in-ws-stream
Open

fix: strip redundant thinking blocks from visible WS stream replies (#187)#188
singleGanghood wants to merge 1 commit into
sunnoy:mainfrom
singleGanghood:fix/leak-thinking-content-in-ws-stream

Conversation

@singleGanghood

Copy link
Copy Markdown

Summary

Fixes #187 — model reasoning/thinking content leaked to WeCom users in WS streaming replies, and visible text was duplicated.

Root cause

OpenClaw can deliver thinking content through two independent channels:

  1. A dedicated reasoning stream (onReasoningStreamstate.reasoningText)
  2. <think>...</think> blocks echoed into the visible text stream (deliverstate.accumulatedText)

When both channels carried thinking content (a common pattern with reasoning-capable models), buildWsStreamContent produced nested <think> tags like:

<think>reasoning stream content</think>
<think>visible-stream thinking block</think>
visible text

The WeCom client then surfaced the inner thinking block as visible text and duplicated the visible reply.

Changes

wecom/ws-monitor.js

  • Added resolveStreamVisibleText(accumulatedText, reasoningText):
    • When a reasoning stream is present, <think> blocks echoed into the visible text stream are stripped (they are redundant — the reasoning is already reported via the reasoning stream).
    • When the model writes thinking directly into the text stream (no reasoning stream), the blocks are preserved and normalized so the WeCom client can render the official collapsed "已完成思考" block (existing behavior, covered by ws.e2e.test.js).
  • Applied it in all forwarding paths: finishThinkingStream, sendReasoningUpdate, sendVisibleUpdate, rotateStream, and the passive markdown candidate.

wecom/callback-inbound.js

  • resolveCallbackFinalText now parses out thinking blocks (via parseThinkingContent) before normalizing the final reply text, so thinking content never reaches users through the callback path either.

Tests

  • New tests/ws-monitor.think-leak.test.js:
  • Updated tests/callback-inbound.test.js to assert thinking blocks are stripped from the final callback text.
  • Full suite: 321 passing (baseline 316 + 5 new tests). The only 2 failures are pre-existing macOS sandbox path issues (/private/var symlink) unrelated to this change.

Verification

  • npm test: 321 pass / 2 fail (2 pre-existing environment failures)
  • The official dynamic thinking stream e2e test (matches the official dynamic thinking stream behavior) still passes.
  • Confirmed the new leak regression test fails on the original code and passes with this fix.

When a model reports reasoning through the dedicated reasoning stream AND
also echoes <think>...</think> blocks into the visible text stream, the
plugin forwarded both, producing nested <think> tags that leaked thinking
content to WeCom users and duplicated visible text.

- add resolveStreamVisibleText(): strip visible-stream thinking blocks
  only when a reasoning stream is present, preserving the official
  dynamic thinking stream behavior when the model writes thinking
  directly into the text stream
- use it in finishThinkingStream, sendReasoningUpdate, sendVisibleUpdate,
  rotateStream and the passive markdown candidate
- callback-inbound: parse out thinking blocks before final text resolution

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Model reasoning (thinking) content leaks to users in WS streaming replies; visible text duplicated

1 participant