From 3854b0ec16ac22be12813f9d709760c84d036bc4 Mon Sep 17 00:00:00 2001 From: Jeff Scott Ward Date: Sun, 28 Jun 2026 21:15:14 -0400 Subject: [PATCH 1/2] fix: describe hidden thinking pulse --- .../src/modes/components/assistant-message.ts | 15 ++++++++------- .../components/assistant-message-error.test.ts | 12 +++++++++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/coding-agent/src/modes/components/assistant-message.ts b/packages/coding-agent/src/modes/components/assistant-message.ts index 5db8282f6..1db10fc2d 100644 --- a/packages/coding-agent/src/modes/components/assistant-message.ts +++ b/packages/coding-agent/src/modes/components/assistant-message.ts @@ -329,17 +329,18 @@ export class AssistantMessageComponent extends Container { #thinkingDotsLabel(): string { const glyph = THINKING_DOTS_FRAMES[this.#thinkingDotsFrame % THINKING_DOTS_FRAMES.length] ?? "…"; const coloredGlyph = theme.fg("thinkingText", glyph); + const thinkingLabel = theme.fg("muted", " Thinking"); const rate = Math.min(SPEED_MAX, sharedSpeedTracker.getSpeed()); // The numeric badge (" · toks/s") only renders while this block // is genuinely streaming provider tokens. A block that has observed no token // delta (e.g. a provider that reports usage only at turn end) or whose rate - // has decayed to zero (a streaming lull) drops it entirely — the bare pulse - // keeps signalling that the model is thinking. The liveness flag also stops - // the session-wide gauge from leaking a previous turn's rate onto a fresh - // token-less block. - if (!this.#thinkingRateLive || rate < 0.05) return coloredGlyph; + // has decayed to zero (a streaming lull) drops it entirely — the persistent + // text label keeps the pulse descriptive for terminals and screen readers. + // The liveness flag also stops the session-wide gauge from leaking a previous + // turn's rate onto a fresh token-less block. + if (!this.#thinkingRateLive || rate < 0.05) return coloredGlyph + thinkingLabel; // Total provider tokens, dimmed, sit next to the pulse. - const totalSpan = this.#thinkingTokens > 0 ? theme.fg("dim", ` ${formatNumber(this.#thinkingTokens)}`) : ""; + const totalSpan = this.#thinkingTokens > 0 ? theme.fg("dim", ` · ${formatNumber(this.#thinkingTokens)}`) : ""; // Speed badge color: dim gray at rest, brightening toward the theme accent as // streaming speed climbs (gray → bright accent). Ease (sqrt) so typical // mid-stream rates already read as clearly accent-tinted instead of staying @@ -348,7 +349,7 @@ export class AssistantMessageComponent extends Container { const hex = lerpHex(theme.getColorHex("dim"), theme.getAccentColorHex(), ratio); const rateText = ` · ${rate.toFixed(1)} toks/s`; const rateSpan = theme.getColorMode() === "truecolor" ? chalk.hex(hex)(rateText) : theme.fg("muted", rateText); - return coloredGlyph + totalSpan + rateSpan; + return coloredGlyph + thinkingLabel + totalSpan + rateSpan; } #startThinkingAnimation(): void { diff --git a/packages/coding-agent/test/modes/components/assistant-message-error.test.ts b/packages/coding-agent/test/modes/components/assistant-message-error.test.ts index 722f7470d..c9fef1bf1 100644 --- a/packages/coding-agent/test/modes/components/assistant-message-error.test.ts +++ b/packages/coding-agent/test/modes/components/assistant-message-error.test.ts @@ -178,10 +178,13 @@ describe("AssistantMessageComponent streaming thinking pulse", () => { // First frame of the expanding/shrinking ✻ pulse; deterministic right after updateContent. const PULSE = "✻"; + const THINKING_LABEL = "Thinking"; + const THINKING_GLYPH_ONLY_LINE = /^[✻✼❉❊✺✹✸✶]\s*$/; - it("shows the pulse in place of hidden reasoning while thinking streams", () => { + it("shows a described pulse in place of hidden reasoning while thinking streams", () => { const lines = liveLines(streaming([{ type: "thinking", thinking: "private reasoning" }])); - expect(lines.some(line => line.includes(PULSE))).toBe(true); + expect(lines.some(line => line.includes(PULSE) && line.includes(THINKING_LABEL))).toBe(true); + expect(lines.map(line => line.trim()).some(line => THINKING_GLYPH_ONLY_LINE.test(line))).toBe(false); expect(lines.some(line => line.includes("private reasoning"))).toBe(false); }); @@ -301,12 +304,14 @@ describe("AssistantMessageComponent streaming thinking pulse", () => { // Long pause: rate observations age out of the window. A same-token update // refreshes the live label, which now drops the numeric badge entirely - // rather than lingering on "0.0 toks/s" — only the bare pulse remains. + // rather than lingering on "0.0 toks/s" while retaining descriptive text. mockTime = 30_000; component.updateContent(streaming([{ type: "thinking", thinking: "ab" }], 57), { transient: true }); const plain = Bun.stripANSI(component.render(RENDER_WIDTH).join("\n")); expect(plain).not.toContain("toks/s"); + expect(plain).not.toContain("57"); expect(plain.includes(PULSE)).toBe(true); + expect(plain).toContain(THINKING_LABEL); nowSpy.mockRestore(); component.dispose(); @@ -335,6 +340,7 @@ describe("AssistantMessageComponent streaming thinking pulse", () => { expect(plain).not.toContain("toks/s"); expect(plain).not.toContain("99"); expect(plain.includes(PULSE)).toBe(true); + expect(plain).toContain(THINKING_LABEL); nowSpy.mockRestore(); b.dispose(); From 329fb91643c46887d6f0241e632749c1acb0bfce Mon Sep 17 00:00:00 2001 From: Jeff Scott Ward Date: Sun, 28 Jun 2026 23:07:14 -0400 Subject: [PATCH 2/2] fix: address hidden thinking review --- packages/coding-agent/CHANGELOG.md | 1 + .../test/modes/components/assistant-message-error.test.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 93e416344..e1bb7d668 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -14,6 +14,7 @@ - Fixed llama.cpp discovery mapping unlimited `max_tokens = -1` / `n_predict = -1` output limits to the generic 32K discovery cap instead of the discovered runtime context window. ([#3781](https://github.com/can1357/oh-my-pi/issues/3781)) - Fixed the bash interceptor blocking `echo` / `printf` redirects to `/dev/null`, `/dev/tty`, `/dev/stdout`, and `/dev/stderr` device sinks while still directing real file writes to the write tool. ([#3763](https://github.com/can1357/oh-my-pi/issues/3763)) - Fixed the `edit` tool persisting unbounded full-file `oldText` / `newText` snapshots in tool-result `details`, inflating per-turn session JSONL lines (hundreds of KB per edit on large files). `details.oldText`/`details.newText` are now pruned when their combined length exceeds 32 KB; the visible diff, path, line, and diagnostic metadata are preserved, and ACP `diff` content still flows for smaller edits. ([#3786](https://github.com/can1357/oh-my-pi/issues/3786)) +- Fixed hidden-thinking live status rows rendering as glyph-only lines by appending a persistent `Thinking` label next to the pulse. ## [16.2.5] - 2026-06-28 diff --git a/packages/coding-agent/test/modes/components/assistant-message-error.test.ts b/packages/coding-agent/test/modes/components/assistant-message-error.test.ts index c9fef1bf1..32820e59a 100644 --- a/packages/coding-agent/test/modes/components/assistant-message-error.test.ts +++ b/packages/coding-agent/test/modes/components/assistant-message-error.test.ts @@ -264,7 +264,7 @@ describe("AssistantMessageComponent streaming thinking pulse", () => { component.updateContent(streaming([{ type: "thinking", thinking: "ab" }], 57), { transient: true }); const plain = Bun.stripANSI(component.render(RENDER_WIDTH).join("\n")); - // Layout: " · toks/s" — 57 provider tokens, 47.0 tok/s. + // Layout: " Thinking · · toks/s" — 57 provider tokens, 47.0 tok/s. expect(plain).toContain("57 · 47.0 toks/s"); nowSpy.mockRestore();