@aragon-agent/cli 0.6.7 → 0.6.9
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 +107 -77
- package/dist/agent/controller.d.ts +11 -7
- package/dist/agent/controller.js +35 -48
- package/dist/agent/headless.d.ts +2 -1
- package/dist/agent/prompt-options.d.ts +15 -0
- package/dist/agent/prompt-options.js +1 -0
- package/dist/agent/reducer.d.ts +7 -113
- package/dist/agent/reducer.js +0 -62
- package/dist/cli.js +111 -182
- package/dist/commands/builtins.js +3 -1
- package/dist/commands/perf.d.ts +32 -2
- package/dist/commands/perf.js +32 -2
- package/dist/commands/registry.d.ts +4 -5
- package/dist/config/env.js +0 -130
- package/dist/config/load.d.ts +0 -2
- package/dist/config/load.js +2 -31
- package/dist/config/schema.d.ts +1 -14
- package/dist/config/schema.js +0 -1
- package/dist/config/store.js +2 -0
- package/dist/input/interrupt-gesture.d.ts +18 -0
- package/dist/input/interrupt-gesture.js +19 -0
- package/dist/input/pointer-router.d.ts +11 -0
- package/dist/input/pointer-router.js +14 -0
- package/dist/logging/install.d.ts +2 -2
- package/dist/logging/install.js +2 -2
- package/dist/todo/follow-through.d.ts +0 -4
- package/dist/todo/follow-through.js +0 -4
- package/dist/todo/limits.d.ts +0 -10
- package/dist/todo/limits.js +0 -10
- package/dist/todo/prompt.d.ts +0 -15
- package/dist/todo/store.d.ts +8 -13
- package/dist/todo/store.js +13 -14
- package/dist/todo/types.d.ts +2 -1
- package/dist/ui/ActivityLine.d.ts +26 -34
- package/dist/ui/ActivityLine.js +30 -19
- package/dist/ui/App.d.ts +3 -41
- package/dist/ui/App.js +223 -247
- package/dist/ui/BottomStatusRow.d.ts +1 -3
- package/dist/ui/BottomStatusRow.js +4 -8
- package/dist/ui/Composer.d.ts +38 -5
- package/dist/ui/Composer.js +45 -23
- package/dist/ui/Header.d.ts +2 -16
- package/dist/ui/Header.js +3 -18
- package/dist/ui/Logo.d.ts +1 -17
- package/dist/ui/Logo.js +0 -16
- package/dist/ui/ModeChip.d.ts +7 -18
- package/dist/ui/ModeChip.js +9 -0
- package/dist/ui/PromptCaret.d.ts +11 -0
- package/dist/ui/PromptCaret.js +31 -0
- package/dist/ui/PromptInput.d.ts +10 -33
- package/dist/ui/PromptInput.js +37 -20
- package/dist/ui/StatusBar.d.ts +1 -11
- package/dist/ui/StatusBar.js +14 -8
- package/dist/ui/ToastStack.d.ts +2 -18
- package/dist/ui/ToastStack.js +4 -11
- package/dist/ui/Transcript.d.ts +5 -71
- package/dist/ui/Transcript.js +10 -199
- package/dist/ui/composer-rows.js +50 -9
- package/dist/ui/entries/AssistantEntry.d.ts +0 -23
- package/dist/ui/entries/AssistantEntry.js +3 -4
- package/dist/ui/entries/DiffView.d.ts +1 -7
- package/dist/ui/entries/DiffView.js +2 -2
- package/dist/ui/entries/ToolCard.d.ts +0 -6
- package/dist/ui/entries/ToolCard.js +4 -13
- package/dist/ui/frame-differ.d.ts +13 -0
- package/dist/ui/frame-differ.js +37 -24
- package/dist/ui/frame-observer.d.ts +17 -0
- package/dist/ui/frame-observer.js +81 -0
- package/dist/ui/frame-parser.d.ts +7 -0
- package/dist/ui/frame-parser.js +18 -0
- package/dist/ui/ink-runtime.d.ts +1 -0
- package/dist/ui/ink-runtime.js +21 -0
- package/dist/ui/layout/AppShell.d.ts +4 -58
- package/dist/ui/layout/AppShell.js +3 -6
- package/dist/ui/layout/OverlayFrame.d.ts +1 -25
- package/dist/ui/layout/OverlayFrame.js +0 -24
- package/dist/ui/layout/ScrollIndicator.d.ts +4 -3
- package/dist/ui/layout/ScrollIndicator.js +4 -4
- package/dist/ui/layout/ScrollViewport.d.ts +16 -66
- package/dist/ui/layout/ScrollViewport.js +195 -185
- package/dist/ui/layout/budget.d.ts +3 -51
- package/dist/ui/layout/budget.js +4 -53
- package/dist/ui/layout/document-layout.d.ts +13 -0
- package/dist/ui/layout/document-layout.js +11 -0
- package/dist/ui/layout/follow-state.d.ts +1 -0
- package/dist/ui/layout/follow-state.js +1 -3
- package/dist/ui/layout/frame.d.ts +0 -22
- package/dist/ui/layout/frame.js +0 -36
- package/dist/ui/layout/scroll-indicator.d.ts +1 -1
- package/dist/ui/layout/scroll-indicator.js +3 -2
- package/dist/ui/layout/scrollbar-geometry.d.ts +17 -0
- package/dist/ui/layout/scrollbar-geometry.js +13 -0
- package/dist/ui/layout/todo-layout.d.ts +2 -3
- package/dist/ui/layout/todo-layout.js +9 -9
- package/dist/ui/layout/viewport-geometry.d.ts +2 -1
- package/dist/ui/layout/viewport-geometry.js +2 -1
- package/dist/ui/layout/virtual-window.d.ts +1 -0
- package/dist/ui/layout/virtual-window.js +1 -1
- package/dist/ui/live-clamp.d.ts +1 -29
- package/dist/ui/live-clamp.js +1 -29
- package/dist/ui/overlays/HelpOverlay.js +6 -2
- package/dist/ui/run-status-row.d.ts +61 -0
- package/dist/ui/run-status-row.js +70 -0
- package/dist/ui/screen.d.ts +1 -1
- package/dist/ui/screen.js +4 -6
- package/dist/ui/scrollbar-controller.d.ts +30 -0
- package/dist/ui/scrollbar-controller.js +124 -0
- package/dist/ui/transcript-text.d.ts +0 -8
- package/dist/ui/transcript-text.js +0 -8
- package/dist/ui/use-startup-notices.d.ts +1 -57
- package/dist/ui/use-startup-notices.js +3 -54
- package/dist/ui/use-wheel-routing.d.ts +3 -1
- package/dist/ui/use-wheel-routing.js +6 -0
- package/package.json +2 -2
- package/dist/ui/TodoStrip.d.ts +0 -36
- package/dist/ui/TodoStrip.js +0 -32
package/dist/ui/PromptInput.js
CHANGED
|
@@ -19,10 +19,11 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
19
19
|
* stays a plain `useState` because it is written from the debounced glob effect
|
|
20
20
|
* rather than from a key.
|
|
21
21
|
*/
|
|
22
|
-
import { useEffect, useLayoutEffect, useReducer, useRef, useState } from 'react';
|
|
22
|
+
import { useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react';
|
|
23
23
|
import { Box, Text, useInput, useStdout } from 'ink';
|
|
24
24
|
import { glob as tinyGlob } from 'tinyglobby';
|
|
25
25
|
import { pickGlyphs } from './glyphs.js';
|
|
26
|
+
import { PromptCaret } from './PromptCaret.js';
|
|
26
27
|
import { recognize } from '../input/keymap.js';
|
|
27
28
|
import { AutocompletePopup } from './AutocompletePopup.js';
|
|
28
29
|
import { buildAutocompleteLayout } from './layout/autocomplete.js';
|
|
@@ -43,8 +44,8 @@ const CHIP_FULL_MIN_COLS = 64;
|
|
|
43
44
|
* reads `999+`.
|
|
44
45
|
*/
|
|
45
46
|
const CHIP_MAX_N = 999;
|
|
46
|
-
/** `↓ 999+
|
|
47
|
-
const CHIP_FULL_CELLS =
|
|
47
|
+
/** `↓ 999+ lines below · PgDn` — the widest form the full branch can produce. */
|
|
48
|
+
const CHIP_FULL_CELLS = 27;
|
|
48
49
|
/** `↓999+` — the widest form the terse branch can produce. */
|
|
49
50
|
const CHIP_TERSE_CELLS = 6;
|
|
50
51
|
/**
|
|
@@ -64,7 +65,7 @@ export function scrollChip(scrolledLines, cols, glyphs) {
|
|
|
64
65
|
}
|
|
65
66
|
const noun = n === 1 ? 'line' : 'lines';
|
|
66
67
|
return {
|
|
67
|
-
text: `${glyphs.arrowDown} ${shown}
|
|
68
|
+
text: `${glyphs.arrowDown} ${shown} ${noun} below ${glyphs.midDot} PgDn`,
|
|
68
69
|
cells: CHIP_FULL_CELLS,
|
|
69
70
|
};
|
|
70
71
|
}
|
|
@@ -247,7 +248,7 @@ function isControlSeq(input) {
|
|
|
247
248
|
// ---------------------------------------------------------------------------
|
|
248
249
|
// Component
|
|
249
250
|
// ---------------------------------------------------------------------------
|
|
250
|
-
export function PromptInput({ isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, agentMode, onSubmit, onHelp,
|
|
251
|
+
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, }) {
|
|
251
252
|
const { stdout } = useStdout();
|
|
252
253
|
const [editor, dispatch] = useReducer(editorReducer, INITIAL_EDITOR_STATE);
|
|
253
254
|
const { buffer, cursor, historyIndex, dismissed, sel } = editor;
|
|
@@ -314,23 +315,20 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
314
315
|
// Deriving it twice — once for the frame and once for the budget — is exactly
|
|
315
316
|
// the trap `BottomStatusRow.tsx:5-27` documents.
|
|
316
317
|
const glyphs = pickGlyphs(caps);
|
|
317
|
-
const cols = stdout?.columns ?? 80;
|
|
318
|
+
const cols = colsProp ?? stdout?.columns ?? 80;
|
|
318
319
|
const terminalRows = stdout?.rows ?? 24;
|
|
319
|
-
|
|
320
|
-
// is always 0 there and the branch is unreachable rather than merely unused.
|
|
321
|
-
// The guard is explicit so it stays that way.
|
|
322
|
-
const chip = bordered ? scrollChip(scrolledLines, cols, glyphs) : null;
|
|
320
|
+
const chip = scrollChip(scrolledLines, cols, glyphs);
|
|
323
321
|
// Err NARROW. Wrapping wider than the box Ink gives us would make Ink wrap a
|
|
324
322
|
// second time, and then `layout.rows.length` is a lie — which is the one thing
|
|
325
323
|
// this module may not be.
|
|
326
|
-
const chromeCols =
|
|
324
|
+
const chromeCols = 2 /* border */ + 2 /* paddingX */ + 2 /* marker */;
|
|
327
325
|
const baseCols = Math.max(8, cols - chromeCols - (chip ? chip.cells + 1 : 0));
|
|
328
326
|
const measure = (wrapCols) => layoutComposer({
|
|
329
327
|
buffer,
|
|
330
328
|
cursor,
|
|
331
329
|
cols: wrapCols,
|
|
332
330
|
maxRows: draftMaxRows(terminalRows),
|
|
333
|
-
active: isActive,
|
|
331
|
+
active: isActive && cursorVisible,
|
|
334
332
|
});
|
|
335
333
|
// TWO PASSES, AND THE SECOND ONE IS LOAD-BEARING (I-8). The overflow indicator
|
|
336
334
|
// rides the last rendered row INSIDE the box the draft wraps in, so its cells
|
|
@@ -348,6 +346,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
348
346
|
const usableCols = overflows ? Math.max(8, baseCols - OVERFLOW_CELLS - 1) : baseCols;
|
|
349
347
|
const layout = overflows ? measure(usableCols) : wide;
|
|
350
348
|
const draftRowCount = Math.max(1, layout.rows.length);
|
|
349
|
+
const caretResetKey = useMemo(() => ({}), [buffer, cursor, isActive, usableCols]);
|
|
351
350
|
// --- Draft presence and height, reported only on a transition. -----------
|
|
352
351
|
// Notifying on every keystroke would re-render the whole App tree per key. The
|
|
353
352
|
// row count changes on a line break or a wrap boundary, not per character
|
|
@@ -368,6 +367,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
368
367
|
// by line. Two dispatches from one key would put the legacy root back to two
|
|
369
368
|
// commits and undo the whole of F3 for that branch.
|
|
370
369
|
const insert = (text) => {
|
|
370
|
+
onInteraction?.();
|
|
371
371
|
dispatch({ type: 'insert', text });
|
|
372
372
|
};
|
|
373
373
|
/**
|
|
@@ -379,6 +379,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
379
379
|
* needs. `clear` releases every payload in the same dispatch.
|
|
380
380
|
*/
|
|
381
381
|
const submit = (text) => {
|
|
382
|
+
onInteraction?.();
|
|
382
383
|
onSubmit(expandPastes(text, editor.pastes));
|
|
383
384
|
dispatch({ type: 'clear' });
|
|
384
385
|
};
|
|
@@ -404,6 +405,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
404
405
|
dispatch({ type: 'recall', buffer: value, cursor: value.length, historyIndex: idx });
|
|
405
406
|
};
|
|
406
407
|
const completeSelection = () => {
|
|
408
|
+
onInteraction?.();
|
|
407
409
|
const chosen = popupItems[clampedSel];
|
|
408
410
|
if (!chosen)
|
|
409
411
|
return;
|
|
@@ -417,6 +419,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
417
419
|
}
|
|
418
420
|
};
|
|
419
421
|
const verticalOrHistory = (dir) => {
|
|
422
|
+
onInteraction?.();
|
|
420
423
|
const moved = moveVertical(buffer, cursor, dir);
|
|
421
424
|
if (moved) {
|
|
422
425
|
dispatch({ type: 'moveCursor', cursor: moved.cursor });
|
|
@@ -441,13 +444,17 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
441
444
|
// appears with a popup open and is therefore easy to ship (AC-P3 / R-P2).
|
|
442
445
|
if (key.tab && key.shift)
|
|
443
446
|
return;
|
|
447
|
+
if (key.pageUp || key.pageDown || (key.shift && (key.upArrow || key.downArrow)))
|
|
448
|
+
return;
|
|
444
449
|
// Popup navigation owns Up/Down/Tab/→/Enter/Esc while it is open.
|
|
445
450
|
if (popupVisible) {
|
|
446
451
|
if (key.upArrow) {
|
|
452
|
+
onInteraction?.();
|
|
447
453
|
dispatch({ type: 'select', sel: Math.max(0, clampedSel - 1) });
|
|
448
454
|
return;
|
|
449
455
|
}
|
|
450
456
|
if (key.downArrow) {
|
|
457
|
+
onInteraction?.();
|
|
451
458
|
dispatch({ type: 'select', sel: Math.min(popupItems.length - 1, clampedSel + 1) });
|
|
452
459
|
return;
|
|
453
460
|
}
|
|
@@ -463,15 +470,19 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
463
470
|
}
|
|
464
471
|
if (key.escape) {
|
|
465
472
|
dispatch({ type: 'dismiss' });
|
|
473
|
+
onEscapeDismiss?.();
|
|
466
474
|
return;
|
|
467
475
|
}
|
|
468
476
|
// Any other key falls through so typing keeps filtering the popup.
|
|
469
477
|
}
|
|
470
|
-
if (key.escape)
|
|
471
|
-
|
|
478
|
+
if (key.escape) {
|
|
479
|
+
onEscape?.();
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
472
482
|
// Line-editing intents (Home/End/word/kill) recognized from raw keys.
|
|
473
483
|
const intent = recognize(input, key);
|
|
474
484
|
if (intent) {
|
|
485
|
+
onInteraction?.();
|
|
475
486
|
const next = applyEdit(buffer, cursor, intent);
|
|
476
487
|
dispatch({ type: 'replace', buffer: next.buffer, cursor: next.cursor });
|
|
477
488
|
return;
|
|
@@ -489,6 +500,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
489
500
|
return;
|
|
490
501
|
}
|
|
491
502
|
if (key.backspace || key.delete) {
|
|
503
|
+
onInteraction?.();
|
|
492
504
|
// The `cursor > 0` guard lives in the reducer now, so this branch is one
|
|
493
505
|
// unconditional dispatch and a no-op at the left edge returns the same
|
|
494
506
|
// state object — which React bails out on, exactly as the old
|
|
@@ -497,11 +509,15 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
497
509
|
return;
|
|
498
510
|
}
|
|
499
511
|
if (key.leftArrow) {
|
|
500
|
-
|
|
512
|
+
onInteraction?.();
|
|
513
|
+
const step = cursor >= 2 && (buffer.codePointAt(cursor - 2) ?? 0) > 0xffff ? 2 : 1;
|
|
514
|
+
dispatch({ type: 'moveCursor', cursor: Math.max(0, cursor - step) });
|
|
501
515
|
return;
|
|
502
516
|
}
|
|
503
517
|
if (key.rightArrow) {
|
|
504
|
-
|
|
518
|
+
onInteraction?.();
|
|
519
|
+
const step = (buffer.codePointAt(cursor) ?? 0) > 0xffff ? 2 : 1;
|
|
520
|
+
dispatch({ type: 'moveCursor', cursor: Math.min(buffer.length, cursor + step) });
|
|
505
521
|
return;
|
|
506
522
|
}
|
|
507
523
|
// `!key.shift` is load-bearing: Shift+↑/↓ is the viewport's line-scroll
|
|
@@ -539,6 +555,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
539
555
|
onNotice?.('warn', refusal);
|
|
540
556
|
return;
|
|
541
557
|
}
|
|
558
|
+
onInteraction?.();
|
|
542
559
|
dispatch({ type: 'input', segments });
|
|
543
560
|
return;
|
|
544
561
|
}
|
|
@@ -549,7 +566,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
549
566
|
const marker = running ? glyphs.steer : glyphs.caret;
|
|
550
567
|
const markerColor = running ? theme.toolRunning : theme.primary;
|
|
551
568
|
const placeholder = running
|
|
552
|
-
? `Type to steer the run, Esc to
|
|
569
|
+
? `Type to steer the run, Esc twice to interrupt${glyphs.ellipsis}`
|
|
553
570
|
: agentMode === 'plan'
|
|
554
571
|
? `Describe what you want to build; I'll research and plan it first${glyphs.ellipsis}`
|
|
555
572
|
: `Send a message (/ for commands, @ for files)${glyphs.ellipsis}`;
|
|
@@ -562,9 +579,9 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
562
579
|
// The caret is drawn as one inverse cell, split at the column
|
|
563
580
|
// `layoutComposer` reported — the same width table it wrapped with (I-9).
|
|
564
581
|
const split = splitRowAtColumn(row, layout.cursorCol);
|
|
565
|
-
return (_jsxs(_Fragment, { children: [renderSegments(split.before, `b${i}.`), _jsx(
|
|
582
|
+
return (_jsxs(_Fragment, { children: [renderSegments(split.before, `b${i}.`), _jsx(PromptCaret, { text: split.at ? split.at.text : ' ', active: isActive && cursorVisible, reducedMotion: reducedMotion, colorLevel: caps.colorLevel, resetKey: caretResetKey, color: split.at?.kind === 'token' ? tokenColor : undefined }), renderSegments(split.after, `a${i}.`)] }));
|
|
566
583
|
});
|
|
567
|
-
const inputRow = (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: markerColor, bold: true, children: [marker, ' '] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: buffer.length === 0 ? (
|
|
584
|
+
const inputRow = (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: markerColor, bold: true, children: [marker, ' '] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: buffer.length === 0 ? (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [_jsx(PromptCaret, { text: String.fromCodePoint(placeholder.codePointAt(0)), active: isActive && cursorVisible, reducedMotion: reducedMotion, colorLevel: caps.colorLevel, color: theme.muted, resetKey: caretResetKey }), placeholder.slice(String.fromCodePoint(placeholder.codePointAt(0)).length)] })) : (rowNodes.map((node, i) => overflow && i === rowNodes.length - 1 ? (
|
|
568
585
|
// The overflow indicator rides the LAST rendered row rather than
|
|
569
586
|
// taking one of its own: an extra row would put the composer's
|
|
570
587
|
// real height one above the `draftRows` it just reported (I-8).
|
|
@@ -579,5 +596,5 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
579
596
|
// the user is typing — `↓ 9` becomes `↓ 10` becomes `↓ 100` — and every
|
|
580
597
|
// change would otherwise re-wrap a draft that fills the line.
|
|
581
598
|
_jsx(Box, { flexShrink: 0, alignSelf: "flex-start", marginLeft: 1, width: chip.cells, justifyContent: "flex-end", children: _jsx(Text, { wrap: "truncate", color: theme.hintFg ?? theme.muted, children: chip.text }) }))] }));
|
|
582
|
-
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [popupVisible && (_jsx(AutocompletePopup, { items: popupItems, selected: clampedSel, theme: theme, caps: caps, maxRows: popupMaxRows, layout: popupLayout })),
|
|
599
|
+
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [popupVisible && (_jsx(AutocompletePopup, { items: popupItems, selected: clampedSel, theme: theme, caps: caps, maxRows: popupMaxRows, layout: popupLayout })), (_jsx(Box, { flexDirection: "column", flexShrink: 0, borderStyle: glyphs.boxStyle, borderColor: borderColor, paddingX: 1, children: inputRow }))] }));
|
|
583
600
|
}
|
package/dist/ui/StatusBar.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type { UsageTotal } from '../agent/reducer.js';
|
|
|
21
21
|
import type { ContextUsageSnapshot } from '../compaction/types.js';
|
|
22
22
|
import { type AgentMode } from '../agent/agent-mode.js';
|
|
23
23
|
interface StatusBarProps {
|
|
24
|
+
interruptHint?: string;
|
|
24
25
|
model: string;
|
|
25
26
|
provider: string;
|
|
26
27
|
usageTotal: UsageTotal;
|
|
@@ -51,17 +52,6 @@ interface StatusBarProps {
|
|
|
51
52
|
* Ctrl+L redraw carrier — invariant I-5 (spec §4.13). See `redrawChar`.
|
|
52
53
|
*/
|
|
53
54
|
redrawNonce?: number;
|
|
54
|
-
/**
|
|
55
|
-
* The session mode. THIS IS THE GUARANTEED MODE INDICATOR (plan-mode §6.4 /
|
|
56
|
-
* P1-6): the composer chip disappears on a short terminal, under
|
|
57
|
-
* `hints: false`, and in inline render mode, but `StatusBar` renders in both
|
|
58
|
-
* `AppShell` branches and its left cluster has no opt-out and no width gate.
|
|
59
|
-
*
|
|
60
|
-
* Design intent, stated here so it survives a later refactor: the status bar
|
|
61
|
-
* must ALWAYS name a non-default mode; the chip may. Do not give this a
|
|
62
|
-
* `cols >= N` breakpoint, and do not move it into the right cluster — those
|
|
63
|
-
* children are flexible and drop characters under pressure.
|
|
64
|
-
*/
|
|
65
55
|
agentMode?: AgentMode;
|
|
66
56
|
/** Non-null while a deferred `plan -> build` waits for `agent_end`. */
|
|
67
57
|
pendingAgentMode?: AgentMode | null;
|
package/dist/ui/StatusBar.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import stringWidth from 'string-width';
|
|
2
3
|
import { Box, Text, useStdout } from 'ink';
|
|
3
4
|
import { pickGlyphs } from './glyphs.js';
|
|
4
5
|
import { formatCost, formatTokens, formatDuration, promptTokensOf } from '../agent/usage.js';
|
|
@@ -95,6 +96,11 @@ export function StatusBar(props) {
|
|
|
95
96
|
: pendingAgentMode
|
|
96
97
|
? `${MODE_LABEL[agentMode]} ${glyphs.arrowRight} ${MODE_LABEL[pendingAgentMode]}`
|
|
97
98
|
: MODE_LABEL[agentMode];
|
|
99
|
+
const actionHints = [running ? (props.interruptHint ?? 'Esc abort') : '',
|
|
100
|
+
servicesActive ? `Ctrl+C stop ${servicesActive.live}` : '',
|
|
101
|
+
scrolledLines > 0 ? 'PgDn down' : ''].filter(Boolean).join(' | ');
|
|
102
|
+
const hintCols = actionHints ? Math.min(Math.max(0, cols - 1), stringWidth(actionHints) + 2) : 0;
|
|
103
|
+
const detailCols = Math.max(0, cols - 1 - hintCols);
|
|
98
104
|
return (
|
|
99
105
|
// `flexShrink={0}` on the left cluster is not cosmetic. When the two clusters
|
|
100
106
|
// over-subscribe the row, yoga shrinks the flexible children and Ink's text
|
|
@@ -102,11 +108,11 @@ export function StatusBar(props) {
|
|
|
102
108
|
// and "? hel" rather than truncating cleanly at one end. Pinning the left
|
|
103
109
|
// cluster keeps the run status and model intact and makes the degradation
|
|
104
110
|
// land in one predictable place.
|
|
105
|
-
_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", flexShrink: 0, children: [_jsxs(Box, { flexDirection: "row", flexShrink: 0, children: [_jsxs(Text, { color: running ? theme.toolRunning : theme.toolDone, children: [
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
_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
|
|
112
|
+
? `agents ${teamActive.running}/${teamActive.total}`
|
|
113
|
+
: `[${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
|
|
114
|
+
? `todo ${todoActive.done}/${todoActive.total}`
|
|
115
|
+
: `[${todoActive.done}/${todoActive.total}]`] })), servicesActive && (_jsxs(Text, { color: theme.accent, children: [' ', cols >= PROC_LIMITS.statusCompactCols
|
|
116
|
+
? `svc ${servicesActive.live}`
|
|
117
|
+
: `[${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)] }))] })] })] }));
|
|
112
118
|
}
|
package/dist/ui/ToastStack.d.ts
CHANGED
|
@@ -1,24 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Toast strip (spec §4.8). Ephemeral acks created by App/commands and
|
|
3
|
-
* auto-dismissed by App's TTL effect; the reducer stays timer-free.
|
|
4
|
-
*
|
|
5
|
-
* In `fullscreen` this ALWAYS occupies exactly one row — an empty row when there
|
|
6
|
-
* is nothing to say. Rendering conditionally would make the viewport height (and
|
|
7
|
-
* therefore the whole transcript) jump every time a toast appears or expires.
|
|
8
|
-
* There is deliberately no `rows` threshold: jitter is a layout property, not a
|
|
9
|
-
* space property, and a short terminal suffers it proportionally MORE, so the
|
|
10
|
-
* tier that most needs the fixed row is exactly the one a threshold would skip.
|
|
11
|
-
* Only the newest toast is shown; older ones collapse into a `+N` counter.
|
|
12
|
-
*
|
|
13
|
-
* `inline` keeps the v0.2.0 conditional 0–3 row stack: without a fixed frame,
|
|
14
|
-
* growing and shrinking is just normal document flow.
|
|
15
|
-
*/
|
|
1
|
+
/** Fixed one-row toast strip; only the newest toast is shown. */
|
|
16
2
|
import React from 'react';
|
|
17
3
|
import type { Theme } from './theme.js';
|
|
18
4
|
import type { Toast } from '../agent/reducer.js';
|
|
19
|
-
|
|
20
|
-
export declare function ToastStack({ toasts, theme, mode, }: {
|
|
5
|
+
export declare function ToastStack({ toasts, theme, }: {
|
|
21
6
|
toasts: Toast[];
|
|
22
7
|
theme: Theme;
|
|
23
|
-
mode?: RenderMode;
|
|
24
8
|
}): React.ReactElement | null;
|
package/dist/ui/ToastStack.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
const MAX_VISIBLE = 3;
|
|
4
3
|
function toastColor(level, theme) {
|
|
5
4
|
switch (level) {
|
|
6
5
|
case 'success':
|
|
@@ -25,14 +24,8 @@ function toastGlyph(level, theme) {
|
|
|
25
24
|
return theme.symbols.info;
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
|
-
export function ToastStack({ toasts, theme,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return (_jsx(Box, { flexShrink: 0, children: latest ? (_jsxs(Text, { wrap: "truncate", color: toastColor(latest.level, theme), children: [' ', toastGlyph(latest.level, theme), " ", latest.text, extra > 0 ? ` +${extra}` : ''] })) : (_jsx(Text, { children: " " })) }));
|
|
33
|
-
}
|
|
34
|
-
const shown = toasts.slice(-MAX_VISIBLE);
|
|
35
|
-
if (shown.length === 0)
|
|
36
|
-
return null;
|
|
37
|
-
return (_jsx(Box, { flexDirection: "column", marginTop: 1, children: shown.map((t) => (_jsxs(Text, { color: toastColor(t.level, theme), children: [toastGlyph(t.level, theme), " ", t.text] }, t.id))) }));
|
|
27
|
+
export function ToastStack({ toasts, theme, }) {
|
|
28
|
+
const latest = toasts[toasts.length - 1];
|
|
29
|
+
const extra = Math.max(0, toasts.length - 1);
|
|
30
|
+
return (_jsx(Box, { flexShrink: 0, children: latest ? (_jsxs(Text, { wrap: "truncate", color: toastColor(latest.level, theme), children: [' ', toastGlyph(latest.level, theme), " ", latest.text, extra > 0 ? ` +${extra}` : ''] })) : (_jsx(Text, { children: " " })) }));
|
|
38
31
|
}
|
package/dist/ui/Transcript.d.ts
CHANGED
|
@@ -1,65 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* once into Ink's `<Static>` (native terminal scrollback, never re-rendered)
|
|
4
|
-
* and a live tail rendered normally. This kills per-token full re-renders and
|
|
5
|
-
* flicker on long/fast sessions.
|
|
6
|
-
*
|
|
7
|
-
* `computeSettledCount` is pure and exported for `transcript-static.test.ts`.
|
|
8
|
-
* The settled boundary is held MONOTONIC at render time: a previously-settled
|
|
9
|
-
* entry never leaves `<Static>` (which cannot un-print it) even if it later
|
|
10
|
-
* becomes expanded, so the live tail can never duplicate a scrolled-off entry.
|
|
11
|
-
*
|
|
12
|
-
* `EntryView` is shared by both render paths ON PURPOSE (§4.3 / §12). Forking it
|
|
13
|
-
* to freeze inline's v0.2.0 output would mean writing every future entry-layer
|
|
14
|
-
* change twice and watching the two drift; inline is a degradation path, not a
|
|
15
|
-
* parallel product. What inline still guarantees is its GEOMETRY contract — no
|
|
16
|
-
* fixed frame, `<Static>` keeps the settled history, no private ANSI — not its
|
|
17
|
-
* pixel-for-pixel appearance.
|
|
18
|
-
*
|
|
19
|
-
* Two things changed with tui-render-performance:
|
|
20
|
-
* - `EntryView` is `React.memo`'d with an explicit comparator (L2 / R3), which
|
|
21
|
-
* is only worth anything because `mapEntry` already preserves object
|
|
22
|
-
* identity for untouched entries;
|
|
23
|
-
* - `TranscriptList` (the FULL-SCREEN body) mounts only the entries inside the
|
|
24
|
-
* viewport and replaces the rest with two spacer boxes (L3 / R1), and the
|
|
25
|
-
* inline `Transcript` clamps its live region so `ink.js:121` is unreachable
|
|
26
|
-
* (L5 / R6).
|
|
27
|
-
*/
|
|
1
|
+
/** Virtualized full-screen transcript and entry cards. */
|
|
2
|
+
/** Virtualized full-screen transcript and shared entry cards. */
|
|
28
3
|
import React from 'react';
|
|
29
4
|
import { type Theme } from './theme.js';
|
|
30
5
|
import type { TermCapabilities } from './capabilities.js';
|
|
31
6
|
import { type DensityMode } from './density.js';
|
|
32
7
|
import type { Entry } from '../agent/reducer.js';
|
|
33
|
-
import type { RenderMode } from './layout/frame.js';
|
|
34
8
|
import { type TailSink } from './layout/follow-state.js';
|
|
35
9
|
import type { HeightStore } from './use-height-store.js';
|
|
36
|
-
/**
|
|
37
|
-
* Largest prefix length P such that `entries[0..P)` are all terminal, none is
|
|
38
|
-
* within the last `LIVE_TAIL` entries, and none is currently expanded. Pure and
|
|
39
|
-
* non-decreasing as entries terminalize (expanding a card can lower it — the
|
|
40
|
-
* component clamps with a monotonic high-water mark to avoid Static duplication).
|
|
41
|
-
*/
|
|
42
|
-
export declare function computeSettledCount(entries: Entry[], expandedToolIds: Record<string, true>): number;
|
|
43
10
|
interface TranscriptProps {
|
|
44
11
|
entries: Entry[];
|
|
45
12
|
expandedToolIds: Record<string, true>;
|
|
46
13
|
thinkingVisible: boolean;
|
|
47
14
|
reducedMotion: boolean;
|
|
48
15
|
density: DensityMode;
|
|
49
|
-
/**
|
|
50
|
-
* The render mode, threaded from `App` as a plain scalar prop.
|
|
51
|
-
*
|
|
52
|
-
* It decides whether the collapsed thinking marker may offer `ctrl+t` (D-16):
|
|
53
|
-
* inline prints settled entries into `<Static>`, which cannot re-print them.
|
|
54
|
-
* NOT read from a module-level singleton — `render-memo.test.tsx` asserts prop
|
|
55
|
-
* stability across frames, and a hidden read would be invisible to it.
|
|
56
|
-
*/
|
|
57
|
-
mode: RenderMode;
|
|
58
16
|
/**
|
|
59
17
|
* Wall-clock seconds, threaded from `App`'s existing 200 ms ticker, for the
|
|
60
18
|
* live tool card's stall row (agent-activity-presentation-live §3.3.4 / D-36).
|
|
61
19
|
*
|
|
62
|
-
*
|
|
20
|
+
* The entry map decides per entry whether to pass it on, and it
|
|
63
21
|
* may pass it unconditionally: `ToolCard` is `React.memo` with the default
|
|
64
22
|
* comparator, so a prop that changes every second on every card would defeat
|
|
65
23
|
* that boundary for the whole transcript.
|
|
@@ -76,15 +34,12 @@ export interface EntryViewProps {
|
|
|
76
34
|
thinkingVisible: boolean;
|
|
77
35
|
reducedMotion: boolean;
|
|
78
36
|
density: DensityMode;
|
|
79
|
-
mode: RenderMode;
|
|
80
37
|
theme: Theme;
|
|
81
38
|
caps: TermCapabilities;
|
|
82
|
-
/** Inline mode only (L5); `undefined` in full-screen, which means no clamp. */
|
|
83
|
-
liveClampRows?: number;
|
|
84
39
|
/** Wall-clock seconds; set ONLY on a running tool entry (§3.3.4 / P1-3). */
|
|
85
40
|
nowSec?: number;
|
|
86
41
|
}
|
|
87
|
-
declare function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reducedMotion, density,
|
|
42
|
+
declare function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reducedMotion, density, theme, caps, nowSec, }: EntryViewProps): React.ReactElement | null;
|
|
88
43
|
/**
|
|
89
44
|
* THE memo boundary that makes a long transcript cheap (L2 / R3).
|
|
90
45
|
*
|
|
@@ -100,27 +55,7 @@ declare function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reduced
|
|
|
100
55
|
* test rather than a silent regression.
|
|
101
56
|
*/
|
|
102
57
|
export declare const EntryView: React.MemoExoticComponent<typeof EntryViewImpl>;
|
|
103
|
-
|
|
104
|
-
* Full-screen transcript body — the same entry renderers with NO `<Static>`.
|
|
105
|
-
*
|
|
106
|
-
* `<Static>` prints above the live frame; once the frame is `rows - 1` tall
|
|
107
|
-
* there is exactly one visible line up there, so the mechanism stops being a
|
|
108
|
-
* history view and starts being a leak. Full-screen therefore owns its history
|
|
109
|
-
* inside `ScrollViewport` instead.
|
|
110
|
-
*
|
|
111
|
-
* `computeSettledCount` is deliberately NOT called here: its only job is to keep
|
|
112
|
-
* an entry that already reached `<Static>` from flowing back into the live
|
|
113
|
-
* region, and with no Static there is nothing to protect. The settled high-water
|
|
114
|
-
* mark feeds nothing else — `/save` serializes `entries` in full — so leaving it
|
|
115
|
-
* un-advanced has no side effect.
|
|
116
|
-
*
|
|
117
|
-
* VIRTUALISED (L3 / R1). `windowSize` is now the SCROLL HORIZON — how far back
|
|
118
|
-
* the user can scroll — and no longer the rendering budget, because entries
|
|
119
|
-
* outside the viewport are not mounted at all. What bounds the frame is
|
|
120
|
-
* `selectWindow`, and the two spacers are what keep the scroll maths honest
|
|
121
|
-
* while they are absent.
|
|
122
|
-
*/
|
|
123
|
-
export declare function TranscriptList({ entries, expandedToolIds, thinkingVisible, reducedMotion, density, mode, nowSec, theme, caps, windowSize, cols, heights, mountedSink, tailSink, }: TranscriptProps & {
|
|
58
|
+
export declare function TranscriptList({ entries, expandedToolIds, thinkingVisible, reducedMotion, density, nowSec, theme, caps, windowSize, cols, heights, mountedSink, tailSink, }: TranscriptProps & {
|
|
124
59
|
windowSize: number;
|
|
125
60
|
/** The viewport's own width — narrower than the terminal when the rail is up. */
|
|
126
61
|
cols: number;
|
|
@@ -153,5 +88,4 @@ export declare function TranscriptList({ entries, expandedToolIds, thinkingVisib
|
|
|
153
88
|
*/
|
|
154
89
|
tailSink?: TailSink;
|
|
155
90
|
}): React.ReactElement;
|
|
156
|
-
export declare function Transcript({ entries, expandedToolIds, thinkingVisible, reducedMotion, density, mode, nowSec, theme, caps, }: TranscriptProps): React.ReactElement;
|
|
157
91
|
export {};
|