Merge PR #3776: Fix hidden thinking glyph-only status rows (@jeffscottward)

This commit is contained in:
can1357
2026-07-01 21:42:26 +02:00
3 changed files with 20 additions and 11 deletions
+2
View File
@@ -169,6 +169,8 @@
- Fixed the DuckDuckGo web_search provider returning empty results for non-encyclopedic queries by switching from the Instant Answer API to parsing the HTML frontend, and added clear error handling for bot-challenge throttling.
- Fixed Windows --extension paths with spaces or \\?\ prefixes being truncated or incorrectly passed to Bun import/spawn APIs.
- Fixed /mcp reauth compatibility with Cloudflare by aligning OAuth prompt behavior with the reference MCP SDK and updating the client label to oh-my-pi.
- 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
@@ -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 ("<total> · <rate> 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 {
@@ -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);
});
@@ -261,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: "<glyph> <total> · <rate> toks/s" — 57 provider tokens, 47.0 tok/s.
// Layout: "<glyph> Thinking · <total> · <rate> toks/s" — 57 provider tokens, 47.0 tok/s.
expect(plain).toContain("57 · 47.0 toks/s");
nowSpy.mockRestore();
@@ -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();