@aragon-agent/cli 0.6.12 → 0.6.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +108 -31
- package/dist/agent/controller.d.ts +38 -2
- package/dist/agent/controller.js +185 -34
- package/dist/agent/model-profile-settings.d.ts +31 -0
- package/dist/agent/model-profile-settings.js +208 -0
- package/dist/agent/queued-messages.d.ts +12 -0
- package/dist/agent/queued-messages.js +18 -0
- package/dist/agent/reducer.d.ts +13 -1
- package/dist/agent/reducer.js +52 -12
- package/dist/boot/guard.d.ts +3 -3
- package/dist/boot/guard.js +3 -3
- package/dist/cli.js +48 -16
- package/dist/commands/builtins.js +66 -39
- package/dist/commands/queue-command.d.ts +3 -0
- package/dist/commands/queue-command.js +5 -0
- package/dist/commands/registry.d.ts +3 -0
- package/dist/compaction/child.d.ts +4 -2
- package/dist/compaction/child.js +8 -1
- package/dist/compaction/compactor.d.ts +7 -2
- package/dist/compaction/compactor.js +21 -16
- package/dist/compaction/context-command.js +10 -3
- package/dist/compaction/meter.js +2 -0
- package/dist/compaction/pressure.d.ts +1 -0
- package/dist/compaction/pressure.js +1 -0
- package/dist/compaction/types.d.ts +2 -0
- package/dist/compaction/wiring.d.ts +4 -3
- package/dist/compaction/wiring.js +10 -2
- package/dist/config/cli-commands.d.ts +2 -0
- package/dist/config/cli-commands.js +16 -4
- package/dist/config/load.d.ts +5 -2
- package/dist/config/load.js +23 -12
- package/dist/config/model-profile-resolution.d.ts +20 -0
- package/dist/config/model-profile-resolution.js +81 -0
- package/dist/config/model-profile-store.d.ts +67 -0
- package/dist/config/model-profile-store.js +153 -0
- package/dist/config/model-profiles.d.ts +44 -0
- package/dist/config/model-profiles.js +121 -0
- package/dist/config/schema.d.ts +11 -2
- package/dist/config/store.d.ts +11 -1
- package/dist/config/store.js +43 -27
- package/dist/fast/resolve.d.ts +2 -1
- package/dist/fast/resolve.js +6 -4
- package/dist/fast/wiring.d.ts +5 -3
- package/dist/fast/wiring.js +8 -4
- package/dist/input/stdin-filter.js +3 -2
- package/dist/logging/install.js +4 -2
- package/dist/logging/secret-registry.d.ts +2 -0
- package/dist/logging/secret-registry.js +18 -0
- package/dist/team/runtime.d.ts +2 -1
- package/dist/team/subagent.d.ts +3 -1
- package/dist/team/subagent.js +3 -1
- package/dist/ui/ActivityLine.d.ts +6 -70
- package/dist/ui/ActivityLine.js +12 -34
- package/dist/ui/App.d.ts +1 -0
- package/dist/ui/App.js +116 -215
- package/dist/ui/BottomStatusRow.d.ts +6 -86
- package/dist/ui/BottomStatusRow.js +11 -26
- package/dist/ui/Composer.d.ts +16 -83
- package/dist/ui/Composer.js +9 -100
- package/dist/ui/Logo.d.ts +3 -5
- package/dist/ui/Logo.js +4 -8
- package/dist/ui/PromptInput.d.ts +4 -1
- package/dist/ui/PromptInput.js +44 -35
- package/dist/ui/QueuePanel.d.ts +8 -0
- package/dist/ui/QueuePanel.js +9 -0
- package/dist/ui/QueueStatusRow.d.ts +2 -0
- package/dist/ui/QueueStatusRow.js +1 -1
- package/dist/ui/StatusBar.d.ts +13 -149
- package/dist/ui/StatusBar.js +27 -142
- package/dist/ui/capabilities.d.ts +2 -0
- package/dist/ui/capabilities.js +6 -0
- package/dist/ui/clipboard-task.d.ts +29 -0
- package/dist/ui/clipboard-task.js +66 -0
- package/dist/ui/clipboard.d.ts +29 -50
- package/dist/ui/clipboard.js +141 -75
- package/dist/ui/color-runtime.d.ts +5 -0
- package/dist/ui/color-runtime.js +29 -0
- package/dist/ui/composer-limits.d.ts +3 -4
- package/dist/ui/composer-limits.js +4 -9
- package/dist/ui/composer-rows.d.ts +1 -1
- package/dist/ui/composer-rows.js +24 -137
- package/dist/ui/editor-navigation.d.ts +45 -0
- package/dist/ui/editor-navigation.js +85 -0
- package/dist/ui/editor-reducer.d.ts +4 -0
- package/dist/ui/editor-reducer.js +29 -15
- package/dist/ui/glyphs.js +10 -8
- package/dist/ui/ink-runtime.d.ts +3 -0
- package/dist/ui/ink-runtime.js +30 -0
- package/dist/ui/interaction-copy.d.ts +77 -0
- package/dist/ui/interaction-copy.js +40 -0
- package/dist/ui/interaction-hints.d.ts +18 -0
- package/dist/ui/interaction-hints.js +92 -0
- package/dist/ui/layout/budget.js +1 -1
- package/dist/ui/layout/queue-layout.d.ts +19 -0
- package/dist/ui/layout/queue-layout.js +26 -0
- package/dist/ui/layout/status-layout.d.ts +55 -0
- package/dist/ui/layout/status-layout.js +110 -0
- package/dist/ui/layout/todo-layout.d.ts +1 -0
- package/dist/ui/layout/todo-layout.js +1 -1
- package/dist/ui/overlays/HelpOverlay.js +8 -8
- package/dist/ui/overlays/ModelProfileEditor.d.ts +12 -0
- package/dist/ui/overlays/ModelProfileEditor.js +103 -0
- package/dist/ui/overlays/ModelProfilePicker.d.ts +35 -0
- package/dist/ui/overlays/ModelProfilePicker.js +167 -0
- package/dist/ui/overlays/QueueOverlay.d.ts +22 -0
- package/dist/ui/overlays/QueueOverlay.js +79 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +9 -2
- package/dist/ui/overlays/SettingsScreen.js +347 -3
- package/dist/ui/overlays/model-profile-state.d.ts +74 -0
- package/dist/ui/overlays/model-profile-state.js +119 -0
- package/dist/ui/render-cache.d.ts +2 -2
- package/dist/ui/render-cache.js +4 -3
- package/dist/ui/run-status-row.d.ts +7 -19
- package/dist/ui/run-status-row.js +2 -18
- package/dist/ui/selection/selection-controller.d.ts +2 -2
- package/dist/ui/selection/selection-controller.js +1 -0
- package/dist/ui/use-wheel-routing.js +2 -1
- package/package.json +2 -2
package/dist/ui/PromptInput.js
CHANGED
|
@@ -34,6 +34,8 @@ import { planComposerInput } from './composer-input.js';
|
|
|
34
34
|
import { allocatePasteId, expandPastes, expandRangeOverTokens } from './paste-tokens.js';
|
|
35
35
|
import { draftMaxRows } from './composer-limits.js';
|
|
36
36
|
import { layoutComposer, splitRowAtColumn } from './composer-rows.js';
|
|
37
|
+
import { moveVisualCursor, snapGrapheme, stepGrapheme } from './editor-navigation.js';
|
|
38
|
+
import { interactionCopy } from './interaction-copy.js';
|
|
37
39
|
import { PASTE_DRAFT_MAX_BYTES, PASTE_MAX_BLOCKS, formatPasteSize, } from '../input/limits.js';
|
|
38
40
|
// ---------------------------------------------------------------------------
|
|
39
41
|
// The scroll chip (§4.2)
|
|
@@ -133,6 +135,8 @@ export function draftLimitRefusal(segments, editor) {
|
|
|
133
135
|
// ---------------------------------------------------------------------------
|
|
134
136
|
const isSpace = (ch) => !!ch && /\s/.test(ch);
|
|
135
137
|
function lineStart(buffer, cursor) {
|
|
138
|
+
if (cursor <= 0)
|
|
139
|
+
return 0;
|
|
136
140
|
const nl = buffer.lastIndexOf('\n', cursor - 1);
|
|
137
141
|
return nl === -1 ? 0 : nl + 1;
|
|
138
142
|
}
|
|
@@ -166,23 +170,23 @@ function nextWord(buffer, cursor) {
|
|
|
166
170
|
* label the user then sends INSTEAD of the text it stands for.
|
|
167
171
|
*/
|
|
168
172
|
function killRange(buffer, from, to) {
|
|
169
|
-
const range = expandRangeOverTokens(buffer, from, to);
|
|
173
|
+
const range = expandRangeOverTokens(buffer, snapGrapheme(buffer, from), snapGrapheme(buffer, to, true));
|
|
170
174
|
return { buffer: buffer.slice(0, range.from) + buffer.slice(range.to), cursor: range.from };
|
|
171
175
|
}
|
|
172
176
|
/** Apply an edit intent as a pure buffer/cursor transform. */
|
|
173
177
|
export function applyEdit(buffer, cursor, intent) {
|
|
178
|
+
cursor = snapGrapheme(buffer, cursor);
|
|
174
179
|
switch (intent) {
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
// a synthetic unit buys a correctness the user cannot notice.
|
|
180
|
+
// Navigation may enter a token label; insertion and range deletion retain
|
|
181
|
+
// the existing token snapping and expansion contracts.
|
|
178
182
|
case 'home':
|
|
179
183
|
return { buffer, cursor: lineStart(buffer, cursor) };
|
|
180
184
|
case 'end':
|
|
181
185
|
return { buffer, cursor: lineEnd(buffer, cursor) };
|
|
182
186
|
case 'wordLeft':
|
|
183
|
-
return { buffer, cursor: prevWord(buffer, cursor) };
|
|
187
|
+
return { buffer, cursor: snapGrapheme(buffer, prevWord(buffer, cursor)) };
|
|
184
188
|
case 'wordRight':
|
|
185
|
-
return { buffer, cursor: nextWord(buffer, cursor) };
|
|
189
|
+
return { buffer, cursor: snapGrapheme(buffer, nextWord(buffer, cursor), true) };
|
|
186
190
|
case 'deleteWordBack':
|
|
187
191
|
return killRange(buffer, prevWord(buffer, cursor), cursor);
|
|
188
192
|
case 'killToStart':
|
|
@@ -197,19 +201,8 @@ export function applyEdit(buffer, cursor, intent) {
|
|
|
197
201
|
* prompt-history recall.
|
|
198
202
|
*/
|
|
199
203
|
export function moveVertical(buffer, cursor, dir) {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
return null;
|
|
203
|
-
const before = buffer.slice(0, cursor);
|
|
204
|
-
const idx = before.split('\n').length - 1;
|
|
205
|
-
const col = before.length - (before.lastIndexOf('\n') + 1);
|
|
206
|
-
const target = dir === 'up' ? idx - 1 : idx + 1;
|
|
207
|
-
if (target < 0 || target >= lines.length)
|
|
208
|
-
return null;
|
|
209
|
-
let start = 0;
|
|
210
|
-
for (let i = 0; i < target; i += 1)
|
|
211
|
-
start += lines[i].length + 1;
|
|
212
|
-
return { cursor: start + Math.min(col, lines[target].length) };
|
|
204
|
+
const moved = moveVisualCursor({ buffer, cursor, direction: dir, cols: Number.MAX_SAFE_INTEGER });
|
|
205
|
+
return moved ? { cursor: moved.cursor } : null;
|
|
213
206
|
}
|
|
214
207
|
/**
|
|
215
208
|
* Slash-command suggestions when the buffer is a bare `/word` (no space yet).
|
|
@@ -250,7 +243,7 @@ function isControlSeq(input) {
|
|
|
250
243
|
// ---------------------------------------------------------------------------
|
|
251
244
|
// Component
|
|
252
245
|
// ---------------------------------------------------------------------------
|
|
253
|
-
export function PromptInput({ cols: colsProp, cursorVisible = true, onInteraction, isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, agentMode, onSubmit, onHelp, onEscape, onEscapeDismiss, reducedMotion = false, borderColor, onDraftChange, onNotice, scrolledLines = 0, }) {
|
|
246
|
+
export function PromptInput({ cols: colsProp, cursorVisible = true, onInteraction, isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, onCompletionContextChange, deleteDisambiguated = false, agentMode, onSubmit, onHelp, onEscape, onEscapeDismiss, reducedMotion = false, borderColor, onDraftChange, onNotice, scrolledLines = 0, }) {
|
|
254
247
|
const { stdout } = useStdout();
|
|
255
248
|
const [editor, dispatch] = useReducer(editorReducer, INITIAL_EDITOR_STATE);
|
|
256
249
|
const { buffer, cursor, historyIndex, dismissed, sel } = editor;
|
|
@@ -280,6 +273,17 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
280
273
|
onPopupRowsChange?.(popupLayout.rowCount);
|
|
281
274
|
}, [onPopupRowsChange, popupLayout.rowCount]);
|
|
282
275
|
useLayoutEffect(() => () => onPopupRowsChange?.(0), [onPopupRowsChange]);
|
|
276
|
+
const completion = popupVisible ? popupKind ?? 'none' : 'none';
|
|
277
|
+
const completionCallback = useRef(onCompletionContextChange);
|
|
278
|
+
completionCallback.current = onCompletionContextChange;
|
|
279
|
+
const lastCompletion = useRef();
|
|
280
|
+
useLayoutEffect(() => {
|
|
281
|
+
if (!onCompletionContextChange || lastCompletion.current === completion)
|
|
282
|
+
return;
|
|
283
|
+
lastCompletion.current = completion;
|
|
284
|
+
onCompletionContextChange?.(completion);
|
|
285
|
+
}, [onCompletionContextChange, completion]);
|
|
286
|
+
useLayoutEffect(() => () => completionCallback.current?.('none'), []);
|
|
283
287
|
// --- Debounced `@file` scan. ---------------------------------------------
|
|
284
288
|
useEffect(() => {
|
|
285
289
|
if (fileQuery === null || fileQuery.length < 1) {
|
|
@@ -330,7 +334,9 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
330
334
|
cursor,
|
|
331
335
|
cols: wrapCols,
|
|
332
336
|
maxRows: draftMaxRows(terminalRows),
|
|
333
|
-
|
|
337
|
+
// Scrolling may hide the caret, but must not change the measured draft.
|
|
338
|
+
// Otherwise visibility can toggle wrapping and feed back into scrolling.
|
|
339
|
+
active: isActive,
|
|
334
340
|
});
|
|
335
341
|
// TWO PASSES, AND THE SECOND ONE IS LOAD-BEARING (I-8). The overflow indicator
|
|
336
342
|
// rides the last rendered row INSIDE the box the draft wraps in, so its cells
|
|
@@ -347,6 +353,8 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
347
353
|
const overflows = wide.hiddenAbove > 0 || wide.hiddenBelow > 0;
|
|
348
354
|
const usableCols = overflows ? Math.max(8, baseCols - OVERFLOW_CELLS - 1) : baseCols;
|
|
349
355
|
const layout = overflows ? measure(usableCols) : wide;
|
|
356
|
+
const preferredColumnWidth = useRef();
|
|
357
|
+
useLayoutEffect(() => { preferredColumnWidth.current = undefined; }, [usableCols]);
|
|
350
358
|
const draftRowCount = Math.max(1, layout.rows.length);
|
|
351
359
|
const caretResetKey = useMemo(() => ({}), [buffer, cursor, isActive, usableCols]);
|
|
352
360
|
// --- Draft presence and height, reported only on a transition. -----------
|
|
@@ -446,9 +454,12 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
446
454
|
};
|
|
447
455
|
const verticalOrHistory = (dir) => {
|
|
448
456
|
onInteraction?.();
|
|
449
|
-
const moved =
|
|
457
|
+
const moved = moveVisualCursor({ buffer, cursor, cols: usableCols, direction: dir,
|
|
458
|
+
preferredVisualColumn: preferredColumnWidth.current === usableCols
|
|
459
|
+
? editor.preferredVisualColumn : undefined });
|
|
450
460
|
if (moved) {
|
|
451
|
-
|
|
461
|
+
preferredColumnWidth.current = usableCols;
|
|
462
|
+
dispatch({ type: 'moveCursor', ...moved });
|
|
452
463
|
return;
|
|
453
464
|
}
|
|
454
465
|
// At an edge: recall history only from an empty buffer or while already
|
|
@@ -514,11 +525,15 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
514
525
|
return;
|
|
515
526
|
}
|
|
516
527
|
// Line-editing intents (Home/End/word/kill) recognized from raw keys.
|
|
517
|
-
const intent =
|
|
528
|
+
const intent = key.meta && (key.backspace || key.delete)
|
|
529
|
+
? 'deleteWordBack' : recognize(input, key);
|
|
518
530
|
if (intent) {
|
|
519
531
|
onInteraction?.();
|
|
520
532
|
const next = applyEdit(buffer, cursor, intent);
|
|
521
|
-
|
|
533
|
+
const moving = intent === 'home' || intent === 'end'
|
|
534
|
+
|| intent === 'wordLeft' || intent === 'wordRight';
|
|
535
|
+
dispatch(moving ? { type: 'moveCursor', cursor: next.cursor }
|
|
536
|
+
: { type: 'replace', buffer: next.buffer, cursor: next.cursor });
|
|
522
537
|
return;
|
|
523
538
|
}
|
|
524
539
|
if (key.ctrl)
|
|
@@ -539,19 +554,17 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
539
554
|
// unconditional dispatch and a no-op at the left edge returns the same
|
|
540
555
|
// state object — which React bails out on, exactly as the old
|
|
541
556
|
// `if (cursor > 0)` skipped the `setState`s.
|
|
542
|
-
dispatch({ type: 'backspace' });
|
|
557
|
+
dispatch({ type: key.delete && deleteDisambiguated ? 'delete' : 'backspace' });
|
|
543
558
|
return;
|
|
544
559
|
}
|
|
545
560
|
if (key.leftArrow) {
|
|
546
561
|
onInteraction?.();
|
|
547
|
-
|
|
548
|
-
dispatch({ type: 'moveCursor', cursor: Math.max(0, cursor - step) });
|
|
562
|
+
dispatch({ type: 'moveCursor', cursor: stepGrapheme(buffer, cursor, 'left') });
|
|
549
563
|
return;
|
|
550
564
|
}
|
|
551
565
|
if (key.rightArrow) {
|
|
552
566
|
onInteraction?.();
|
|
553
|
-
|
|
554
|
-
dispatch({ type: 'moveCursor', cursor: Math.min(buffer.length, cursor + step) });
|
|
567
|
+
dispatch({ type: 'moveCursor', cursor: stepGrapheme(buffer, cursor, 'right') });
|
|
555
568
|
return;
|
|
556
569
|
}
|
|
557
570
|
// `!key.shift` is load-bearing: Shift+↑/↓ is the viewport's line-scroll
|
|
@@ -576,11 +589,7 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
576
589
|
}, { isActive });
|
|
577
590
|
const marker = running ? glyphs.steer : glyphs.caret;
|
|
578
591
|
const markerColor = running ? theme.toolRunning : theme.primary;
|
|
579
|
-
const placeholder = running
|
|
580
|
-
? `Type to queue a message, Esc twice to interrupt${glyphs.ellipsis}`
|
|
581
|
-
: agentMode === 'plan'
|
|
582
|
-
? `Describe what you want to build; I'll research and plan it first${glyphs.ellipsis}`
|
|
583
|
-
: `Send a message (/ for commands, @ for files)${glyphs.ellipsis}`;
|
|
592
|
+
const placeholder = running ? interactionCopy.runningPlaceholder : interactionCopy.idlePlaceholder;
|
|
584
593
|
const overflow = overflowChip(layout.hiddenAbove, layout.hiddenBelow, glyphs);
|
|
585
594
|
const tokenColor = theme.hintFg ?? theme.muted;
|
|
586
595
|
const renderSegments = (segments, keyPrefix) => segments.map((segment, i) => (_jsx(Text, { color: segment.kind === 'token' ? tokenColor : undefined, children: segment.text }, `${keyPrefix}${i}`)));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { QueueLayout } from './layout/queue-layout.js';
|
|
3
|
+
import type { Theme } from './theme.js';
|
|
4
|
+
/** Persistent receipt-backed footer, with no timer or separate queue state. */
|
|
5
|
+
export declare const QueuePanel: React.NamedExoticComponent<{
|
|
6
|
+
layout: QueueLayout;
|
|
7
|
+
theme: Theme;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
/** Persistent receipt-backed footer, with no timer or separate queue state. */
|
|
5
|
+
export const QueuePanel = React.memo(function QueuePanel({ layout, theme }) {
|
|
6
|
+
if (!layout.rows)
|
|
7
|
+
return null;
|
|
8
|
+
return _jsxs(Box, { flexDirection: "column", flexShrink: 0, height: layout.rows, children: [_jsx(Text, { color: theme.accent, wrap: "truncate", children: layout.title }), layout.rows > 1 && layout.items.map((item) => _jsx(Text, { color: theme.muted, wrap: "truncate", children: item.label }, item.queueId))] });
|
|
9
|
+
});
|
|
@@ -12,6 +12,8 @@ export interface QueueStatusRowProps {
|
|
|
12
12
|
}
|
|
13
13
|
/** Return the first nonempty logical line without terminal control characters. */
|
|
14
14
|
export declare function queueSummary(text: string): string;
|
|
15
|
+
/** Truncate at a whole grapheme, reserving the ellipsis in the same cell budget. */
|
|
16
|
+
export declare function truncateSummary(text: string, columns: number, ellipsis: string): string;
|
|
15
17
|
/** Format a queue using only the columns assigned by the status bar. */
|
|
16
18
|
export declare function formatQueueStatus(props: Omit<QueueStatusRowProps, 'theme'>): {
|
|
17
19
|
prefix: string;
|
|
@@ -11,7 +11,7 @@ export function queueSummary(text) {
|
|
|
11
11
|
.find(Boolean) ?? '(empty)';
|
|
12
12
|
}
|
|
13
13
|
/** Truncate at a whole grapheme, reserving the ellipsis in the same cell budget. */
|
|
14
|
-
function truncateSummary(text, columns, ellipsis) {
|
|
14
|
+
export function truncateSummary(text, columns, ellipsis) {
|
|
15
15
|
if (stringWidth(text) <= columns)
|
|
16
16
|
return text;
|
|
17
17
|
if (columns < stringWidth(ellipsis))
|
package/dist/ui/StatusBar.d.ts
CHANGED
|
@@ -1,42 +1,27 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Status bar (spec §4.8). Width-aware, borderless, EXACTLY one row: a left
|
|
3
|
-
* cluster (run status, model, thinking level) and a right cluster (context
|
|
4
|
-
* gauge, occupancy pair, session total, cost, tokens/sec + elapsed while
|
|
5
|
-
* running, off-bottom `↑N`). Clusters drop out gracefully on narrow terminals.
|
|
6
|
-
*
|
|
7
|
-
* THE RIGHT CLUSTER'S WIDTH LADDER (context-usage-gauge-accuracy §3.6):
|
|
8
|
-
*
|
|
9
|
-
* >= 96 [####....] 43% 86.0k/200.0k total 1.2M^ 48.0k v $3.21 12 tok/s 1m02s
|
|
10
|
-
* 72-95 [####....] 43% 86.0k/200.0k $3.21
|
|
11
|
-
* 60-71 [####....] 43% $3.21
|
|
12
|
-
* < 60 43% $3.21
|
|
13
|
-
*
|
|
14
|
-
* Dropping the round border here buys back 3 rows of a 24-row terminal — an
|
|
15
|
-
* eighth of the screen was being spent framing a single line of text.
|
|
16
|
-
*/
|
|
1
|
+
/** A single row of complete, measured status fields. */
|
|
17
2
|
import React from 'react';
|
|
18
3
|
import type { Theme } from './theme.js';
|
|
19
4
|
import type { TermCapabilities } from './capabilities.js';
|
|
20
5
|
import type { UsageTotal } from '../agent/reducer.js';
|
|
21
6
|
import type { ContextUsageSnapshot } from '../compaction/types.js';
|
|
22
|
-
import {
|
|
7
|
+
import type { AgentMode } from '../agent/agent-mode.js';
|
|
23
8
|
import type { PendingSteering } from '../agent/queued-messages.js';
|
|
24
|
-
|
|
9
|
+
import { type RunPhase } from './layout/status-layout.js';
|
|
10
|
+
export interface StatusBarProps {
|
|
11
|
+
runPhase?: RunPhase;
|
|
12
|
+
activeTool?: {
|
|
13
|
+
name: string;
|
|
14
|
+
toolCallId: string;
|
|
15
|
+
} | null;
|
|
16
|
+
runOutcome?: "none" | "ended" | "interrupted" | "failed";
|
|
17
|
+
reducedMotion?: boolean;
|
|
18
|
+
waitingForConfirmation?: boolean;
|
|
19
|
+
stopping?: boolean;
|
|
25
20
|
pendingSteering?: readonly PendingSteering[];
|
|
26
21
|
interruptHint?: string;
|
|
27
22
|
model: string;
|
|
28
23
|
provider: string;
|
|
29
24
|
usageTotal: UsageTotal;
|
|
30
|
-
/**
|
|
31
|
-
* Context occupancy, as one object (context-usage-gauge-accuracy §4.3).
|
|
32
|
-
*
|
|
33
|
-
* IT REPLACED FOUR PARALLEL PROPS - `contextTokens`, `contextWindow`,
|
|
34
|
-
* `contextWindowKnown`, `contextEstimated`. Four props describing one reading
|
|
35
|
-
* are four chances for a caller to update three of them, which is the props-
|
|
36
|
-
* layer form of the "many writers" defect this feature removed from the
|
|
37
|
-
* reducer. `usageTotal` stays separate because it genuinely IS another
|
|
38
|
-
* quantity: session spend, not occupancy.
|
|
39
|
-
*/
|
|
40
25
|
context: ContextUsageSnapshot;
|
|
41
26
|
status: 'idle' | 'running';
|
|
42
27
|
elapsedMs: number;
|
|
@@ -44,157 +29,36 @@ interface StatusBarProps {
|
|
|
44
29
|
tokPerSec: number;
|
|
45
30
|
theme: Theme;
|
|
46
31
|
caps: TermCapabilities;
|
|
47
|
-
/**
|
|
48
|
-
* Lines scrolled off the bottom; > 0 renders `↑N`. Inline mode has no
|
|
49
|
-
* self-drawn viewport, so it simply never sends anything but 0 — the bar
|
|
50
|
-
* needs no render-mode branch of its own.
|
|
51
|
-
*/
|
|
52
32
|
scrolledLines?: number;
|
|
53
|
-
/**
|
|
54
|
-
* Ctrl+L redraw carrier — invariant I-5 (spec §4.13). See `redrawChar`.
|
|
55
|
-
*/
|
|
56
33
|
redrawNonce?: number;
|
|
57
34
|
agentMode?: AgentMode;
|
|
58
|
-
/** Non-null while a deferred `plan -> build` waits for `agent_end`. */
|
|
59
35
|
pendingAgentMode?: AgentMode | null;
|
|
60
|
-
/**
|
|
61
|
-
* Live subagent counts (team-subagents §6.2), or absent when no dispatch is
|
|
62
|
-
* running — an ordinary session's bar is then unchanged.
|
|
63
|
-
*
|
|
64
|
-
* LEFT CLUSTER, and it degrades (D-20 / P2-1). The left cluster is
|
|
65
|
-
* `flexShrink={0}`, so every column it takes comes out of the context gauge
|
|
66
|
-
* and the cost readout opposite it; on an 80-column terminal the full
|
|
67
|
-
* `agents 3/5` visibly squeezes them, and the gauge is how a user notices they
|
|
68
|
-
* are about to run out of context. Below `TEAM_LIMITS.statusCompactCols` this
|
|
69
|
-
* therefore renders as `[3]` — the running count alone.
|
|
70
|
-
*
|
|
71
|
-
* This does NOT contradict the "no `cols >= N` breakpoint" rule above: that
|
|
72
|
-
* rule is written about `agentMode`, whose reason is that the mode must never
|
|
73
|
-
* be unreportable. The team counter has a second, fuller home in `TeamPanel`
|
|
74
|
-
* and a third in `TeamCard`; it is the one readout on this bar that can afford
|
|
75
|
-
* to degrade.
|
|
76
|
-
*/
|
|
77
36
|
teamActive?: {
|
|
78
37
|
running: number;
|
|
79
38
|
total: number;
|
|
80
39
|
};
|
|
81
|
-
/**
|
|
82
|
-
* Live todo counts (todo-plan-execution §6.3), or absent.
|
|
83
|
-
*
|
|
84
|
-
* `App` sends this ONLY when a list exists AND the rail does not: with the
|
|
85
|
-
* rail on screen the numbers are already there in a larger typeface, and
|
|
86
|
-
* duplicating them costs the context gauge columns it cannot spare. This is
|
|
87
|
-
* therefore the INLINE-MODE and NARROW-TERMINAL fallback for R-d, which is
|
|
88
|
-
* exactly why it is here rather than being folded into the rail's own gate.
|
|
89
|
-
*
|
|
90
|
-
* Degrades below `TODO_LIMITS.statusCompactCols` from `todo 2/7` to `[2/7]`,
|
|
91
|
-
* the same treatment and the same justification as the team counter's
|
|
92
|
-
* `agents 3/5` -> `[3]`. See the note on `agentMode` for why that rule does
|
|
93
|
-
* NOT apply to the mode word.
|
|
94
|
-
*/
|
|
95
40
|
todoActive?: {
|
|
96
41
|
done: number;
|
|
97
42
|
total: number;
|
|
98
43
|
};
|
|
99
|
-
/**
|
|
100
|
-
* Live background services (background-service-supervision §3.8), or absent
|
|
101
|
-
* when none are running — an ordinary session's bar is then unchanged.
|
|
102
|
-
*
|
|
103
|
-
* LEFT CLUSTER, after the todo chip, and it DEGRADES rather than hiding below
|
|
104
|
-
* `PROC_LIMITS.statusCompactCols`. That is the `todoActive` / `teamActive`
|
|
105
|
-
* ladder rather than `compactionActive`'s hide-below-threshold one, and the
|
|
106
|
-
* choice is deliberate: `compact` and `fast` describe something the CLI is
|
|
107
|
-
* doing to itself, whereas this is a PROCESS ON THE USER'S MACHINE HOLDING A
|
|
108
|
-
* PORT. A narrow terminal is a reason to shorten it, never a reason to stop
|
|
109
|
-
* saying it.
|
|
110
|
-
*
|
|
111
|
-
* `live` counts `starting | ready | running`. The transcript records events
|
|
112
|
-
* about a service (D-11); this is where "what is running right now" lives,
|
|
113
|
-
* alongside `/bg` and `bash_output`.
|
|
114
|
-
*/
|
|
115
44
|
servicesActive?: {
|
|
116
45
|
live: number;
|
|
117
46
|
};
|
|
118
|
-
/**
|
|
119
|
-
* Live retry state (llm-api-retry-backoff §6.6), or absent when nothing is
|
|
120
|
-
* retrying — an ordinary session's bar is then unchanged.
|
|
121
|
-
*
|
|
122
|
-
* LEFT CLUSTER, after `teamActive`, and it DEGRADES. It is a run-state fact like
|
|
123
|
-
* `agents 3/5`, and the left cluster is `flexShrink={0}` so every column it takes
|
|
124
|
-
* comes out of the context gauge; below `RETRY_UI.statusCompactCols` it renders
|
|
125
|
-
* as `[r3]`.
|
|
126
|
-
*
|
|
127
|
-
* This does NOT contradict the "no `cols >= N` breakpoint" rule on `agentMode`
|
|
128
|
-
* above: that rule exists because the MODE must never be unreportable. The retry
|
|
129
|
-
* has a second, fuller home in the transcript card, which is exactly why it is
|
|
130
|
-
* the kind of readout that can afford to shrink.
|
|
131
|
-
*/
|
|
132
47
|
retryActive?: {
|
|
133
48
|
attempt: number;
|
|
134
49
|
max: number;
|
|
135
50
|
secondsLeft: number;
|
|
136
51
|
};
|
|
137
|
-
/**
|
|
138
|
-
* Render-governor rung (tui-render-performance L4). `> 0` renders a muted
|
|
139
|
-
* `eco` chip.
|
|
140
|
-
*
|
|
141
|
-
* I-L4-2 — THE GOVERNOR IS USER-VISIBLE, and this is where. A user who cannot
|
|
142
|
-
* see why the stream got chunkier will conclude the model got slower, and
|
|
143
|
-
* degrading silently is the one thing a claim of robustness cannot afford.
|
|
144
|
-
* It sits in the RIGHT cluster next to the other derived numbers and is
|
|
145
|
-
* suppressed on a narrow terminal, which is the `teamActive` treatment rather
|
|
146
|
-
* than the `agentMode` one: the mode must never be unreportable, whereas an
|
|
147
|
-
* eco chip on a 50-column terminal costs the context gauge more than it says.
|
|
148
|
-
*/
|
|
149
52
|
ecoRung?: number;
|
|
150
|
-
/**
|
|
151
|
-
* The live fast tier (fast-model-tier §6), or absent when it is off for this
|
|
152
|
-
* session — in which case an ordinary session's bar is byte-identical.
|
|
153
|
-
*
|
|
154
|
-
* RIGHT CLUSTER, next to `eco`, and it DEGRADES below
|
|
155
|
-
* `FAST_LIMITS.statusCompactCols`. This is the `teamActive` / `ecoRung`
|
|
156
|
-
* treatment rather than the `agentMode` one, and the distinction is the note
|
|
157
|
-
* on `agentMode` above: the mode must never be unreportable, whereas the fast
|
|
158
|
-
* tier has a fuller home in `/fast status` — which is the GUARANTEED reporting
|
|
159
|
-
* surface — and a chip on a 60-column terminal costs the context gauge more
|
|
160
|
-
* than it says.
|
|
161
|
-
*
|
|
162
|
-
* `inFlight` renders `fast*`, so a review that is taking a while is visible
|
|
163
|
-
* without a spinner competing with the run's own.
|
|
164
|
-
*/
|
|
165
53
|
fastActive?: {
|
|
166
54
|
inFlight: boolean;
|
|
167
55
|
};
|
|
168
|
-
/**
|
|
169
|
-
* Live context compaction (context-auto-compaction §6.2), or absent when it is
|
|
170
|
-
* off for this session — in which case an ordinary session's bar is unchanged.
|
|
171
|
-
*
|
|
172
|
-
* RIGHT CLUSTER, next to `fast` and `eco`, and it DEGRADES below
|
|
173
|
-
* `COMPACTION_LIMITS.statusCompactCols`. The `teamActive` / `ecoRung` /
|
|
174
|
-
* `fastActive` treatment rather than the `agentMode` one, for the reason the
|
|
175
|
-
* note on `agentMode` gives: the mode must never be unreportable, whereas
|
|
176
|
-
* compaction has a fuller home in `/compact status` — the GUARANTEED reporting
|
|
177
|
-
* surface (§6.5) — and a chip on a 60-column terminal costs the context gauge
|
|
178
|
-
* more than it says.
|
|
179
|
-
*
|
|
180
|
-
* `inFlight` renders `compacting`, so a summarization that is taking a while is
|
|
181
|
-
* visible on this row without a spinner competing with the activity line's.
|
|
182
|
-
*/
|
|
183
56
|
compactionActive?: {
|
|
184
57
|
inFlight: boolean;
|
|
185
58
|
};
|
|
186
|
-
/**
|
|
187
|
-
* Where the gauge turns amber and red (§6.2).
|
|
188
|
-
*
|
|
189
|
-
* ABSENT means today's hardcoded `{ warn: 60, high: 85 }`, so a session with
|
|
190
|
-
* compaction off renders a byte-identical bar. When present these are
|
|
191
|
-
* `warnThreshold * 100` and `threshold * 100`, which is what makes the colour
|
|
192
|
-
* the user sees agree with the number the trigger fires on.
|
|
193
|
-
*/
|
|
194
59
|
gaugeMarks?: {
|
|
195
60
|
warn: number;
|
|
196
61
|
high: number;
|
|
197
62
|
};
|
|
198
63
|
}
|
|
199
64
|
export declare function StatusBar(props: StatusBarProps): React.ReactElement;
|
|
200
|
-
export {};
|
package/dist/ui/StatusBar.js
CHANGED
|
@@ -1,148 +1,33 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** A single row of complete, measured status fields. */
|
|
3
|
+
import React from 'react';
|
|
3
4
|
import { Box, Text, useStdout } from 'ink';
|
|
4
5
|
import { pickGlyphs } from './glyphs.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import { FAST_LIMITS } from '../fast/limits.js';
|
|
10
|
-
import { COMPACTION_LIMITS } from '../compaction/limits.js';
|
|
11
|
-
import { TEAM_LIMITS } from '../team/limits.js';
|
|
12
|
-
import { TODO_LIMITS } from '../todo/limits.js';
|
|
13
|
-
import { PROC_LIMITS } from '../proc/limits.js';
|
|
14
|
-
import { QueueStatusRow } from './QueueStatusRow.js';
|
|
15
|
-
/** Below this the `eco` chip costs the context gauge more than it is worth. */
|
|
16
|
-
const ECO_MIN_COLS = 72;
|
|
17
|
-
/**
|
|
18
|
-
* Where `86k/200k` starts being shown (context-usage-gauge-accuracy §3.6 / W5).
|
|
19
|
-
*
|
|
20
|
-
* IT TAKES THE COLUMNS THE SESSION TOTAL USED TO HAVE, and the ordering is the
|
|
21
|
-
* point rather than a layout accident (D-6). "How full am I, out of how much" is
|
|
22
|
-
* the question a user asks the gauge; "what has this session cost in tokens" is
|
|
23
|
-
* a different one, and putting the second in front of the first spends the
|
|
24
|
-
* scarce columns on the number the user is more likely to MISREAD - the total
|
|
25
|
-
* looks like an occupancy until you notice it exceeds the window.
|
|
26
|
-
*/
|
|
27
|
-
const ABSOLUTE_PAIR_MIN_COLS = 72;
|
|
28
|
-
/**
|
|
29
|
-
* Where the session total returns, now prefixed.
|
|
30
|
-
*
|
|
31
|
-
* `total ` IS AN ASCII WORD, NOT A SIGMA. `ui/**` is inside the glyph scanner's
|
|
32
|
-
* scope, so a new symbol would need an entry in `glyphs.ts` with an ASCII
|
|
33
|
-
* fallback for a legacy `cmd.exe` - a real cost for a three-character prefix
|
|
34
|
-
* that reads worse.
|
|
35
|
-
*/
|
|
36
|
-
const SESSION_TOTAL_MIN_COLS = 96;
|
|
37
|
-
/**
|
|
38
|
-
* I-5 — DO NOT "CLEAN THIS UP". Ink short-circuits identical output at TWO
|
|
39
|
-
* gates (`ink.js:132` and `log-update.js:13`), so a fixed frame whose React
|
|
40
|
-
* output has not changed is never repainted. `Ctrl+L` therefore cannot work by
|
|
41
|
-
* writing an escape sequence; it works by making the rendered STRING differ.
|
|
42
|
-
* This alternates the status bar's left gutter between U+0020 and U+00A0: both
|
|
43
|
-
* render one invisible column, so there is zero visual or layout difference,
|
|
44
|
-
* but the bytes differ and both gates open.
|
|
45
|
-
*
|
|
46
|
-
* It must sit at the START of the line: Ink `trimEnd()`s every row (output.js),
|
|
47
|
-
* and JS `trimEnd` strips U+00A0 too — a trailing carrier would be deleted and
|
|
48
|
-
* Ctrl+L would degrade to a silent no-op.
|
|
49
|
-
*/
|
|
50
|
-
/** Built by code point so no formatter can normalize it back to a plain space. */
|
|
51
|
-
const NBSP = String.fromCharCode(0x00a0);
|
|
52
|
-
function redrawChar(nonce) {
|
|
53
|
-
return nonce % 2 === 0 ? ' ' : NBSP;
|
|
54
|
-
}
|
|
55
|
-
function queueStateLabels(props, compact) {
|
|
56
|
-
const mode = props.agentMode ?? 'build';
|
|
57
|
-
const next = props.pendingAgentMode;
|
|
58
|
-
const modeLabel = compact ? mode[0].toUpperCase() : mode;
|
|
59
|
-
const nextLabel = next ? (compact ? next[0].toUpperCase() : next) : '';
|
|
60
|
-
const stopping = /force|again/i.test(props.interruptHint ?? '');
|
|
61
|
-
return [
|
|
62
|
-
nextLabel ? `${modeLabel}>${nextLabel}` : modeLabel,
|
|
63
|
-
stopping ? (compact ? 'E!' : 'Esc!')
|
|
64
|
-
: props.status === 'running' ? (compact ? 'E2' : 'Esc x2') : '',
|
|
65
|
-
props.servicesActive?.live ? (compact ? 'S+' : `svc ${props.servicesActive.live}`) : '',
|
|
66
|
-
props.todoActive?.total ? (compact ? 'T+'
|
|
67
|
-
: `todo ${props.todoActive.done}/${props.todoActive.total}`) : '',
|
|
68
|
-
].filter(Boolean).join(' ') + ' ';
|
|
69
|
-
}
|
|
70
|
-
function QueueStatusBar(props) {
|
|
71
|
-
const pending = props.pendingSteering;
|
|
72
|
-
const normal = queueStateLabels(props, false);
|
|
73
|
-
const paused = props.status !== 'running';
|
|
74
|
-
const suffix = pending.length > 1 ? ` (+${pending.length - 1} more)` : '';
|
|
75
|
-
const minimum = stringWidth((paused ? 'Queue (paused): ' : 'Queue: ') + suffix)
|
|
76
|
-
+ stringWidth(pickGlyphs(props.caps).ellipsis);
|
|
77
|
-
const compact = stringWidth(normal) + minimum + 1 > props.columns;
|
|
78
|
-
const labels = compact ? queueStateLabels(props, true) : normal;
|
|
79
|
-
const columns = props.columns - 1 - stringWidth(labels);
|
|
80
|
-
return _jsxs(Box, { width: props.columns, height: 1, flexShrink: 0, children: [_jsx(Text, { children: redrawChar(props.redrawNonce ?? 0) }), _jsx(Text, { color: props.theme.noticeWarn, children: labels }), _jsx(QueueStatusRow, { pending: pending, paused: paused, columns: columns, compact: compact, theme: props.theme, caps: props.caps })] });
|
|
81
|
-
}
|
|
6
|
+
import { liveSpinner } from './ActivityLine.js';
|
|
7
|
+
import { planStatusFields } from './layout/status-layout.js';
|
|
8
|
+
/** An invisible leading byte change forces Ink to repaint on Ctrl+L. */
|
|
9
|
+
const redrawChar = (nonce) => nonce % 2 ? String.fromCharCode(0x00a0) : ' ';
|
|
82
10
|
export function StatusBar(props) {
|
|
83
|
-
const { model, provider, usageTotal, context, status, elapsedMs, thinkingLevel, tokPerSec, theme, caps, scrolledLines = 0, redrawNonce = 0, agentMode = 'build', pendingAgentMode = null, teamActive, todoActive, servicesActive, retryActive, ecoRung = 0, fastActive, compactionActive, gaugeMarks, } = props;
|
|
84
11
|
const { stdout } = useStdout();
|
|
85
12
|
const cols = stdout?.columns ?? 80;
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// absolute pair sees the identical string it saw before.
|
|
107
|
-
const occupancyApproximate = context.source === 'estimate' || context.deltaTokens > 0;
|
|
108
|
-
const pctLabel = `${context.windowKnown && !occupancyApproximate ? '' : '~'}${gauge.pct}%`;
|
|
109
|
-
// `?` ON THE WINDOW, AND ONLY ON THE NEW READOUT. `windowKnown` is false
|
|
110
|
-
// exactly when `buildRuntimeModel` invented the 128k placeholder, and saying
|
|
111
|
-
// `128k?` is the difference between a denominator the user can act on and one
|
|
112
|
-
// they will assume came from their model card.
|
|
113
|
-
const windowLabel = `${formatTokens(context.window)}${context.windowKnown ? '' : '?'}`;
|
|
114
|
-
const absolutePair = `${formatTokens(context.occupied)}/${windowLabel}`;
|
|
115
|
-
// THE PROMPT-SIDE TOTAL, NOT `inputTokens` (P1-3 / AC-11). Anthropic reports
|
|
116
|
-
// `input_tokens` EXCLUDING cached tokens, so the old expression under-reported
|
|
117
|
-
// for anyone behind a caching gateway - on the same row as a `$` that has
|
|
118
|
-
// always priced the cache. Any session with cache hits sees this number grow,
|
|
119
|
-
// and that is the fix rather than a regression.
|
|
120
|
-
const tokens = `total ${formatTokens(promptTokensOf(usageTotal))}${glyphs.arrowUp} ${formatTokens(usageTotal.outputTokens)}${glyphs.arrowDown}`;
|
|
121
|
-
const statusGlyph = running ? theme.symbols.toolRunning : theme.symbols.toolPending;
|
|
122
|
-
// Nothing at all in build mode with nothing pending: the default posture is
|
|
123
|
-
// not news, and a permanent word here would be furniture.
|
|
124
|
-
const modeWord = agentMode === 'build' && !pendingAgentMode
|
|
125
|
-
? ''
|
|
126
|
-
: pendingAgentMode
|
|
127
|
-
? `${MODE_LABEL[agentMode]} ${glyphs.arrowRight} ${MODE_LABEL[pendingAgentMode]}`
|
|
128
|
-
: MODE_LABEL[agentMode];
|
|
129
|
-
const actionHints = [running ? (props.interruptHint ?? 'Esc abort') : '',
|
|
130
|
-
servicesActive ? `Ctrl+C stop ${servicesActive.live}` : '',
|
|
131
|
-
scrolledLines > 0 ? 'PgDn down' : ''].filter(Boolean).join(' | ');
|
|
132
|
-
const hintCols = actionHints ? Math.min(Math.max(0, cols - 1), stringWidth(actionHints) + 2) : 0;
|
|
133
|
-
const detailCols = Math.max(0, cols - 1 - hintCols);
|
|
134
|
-
return (
|
|
135
|
-
// `flexShrink={0}` on the left cluster is not cosmetic. When the two clusters
|
|
136
|
-
// over-subscribe the row, yoga shrinks the flexible children and Ink's text
|
|
137
|
-
// measurement drops a character from EACH of them — the bar renders "idl"
|
|
138
|
-
// and "? hel" rather than truncating cleanly at one end. Pinning the left
|
|
139
|
-
// cluster keeps the run status and model intact and makes the degradation
|
|
140
|
-
// land in one predictable place.
|
|
141
|
-
_jsxs(Box, { width: cols, flexDirection: "row", justifyContent: "space-between", flexShrink: 0, height: 1, overflow: "hidden", children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { children: redrawChar(redrawNonce) }) }), hintCols > 0 && (_jsx(Box, { width: hintCols, flexShrink: 0, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: theme.noticeWarn, children: [actionHints, ' '] }) })), _jsxs(Box, { display: detailCols > 0 ? 'flex' : 'none', width: detailCols, flexShrink: 0, flexDirection: "row", justifyContent: "space-between", overflow: "hidden", children: [_jsxs(Box, { flexDirection: "row", flexShrink: 0, overflow: "hidden", children: [_jsxs(Text, { color: running ? theme.toolRunning : theme.toolDone, children: [statusGlyph, " ", running ? 'running' : 'idle'] }), modeWord && (_jsxs(Text, { color: theme.accent, bold: true, children: [' ', modeWord] })), teamActive && (_jsxs(Text, { color: theme.toolRunning, bold: true, children: [' ', cols >= TEAM_LIMITS.statusCompactCols
|
|
142
|
-
? `agents ${teamActive.running}/${teamActive.total}`
|
|
143
|
-
: `[${teamActive.running}]`] })), retryActive && (_jsxs(Text, { color: theme.noticeWarn, bold: true, children: [' ', formatRetryChip(retryActive, cols)] })), todoActive && (_jsxs(Text, { color: theme.accent, children: [' ', cols >= TODO_LIMITS.statusCompactCols
|
|
144
|
-
? `todo ${todoActive.done}/${todoActive.total}`
|
|
145
|
-
: `[${todoActive.done}/${todoActive.total}]`] })), servicesActive && (_jsxs(Text, { color: theme.accent, children: [' ', cols >= PROC_LIMITS.statusCompactCols
|
|
146
|
-
? `svc ${servicesActive.live}`
|
|
147
|
-
: `[${servicesActive.live}]`] })), cols >= 60 && (cols >= 110 || (!running && !servicesActive && scrolledLines === 0)) && (_jsxs(Text, { color: theme.muted, children: [' ', provider, ":", model] })), thinkingLevel !== 'off' && cols >= 72 && (_jsxs(Text, { color: theme.accent, children: [" think:", thinkingLevel] }))] }), _jsxs(Box, { flexDirection: "row", children: [compactionActive && cols >= COMPACTION_LIMITS.statusCompactCols && (_jsxs(Text, { color: compactionActive.inFlight ? theme.accent : theme.muted, children: [compactionActive.inFlight ? 'compacting' : 'compact', ' '] })), fastActive && cols >= FAST_LIMITS.statusCompactCols && (_jsxs(Text, { color: theme.muted, children: ["fast", fastActive.inFlight ? '*' : '', ' '] })), ecoRung > 0 && cols >= ECO_MIN_COLS && (_jsxs(Text, { color: theme.muted, children: ["eco", ' '] })), scrolledLines > 0 && (_jsxs(Text, { color: theme.noticeWarn, children: [glyphs.arrowUp, scrolledLines, ' '] })), cols >= 60 && (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: "[" }), _jsx(Text, { color: gauge.fillColor, children: gauge.filled }), _jsx(Text, { color: gauge.trackColor, children: gauge.empty }), _jsx(Text, { color: theme.muted, children: "] " })] })), _jsx(Text, { color: theme.muted, children: pctLabel }), cols >= ABSOLUTE_PAIR_MIN_COLS && (_jsxs(Text, { color: theme.muted, children: [" ", absolutePair] })), cols >= SESSION_TOTAL_MIN_COLS && _jsxs(Text, { color: theme.muted, children: [" ", tokens] }), _jsxs(Text, { color: theme.muted, children: [" ", formatCost(usageTotal.costUsd)] }), running && (_jsxs(Text, { color: theme.muted, children: [tokPerSec > 0 && cols >= 72 ? ` ${tokPerSec} tok/s` : '', " ", formatDuration(elapsedMs)] }))] })] })] }));
|
|
13
|
+
const phase = props.runPhase ?? (props.status === 'running' ? 'waiting' : 'idle');
|
|
14
|
+
const active = phase !== 'idle' || !!props.compactionActive?.inFlight;
|
|
15
|
+
const glyph = pickGlyphs(props.caps).spinnerStill;
|
|
16
|
+
const spinner = active ? liveSpinner(props.reducedMotion ?? false, props.caps) : null;
|
|
17
|
+
const fields = planStatusFields({
|
|
18
|
+
columns: cols, phase, pendingCount: props.pendingSteering?.length ?? 0,
|
|
19
|
+
context: props.context, usageTotal: props.usageTotal,
|
|
20
|
+
thinkingLevel: props.thinkingLevel, elapsedMs: props.elapsedMs, tokPerSec: props.tokPerSec,
|
|
21
|
+
activeTool: props.activeTool, runOutcome: props.runOutcome, spinner: active ? glyph : '',
|
|
22
|
+
waitingForConfirmation: props.waitingForConfirmation, stopping: props.stopping,
|
|
23
|
+
compacting: props.compactionActive?.inFlight, retryActive: props.retryActive,
|
|
24
|
+
mode: props.agentMode, pendingMode: props.pendingAgentMode, servicesActive: props.servicesActive,
|
|
25
|
+
teamActive: props.teamActive, todoActive: props.todoActive,
|
|
26
|
+
ecoRung: props.ecoRung, fastActive: props.fastActive, scrolledLines: props.scrolledLines,
|
|
27
|
+
});
|
|
28
|
+
const tones = { normal: props.theme.primary, muted: props.theme.muted,
|
|
29
|
+
warning: props.theme.noticeWarn, error: props.theme.noticeError };
|
|
30
|
+
return _jsxs(Box, { width: cols, height: 1, flexShrink: 0, flexDirection: "row", children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { children: redrawChar(props.redrawNonce ?? 0) }) }), fields.map((item, index) => _jsxs(React.Fragment, { children: [index > 0 && _jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { color: props.theme.muted, children: " | " }) }), _jsx(Box, { width: item.cells, flexShrink: 0, children: _jsx(Text, { color: tones[item.tone], children: item.id === 'phase' && active
|
|
31
|
+
? _jsxs(_Fragment, { children: [spinner ?? glyph, item.text.slice(glyph.length)] })
|
|
32
|
+
: item.text }) })] }, item.id))] });
|
|
148
33
|
}
|
|
@@ -22,4 +22,6 @@ interface StdoutLike {
|
|
|
22
22
|
* for future TTY-aware refinements and kept in the signature for callers.
|
|
23
23
|
*/
|
|
24
24
|
export declare function detectCapabilities(env: EnvLike, _stdout?: StdoutLike): TermCapabilities;
|
|
25
|
+
/** Full TUI styling is a policy, not a guess based on inherited shell markers. */
|
|
26
|
+
export declare function resolveTuiCapabilities(env: EnvLike, color?: boolean): TermCapabilities;
|
|
25
27
|
export {};
|
package/dist/ui/capabilities.js
CHANGED
|
@@ -57,3 +57,9 @@ export function detectCapabilities(env, _stdout) {
|
|
|
57
57
|
unicode: detectUnicode(env),
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
+
/** Full TUI styling is a policy, not a guess based on inherited shell markers. */
|
|
61
|
+
export function resolveTuiCapabilities(env, color) {
|
|
62
|
+
const disabled = color === false || env.NO_COLOR !== undefined ||
|
|
63
|
+
env.FORCE_COLOR === '0' || env.FORCE_COLOR === 'false';
|
|
64
|
+
return { unicode: true, colorLevel: disabled ? 0 : 3 };
|
|
65
|
+
}
|