From 4d0e74ab6c875ae4c93f4119ff6196e02b6075b2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 10 Jan 2026 23:23:23 +0100 Subject: [PATCH] fix: cover extra thinking tags (#688) (thanks @theglove44) --- CHANGELOG.md | 1 + src/agents/clawdbot-gateway-tool.test.ts | 4 +-- src/agents/pi-embedded-subscribe.test.ts | 35 +++++++++++++++++------- src/agents/pi-embedded-subscribe.ts | 3 +- src/cli/update-cli.ts | 4 +-- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2025c694d..57c8ebff5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Docker: allow optional home volume + extra bind mounts in `docker-setup.sh`. (#679) — thanks @gabriel-trigo. ### Fixes +- Agents: strip ``/`` tags from hidden reasoning output and cover tag variants in tests. (#688) — thanks @theglove44. - Agents: recognize "usage limit" errors as rate limits for failover. (#687) — thanks @evalexpr. - CLI: avoid success message when daemon restart is skipped. (#685) — thanks @carlulsoe. - Gateway: disable the OpenAI-compatible `/v1/chat/completions` endpoint by default; enable via `gateway.http.endpoints.chatCompletions.enabled=true`. diff --git a/src/agents/clawdbot-gateway-tool.test.ts b/src/agents/clawdbot-gateway-tool.test.ts index b69811c09..ea2ba8f1c 100644 --- a/src/agents/clawdbot-gateway-tool.test.ts +++ b/src/agents/clawdbot-gateway-tool.test.ts @@ -14,9 +14,7 @@ describe("gateway tool", () => { vi.useFakeTimers(); const kill = vi.spyOn(process, "kill").mockImplementation(() => true); const previousStateDir = process.env.CLAWDBOT_STATE_DIR; - const stateDir = await fs.mkdtemp( - path.join(os.tmpdir(), "clawdbot-test-"), - ); + const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-test-")); process.env.CLAWDBOT_STATE_DIR = stateDir; try { diff --git a/src/agents/pi-embedded-subscribe.test.ts b/src/agents/pi-embedded-subscribe.test.ts index 1502c5822..120b25089 100644 --- a/src/agents/pi-embedded-subscribe.test.ts +++ b/src/agents/pi-embedded-subscribe.test.ts @@ -10,6 +10,12 @@ type StubSession = { type SessionEventHandler = (evt: unknown) => void; describe("subscribeEmbeddedPiSession", () => { + const THINKING_TAG_CASES = [ + { tag: "think", open: "", close: "" }, + { tag: "thinking", open: "", close: "" }, + { tag: "thought", open: "", close: "" }, + { tag: "antthinking", open: "", close: "" }, + ] as const; it("filters to and falls back when tags are malformed", () => { let handler: ((evt: unknown) => void) | undefined; const session: StubSession = { @@ -167,7 +173,12 @@ describe("subscribeEmbeddedPiSession", () => { expect(onBlockReply.mock.calls[1][0].text).toBe("Final answer"); }); - it("promotes tags to thinking blocks at write-time", () => { + it.each( + THINKING_TAG_CASES, + )("promotes <%s> tags to thinking blocks at write-time", ({ + open, + close, + }) => { let handler: ((evt: unknown) => void) | undefined; const session: StubSession = { subscribe: (fn) => { @@ -193,7 +204,7 @@ describe("subscribeEmbeddedPiSession", () => { content: [ { type: "text", - text: "\nBecause it helps\n\n\nFinal answer", + text: `${open}\nBecause it helps\n${close}\n\nFinal answer`, }, ], } as AssistantMessage; @@ -212,7 +223,12 @@ describe("subscribeEmbeddedPiSession", () => { ]); }); - it("streams reasoning via onReasoningStream without leaking into final text", () => { + it.each( + THINKING_TAG_CASES, + )("streams <%s> reasoning via onReasoningStream without leaking into final text", ({ + open, + close, + }) => { let handler: ((evt: unknown) => void) | undefined; const session: StubSession = { subscribe: (fn) => { @@ -240,7 +256,7 @@ describe("subscribeEmbeddedPiSession", () => { message: { role: "assistant" }, assistantMessageEvent: { type: "text_delta", - delta: "\nBecause", + delta: `${open}\nBecause`, }, }); @@ -249,7 +265,7 @@ describe("subscribeEmbeddedPiSession", () => { message: { role: "assistant" }, assistantMessageEvent: { type: "text_delta", - delta: " it helps\n\n\nFinal answer", + delta: ` it helps\n${close}\n\nFinal answer`, }, }); @@ -258,7 +274,7 @@ describe("subscribeEmbeddedPiSession", () => { content: [ { type: "text", - text: "\nBecause it helps\n\n\nFinal answer", + text: `${open}\nBecause it helps\n${close}\n\nFinal answer`, }, ], } as AssistantMessage; @@ -279,10 +295,9 @@ describe("subscribeEmbeddedPiSession", () => { ]); }); - it.each([ - { tag: "think", open: "", close: "" }, - { tag: "thinking", open: "", close: "" }, - ])("suppresses <%s> blocks across chunk boundaries", ({ open, close }) => { + it.each( + THINKING_TAG_CASES, + )("suppresses <%s> blocks across chunk boundaries", ({ open, close }) => { let handler: ((evt: unknown) => void) | undefined; const session: StubSession = { subscribe: (fn) => { diff --git a/src/agents/pi-embedded-subscribe.ts b/src/agents/pi-embedded-subscribe.ts index a48a2de71..bc2208c96 100644 --- a/src/agents/pi-embedded-subscribe.ts +++ b/src/agents/pi-embedded-subscribe.ts @@ -35,7 +35,8 @@ import { const THINKING_TAG_RE = /<\s*\/?\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi; const THINKING_OPEN_RE = /<\s*(?:think(?:ing)?|thought|antthinking)\s*>/i; const THINKING_CLOSE_RE = /<\s*\/\s*(?:think(?:ing)?|thought|antthinking)\s*>/i; -const THINKING_TAG_SCAN_RE = /<\s*(\/?)\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi; +const THINKING_TAG_SCAN_RE = + /<\s*(\/?)\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi; const TOOL_RESULT_MAX_CHARS = 8000; const log = createSubsystemLogger("agent/embedded"); const RAW_STREAM_ENABLED = process.env.CLAWDBOT_RAW_STREAM === "1"; diff --git a/src/cli/update-cli.ts b/src/cli/update-cli.ts index 52a85a6fd..5e97b39ef 100644 --- a/src/cli/update-cli.ts +++ b/src/cli/update-cli.ts @@ -165,9 +165,7 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { try { await doctorCommand(defaultRuntime, { nonInteractive: true }); } catch (err) { - defaultRuntime.log( - theme.warn(`Doctor failed: ${String(err)}`), - ); + defaultRuntime.log(theme.warn(`Doctor failed: ${String(err)}`)); } finally { delete process.env.CLAWDBOT_UPDATE_IN_PROGRESS; }