From 377ed34e0876206e7b0ab0c64dd2c59a528800a4 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sat, 30 May 2026 06:38:09 +0200 Subject: [PATCH] =?UTF-8?q?refactor(coding-agent):=20replaced=20ctx/=CE=A3?= =?UTF-8?q?=20labels=20with=20icon=20and=20cleaner=20cost=20separator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed "ctx" suffix and cumulative Σ-token display from status lines. - Replaced "N tools" text with tool count + extensionTool icon. - Changed cost separator to ` . ` to visually distinguish it from dim stats. - Added test asserting new format and absence of old labels. --- .../components/session-observer-overlay.ts | 25 +++++++++------ packages/coding-agent/src/task/render.ts | 19 +++++------- .../test/task/render-nested-live.test.ts | 31 ++++++++++++++++--- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/packages/coding-agent/src/modes/components/session-observer-overlay.ts b/packages/coding-agent/src/modes/components/session-observer-overlay.ts index 27544a3e1..075c1679a 100644 --- a/packages/coding-agent/src/modes/components/session-observer-overlay.ts +++ b/packages/coding-agent/src/modes/components/session-observer-overlay.ts @@ -266,23 +266,28 @@ export class SessionObserverOverlayComponent extends Container { const progress = session?.progress; if (!progress) return ""; const stats: string[] = []; - if (progress.toolCount > 0) stats.push(`${formatNumber(progress.toolCount)} tools`); // Current per-turn context — what the user reads as "how full is the context". - // Falls back to cumulative billing volume (Σ-prefixed) when context size is unknown. + // Used as a compact progress gauge instead of cumulative billing volume. if (progress.contextTokens && progress.contextTokens > 0) { const ctx = progress.contextWindow && progress.contextWindow > 0 - ? `${formatNumber(progress.contextTokens)}/${formatNumber(progress.contextWindow)} ctx` - : `${formatNumber(progress.contextTokens)} ctx`; + ? `${formatNumber(progress.contextTokens)}/${formatNumber(progress.contextWindow)}` + : `${formatNumber(progress.contextTokens)}`; stats.push(ctx); - if (progress.tokens > 0) stats.push(`Σ${formatNumber(progress.tokens)}`); - } else if (progress.tokens > 0) { - stats.push(`Σ${formatNumber(progress.tokens)}`); } - if (progress.durationMs > 0) stats.push(formatDuration(progress.durationMs)); + if (progress.durationMs > 0) { + stats.push(formatDuration(progress.durationMs)); + } const parts: string[] = []; - if (stats.length > 0) parts.push(theme.fg("dim", stats.join(theme.sep.dot))); - if (progress.cost > 0) parts.push(theme.fg("statusLineCost", `$${progress.cost.toFixed(2)}`)); + if (stats.length > 0 || progress.toolCount > 0) { + const toolCountStat = + progress.toolCount > 0 ? `${formatNumber(progress.toolCount)} ${theme.icon.extensionTool}` : undefined; + const statSegments = [toolCountStat, ...stats].filter((segment): segment is string => Boolean(segment)); + parts.push(theme.fg("dim", statSegments.join(theme.sep.dot))); + } + if (progress.cost > 0) { + parts.push(`. ${theme.fg("statusLineCost", `$${progress.cost.toFixed(2)}`)}`); + } return parts.join(theme.sep.dot); } diff --git a/packages/coding-agent/src/task/render.ts b/packages/coding-agent/src/task/render.ts index 56e06f49f..3c2630039 100644 --- a/packages/coding-agent/src/task/render.ts +++ b/packages/coding-agent/src/task/render.ts @@ -51,7 +51,9 @@ function getStatusIcon(status: AgentProgress["status"], theme: Theme, spinnerFra } } -/** Append tool-count, context, cumulative-tokens, and cost stats to a status line string. */ +/** + * Append tool-count, context, and cost stats to a status line string. + */ function appendAgentStats( line: string, opts: { @@ -66,25 +68,18 @@ function appendAgentStats( theme: Theme, ): string { if (opts.toolCount) { - line += `${theme.sep.dot}${theme.fg("dim", `${opts.toolCount} tools`)}`; + line += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(opts.toolCount)} ${theme.icon.extensionTool}`)}`; } // Current per-turn context — what the user reads as "how full is the context". - // Cumulative tokens (billing volume) renders separately with a Σ sigil to avoid - // being mistaken for current window pressure. if (opts.contextTokens && opts.contextTokens > 0) { const ctx = opts.contextWindow && opts.contextWindow > 0 - ? `${formatNumber(opts.contextTokens)}/${formatNumber(opts.contextWindow)} ctx` - : `${formatNumber(opts.contextTokens)} ctx`; + ? `${formatNumber(opts.contextTokens)}/${formatNumber(opts.contextWindow)}` + : `${formatNumber(opts.contextTokens)}`; line += `${theme.sep.dot}${theme.fg("dim", ctx)}`; - if (opts.tokens > 0) { - line += `${theme.sep.dot}${theme.fg("dim", `Σ${formatNumber(opts.tokens)}`)}`; - } - } else if (opts.tokens > 0) { - line += `${theme.sep.dot}${theme.fg("dim", `Σ${formatNumber(opts.tokens)}`)}`; } if (opts.cost > 0) { - line += `${theme.sep.dot}${theme.fg("statusLineCost", `$${opts.cost.toFixed(2)}`)}`; + line += ` . ${theme.fg("statusLineCost", `$${opts.cost.toFixed(2)}`)}`; } if (opts.resolvedModel && opts.showResolvedModelBadge) { line += `${theme.sep.dot}${theme.fg("dim", truncateToWidth(replaceTabs(opts.resolvedModel), 30))}`; diff --git a/packages/coding-agent/test/task/render-nested-live.test.ts b/packages/coding-agent/test/task/render-nested-live.test.ts index 3b9a69520..df7d85c6a 100644 --- a/packages/coding-agent/test/task/render-nested-live.test.ts +++ b/packages/coding-agent/test/task/render-nested-live.test.ts @@ -3,12 +3,8 @@ import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config import { getThemeByName, setThemeInstance } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import type { AgentProgress, SingleResult, TaskToolDetails } from "@oh-my-pi/pi-coding-agent/task"; import { taskToolRenderer } from "@oh-my-pi/pi-coding-agent/task/render"; +import { formatNumber } from "@oh-my-pi/pi-utils"; -// Defends the live-rendering contract for the `task` tool: while a Level-1 -// subagent is still mid-flight, any nested `task` activity it has produced -// (already-completed sub-calls in `extractedToolData.task`, plus the in-flight -// snapshot in `inflightTaskDetails`) MUST surface in the parent's streaming -// output — same way it surfaces in the finished result. describe("task renderer: nested live rendering", () => { beforeAll(async () => { resetSettingsForTest(); @@ -22,6 +18,12 @@ describe("task renderer: nested live rendering", () => { resetSettingsForTest(); }); + // Defends the live-rendering contract for the `task` tool: while a Level-1 + // subagent is still mid-flight, any nested `task` activity it has produced + // (already-completed sub-calls in `extractedToolData.task`, plus the in-flight + // snapshot in `inflightTaskDetails`) MUST surface in the parent's streaming + // output — same way it surfaces in the finished result. + function makeRunningProgress(overrides: Partial): AgentProgress { return { index: 0, @@ -181,4 +183,23 @@ describe("task renderer: nested live rendering", () => { // Completed entries are emitted before the in-flight snapshot. expect(epsilonIdx).toBeLessThan(zetaIdx); }); + + it("formats running progress stats with tool icon, context window, and cost", async () => { + const theme = (await getThemeByName("dark"))!; + const text = await render( + makeRunningProgress({ + toolCount: 19, + contextTokens: 58_000, + contextWindow: 272_000, + cost: 2.1, + durationMs: 0, + }), + ); + + const expectedStats = `${formatNumber(19)} ${theme.icon.extensionTool} · ${formatNumber(58_000)}/${formatNumber(272_000)} . $2.10`; + expect(text).toContain(expectedStats); + expect(text).not.toContain("tools"); + expect(text).not.toContain("ctx"); + expect(text).not.toContain("Σ"); + }); });