From fe8fc56f95b962da19e4355dc64fa005cf6e6afa Mon Sep 17 00:00:00 2001 From: me2seeks Date: Fri, 21 Aug 2026 20:35:53 +0800 Subject: [PATCH] feat(cli): responsive priority-based status line for narrow terminals (#3421) The status line joined every segment left-to-right and hard-truncated the right side on overflow, cutting whichever segment happened to sit at the boundary mid-token and treating low-value static segments (connection, cwd) the same as safety- and budget-relevant ones. Segments now carry a drop rank; on overflow whole segments drop lowest-value-first (cache, cost, connection, thinking, orchestration), cwd degrades full -> basename -> dropped, and title/permission mode/goal/ctx never drop. Wide rendering is unchanged; the previous hard truncation remains only as the final fallback. --- .../cli/src/__tests__/pi-transcript.test.ts | 94 ++++++++++++++++++ .../cli/src/__tests__/pi-tui-runner.test.ts | 4 +- packages/cli/src/pi-transcript.ts | 99 ++++++++++++++----- 3 files changed, 173 insertions(+), 24 deletions(-) diff --git a/packages/cli/src/__tests__/pi-transcript.test.ts b/packages/cli/src/__tests__/pi-transcript.test.ts index f7537e0b70..3c8f86f4e7 100644 --- a/packages/cli/src/__tests__/pi-transcript.test.ts +++ b/packages/cli/src/__tests__/pi-transcript.test.ts @@ -118,6 +118,100 @@ describe('Maka Pi TUI transcript', () => { assert.doesNotMatch(stripAnsi(renderMakaPiStatusLine({ ...meta(), goal: null }, 120)), /goal/); }); + test('status line drops whole low-value segments on overflow, lowest rank first (#3421)', () => { + const richMeta = { + ...meta(), + modelContextWindow: 500_000, + usage: { + costUsd: 0.42, + cacheHitInput: 60, + cacheMissInput: 40, + contextRemaining: 480_000, + }, + }; + // Wide: everything renders. + const wide = stripAnsi(renderMakaPiStatusLine(richMeta, 120)); + assert.match(wide, /ctx 20k\/500k 4%/); + assert.match(wide, /\$0\.42/); + assert.match(wide, /cache 60%/); + assert.match(wide, /deepseek · \/tmp\/project/); + + // Below full width, cache drops before cost, and no segment is cut + // mid-token while any lower rank still survives. + const fullWidth = visibleWidth(wide); + const noCache = stripAnsi(renderMakaPiStatusLine(richMeta, fullWidth - 1)); + assert.doesNotMatch(noCache, /cache/); + assert.match(noCache, /\$0\.42/); + const noCost = stripAnsi( + renderMakaPiStatusLine(richMeta, fullWidth - 'cache 60% · '.length - 1), + ); + assert.doesNotMatch(noCost, /cache|\$0\.42/); + assert.match(noCost, /deepseek · \/tmp\/project/); + }); + + test('status line shortens cwd to its basename before dropping it (#3421)', () => { + const line = stripAnsi( + renderMakaPiStatusLine( + { + ...meta(), + cwd: '/very/long/nested/project-directory', + modelContextWindow: 500_000, + usage: { + costUsd: 0, + cacheHitInput: 1, + cacheMissInput: 1, + contextRemaining: 480_000, + }, + }, + // Room for title, mode, model, ctx and a short tail only. + 'Maka · Auto · deepseek-v4-flash · ctx 20k/500k 4% · project-directory'.length, + ), + ); + assert.doesNotMatch(line, /very\/long/); + assert.match(line, /project-directory/); + }); + + test('status line never drops mode, model, goal, or ctx at narrow widths (#3421)', () => { + const line = stripAnsi( + renderMakaPiStatusLine( + { + ...meta(), + permissionMode: 'bypass', + modelContextWindow: 500_000, + usage: { + costUsd: 9.99, + cacheHitInput: 1, + cacheMissInput: 1, + contextRemaining: 480_000, + }, + goal: { + goalId: 'goal-1', + revision: 1, + sessionId: 'session-1', + condition: 'Ship it', + setAt: Date.now() - 60_000, + iterations: 1, + maxIterations: 50, + consecutiveNoProgress: 0, + blockCap: 8, + tokenBudget: null, + tokensSpent: 0, + lastReason: null, + achievedAt: null, + pausedAt: null, + status: 'active' as const, + }, + }, + 75, + ), + ); + assert.match(line, /Full access/); + assert.match(line, /deepseek-v4-flash/); + assert.match(line, /goal 1\/50/); + assert.match(line, /ctx 20k\/500k 4%/); + assert.doesNotMatch(line, /\$9\.99|cache|deepseek ·|tmp\/project/); + }); + test('keeps assistant text after a tool call visible after the tool block', () => { const state = createMakaPiTranscriptState(); appendUserPrompt(state, 'inspect the package'); diff --git a/packages/cli/src/__tests__/pi-tui-runner.test.ts b/packages/cli/src/__tests__/pi-tui-runner.test.ts index 0414e7c6e9..30370bc17b 100644 --- a/packages/cli/src/__tests__/pi-tui-runner.test.ts +++ b/packages/cli/src/__tests__/pi-tui-runner.test.ts @@ -3182,7 +3182,9 @@ describe('Maka Pi TUI runner', () => { }); test('restores switched session state from stored messages', async () => { - const terminal = new FakeTerminal(); + // 120 cols: the status line fits every segment, so the usage segments this + // test asserts (ctx, cache) are not priority-dropped (#3421). + const terminal = new FakeTerminal(120); const driver = new SlashCommandDriver( [fakeSessionSummary('session-2', '/repo')], new Map([ diff --git a/packages/cli/src/pi-transcript.ts b/packages/cli/src/pi-transcript.ts index 10be3973c5..8b4cc504ed 100644 --- a/packages/cli/src/pi-transcript.ts +++ b/packages/cli/src/pi-transcript.ts @@ -1,4 +1,4 @@ -import { Markdown } from '@earendil-works/pi-tui'; +import { Markdown, visibleWidth } from '@earendil-works/pi-tui'; import type { ProviderRetryEvent, SandboxBoundaryRequestEvent, @@ -20,6 +20,7 @@ import { mergeShellRunStateWithDiagnostics } from '@maka/core/shell-run-result'; import { projectToolActivityArgs } from '@maka/core/tool-activity-args'; import { type ShellRunUpdate } from '@maka/core/events'; import { homedir } from 'node:os'; +import { basename } from 'node:path'; import { materializeSession, type ChatItem, @@ -1328,20 +1329,25 @@ export function permissionModeLabel(mode: string): string { export function renderMakaPiStatusLine(metadata: MakaPiTranscriptMetadata, width: number): string { const safeWidth = Math.max(1, width); const sep = ansi.dim(' · '); - const parts: string[] = [ - ansi.bold(metadata.title), - ansi.dim(permissionModeLabel(metadata.permissionMode)), - ansi.dim(metadata.model), + // #3421: segments carry a dropRank so overflow drops whole low-value + // segments instead of cutting the chain mid-token from the right. + // Lower ranks drop first; segments without a rank never drop: + // title, permission mode and goal are safety-relevant, ctx is the + // context budget, model is the session's identity. + const parts: MakaPiStatusLineSegment[] = [ + { text: ansi.bold(metadata.title) }, + { text: ansi.dim(permissionModeLabel(metadata.permissionMode)) }, + { text: ansi.dim(metadata.model) }, ]; // #1064: omit thinking:default — it is noise before the user explicitly // changes the level. Only a non-default, explicitly set level shows. if (metadata.thinkingLevel) { - parts.push(ansi.dim(`thinking:${metadata.thinkingLevel}`)); + parts.push({ text: ansi.dim(`thinking:${metadata.thinkingLevel}`), dropRank: 3 }); } if (metadata.orchestrationMode === 'swarm') { - parts.push(ansi.accent('swarm')); + parts.push({ text: ansi.accent('swarm'), dropRank: 4 }); } else if (metadata.orchestrationMode === 'graph') { - parts.push(ansi.accent('graph')); + parts.push({ text: ansi.accent('graph'), dropRank: 4 }); } // An autonomous goal burns tokens between prompts; it must never be // invisible. Terminal goals show nothing (the desktop chip hides them too). @@ -1350,13 +1356,14 @@ export function renderMakaPiStatusLine(metadata: MakaPiTranscriptMetadata, width // paused gets warning salience: the loop stopped burning but stays armed // and resumable, which the user must not miss. waiting is a normal // transient between turns, so it stays dim like the other chrome. - parts.push( - metadata.goal.status === 'active' - ? ansi.accent(text) - : metadata.goal.status === 'paused' - ? ansi.yellow(text) - : ansi.dim(text), - ); + parts.push({ + text: + metadata.goal.status === 'active' + ? ansi.accent(text) + : metadata.goal.status === 'paused' + ? ansi.yellow(text) + : ansi.dim(text), + }); } const usage = metadata.usage; if (usage) { @@ -1369,25 +1376,71 @@ export function renderMakaPiStatusLine(metadata: MakaPiTranscriptMetadata, width const pct = Math.round((used / metadata.modelContextWindow) * 100); // #1064: color warning — yellow >80%, red >95%, dim otherwise. const ctxColor = pct > 95 ? ansi.red : pct > 80 ? ansi.yellow : ansi.dim; - parts.push( - ctxColor( + parts.push({ + text: ctxColor( `ctx ${formatTokenCount(used)}/${formatTokenCount(metadata.modelContextWindow)} ${pct}%`, ), - ); + }); } if (usage.costUsd > 0) { - parts.push(ansi.dim(`$${formatCost(usage.costUsd)}`)); + parts.push({ text: ansi.dim(`$${formatCost(usage.costUsd)}`), dropRank: 1 }); } const totalCache = usage.cacheHitInput + usage.cacheMissInput; if (totalCache > 0) { const hitRate = Math.round((usage.cacheHitInput / totalCache) * 100); - parts.push(ansi.dim(`cache ${hitRate}%`)); + parts.push({ text: ansi.dim(`cache ${hitRate}%`), dropRank: 0 }); } } - parts.push(ansi.dim(metadata.connectionSlug)); + parts.push({ text: ansi.dim(metadata.connectionSlug), dropRank: 2 }); // #1064: shorten cwd to ~-relative path instead of the full path. - parts.push(ansi.dim(shortenCwd(metadata.cwd))); - return fitLine(parts.join(sep), safeWidth); + const cwd = shortenCwd(metadata.cwd); + // cwd degrades progressively (full → basename → dropped), after every + // ranked segment above but before the final truncation fallback. + parts.push({ + text: ansi.dim(cwd), + dropRank: 5, + shortenedText: cwd === '~' || cwd === '/' ? undefined : ansi.dim(basename(cwd)), + }); + return fitStatusLine(parts, sep, safeWidth); +} + +interface MakaPiStatusLineSegment { + text: string; + /** Overflow drops whole segments lowest-rank-first; undefined never drops. */ + dropRank?: number; + /** Progressive fallback tried before this segment is dropped entirely. */ + shortenedText?: string; +} + +function fitStatusLine(segments: MakaPiStatusLineSegment[], sep: string, width: number): string { + const lineWidth = (segs: MakaPiStatusLineSegment[]): number => + visibleWidth(segs.map((segment) => segment.text).join(sep)); + let kept = segments; + // Drop whole low-value segments, lowest rank first, re-checking after each + // rank so the fewest possible segments are sacrificed. + for (let rank = 0; lineWidth(kept) > width; rank++) { + const droppable = kept.some((segment) => segment.dropRank !== undefined); + if (!droppable) break; + const lowest = Math.min( + ...kept.flatMap((segment) => (segment.dropRank !== undefined ? [segment.dropRank] : [])), + ); + // A segment with a shortened form degrades to it before dropping. + const shorten = kept.find( + (segment) => segment.dropRank === lowest && segment.shortenedText !== undefined, + ); + if (shorten) { + kept = kept.map((segment) => + segment === shorten + ? { ...segment, text: segment.shortenedText ?? segment.text, shortenedText: undefined } + : segment, + ); + } else { + kept = kept.filter((segment) => segment.dropRank !== lowest); + } + } + // Last resort for still-oversized lines (e.g. a long model id alone): + // the previous hard truncation. + return fitLine(kept.map((segment) => segment.text).join(sep), width); } /**