@aragon-agent/cli 0.5.8 → 0.5.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 +279 -0
- package/README.md +1429 -1163
- package/dist/agent/coalesce.d.ts +13 -0
- package/dist/agent/coalesce.js +50 -11
- package/dist/agent/controller.d.ts +141 -2
- package/dist/agent/controller.js +396 -4
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +40 -0
- package/dist/agent/reducer.d.ts +245 -3
- package/dist/agent/reducer.js +490 -17
- package/dist/agent/retry-view.d.ts +68 -0
- package/dist/agent/retry-view.js +95 -0
- package/dist/agent/system-prompt.d.ts +11 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +86 -2
- package/dist/commands/builtins.d.ts +1 -1
- package/dist/commands/builtins.js +308 -2
- package/dist/config/cli-commands.d.ts +53 -2
- package/dist/config/cli-commands.js +136 -3
- package/dist/config/env.d.ts +6 -3
- package/dist/config/env.js +103 -3
- package/dist/config/load.d.ts +49 -0
- package/dist/config/load.js +83 -1
- package/dist/config/schema.d.ts +212 -2
- package/dist/config/schema.js +207 -1
- package/dist/config/store.js +20 -1
- package/dist/fast/critique.d.ts +43 -0
- package/dist/fast/critique.js +62 -0
- package/dist/fast/digest.d.ts +42 -0
- package/dist/fast/digest.js +85 -0
- package/dist/fast/frames.d.ts +99 -0
- package/dist/fast/frames.js +180 -0
- package/dist/fast/limits.d.ts +98 -0
- package/dist/fast/limits.js +98 -0
- package/dist/fast/prompt.d.ts +72 -0
- package/dist/fast/prompt.js +94 -0
- package/dist/fast/resolve.d.ts +43 -0
- package/dist/fast/resolve.js +105 -0
- package/dist/fast/review-call.d.ts +99 -0
- package/dist/fast/review-call.js +130 -0
- package/dist/fast/reviewer.d.ts +248 -0
- package/dist/fast/reviewer.js +664 -0
- package/dist/fast/types.d.ts +128 -0
- package/dist/fast/types.js +14 -0
- package/dist/fast/wiring.d.ts +170 -0
- package/dist/fast/wiring.js +316 -0
- package/dist/logging/install.d.ts +15 -1
- package/dist/logging/install.js +37 -4
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/persist.js +57 -1
- package/dist/team/normalize.d.ts +26 -1
- package/dist/team/normalize.js +23 -3
- package/dist/team/prompt.d.ts +11 -0
- package/dist/team/prompt.js +10 -0
- package/dist/team/report.d.ts +18 -0
- package/dist/team/report.js +27 -1
- package/dist/team/runtime.d.ts +13 -2
- package/dist/team/runtime.js +33 -6
- package/dist/team/subagent.d.ts +19 -1
- package/dist/team/subagent.js +72 -10
- package/dist/team/task-tool.d.ts +26 -0
- package/dist/team/task-tool.js +48 -4
- package/dist/team/types.d.ts +80 -1
- package/dist/tools/bash-tool.js +19 -3
- package/dist/tools/diff.d.ts +25 -5
- package/dist/tools/diff.js +72 -41
- package/dist/tools/file-change-store.d.ts +34 -0
- package/dist/tools/file-change-store.js +57 -0
- package/dist/tools/fs-tools.d.ts +26 -0
- package/dist/tools/fs-tools.js +62 -5
- package/dist/tools/index.d.ts +31 -0
- package/dist/tools/index.js +7 -1
- package/dist/tools/patch.d.ts +115 -0
- package/dist/tools/patch.js +453 -0
- package/dist/tools/terminal-output.d.ts +107 -0
- package/dist/tools/terminal-output.js +198 -0
- package/dist/tools/tool-output-store.d.ts +68 -0
- package/dist/tools/tool-output-store.js +102 -0
- package/dist/ui/ActivityLine.d.ts +48 -0
- package/dist/ui/ActivityLine.js +22 -0
- package/dist/ui/App.d.ts +1 -4
- package/dist/ui/App.js +273 -37
- package/dist/ui/BottomStatusRow.d.ts +43 -0
- package/dist/ui/BottomStatusRow.js +14 -0
- package/dist/ui/Composer.d.ts +1 -6
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/PromptInput.d.ts +1 -15
- package/dist/ui/PromptInput.js +1 -38
- package/dist/ui/StatusBar.d.ts +37 -0
- package/dist/ui/StatusBar.js +5 -3
- package/dist/ui/TeamPanel.d.ts +5 -4
- package/dist/ui/TeamPanel.js +1 -1
- package/dist/ui/TodoPanel.d.ts +5 -4
- package/dist/ui/Transcript.d.ts +26 -3
- package/dist/ui/Transcript.js +76 -10
- package/dist/ui/activity-phrases.d.ts +33 -0
- package/dist/ui/activity-phrases.js +50 -0
- package/dist/ui/entries/AssistantEntry.d.ts +20 -0
- package/dist/ui/entries/AssistantEntry.js +3 -2
- package/dist/ui/entries/DiffView.d.ts +67 -0
- package/dist/ui/entries/DiffView.js +103 -0
- package/dist/ui/entries/FastCard.d.ts +53 -0
- package/dist/ui/entries/FastCard.js +87 -0
- package/dist/ui/entries/RetryCard.d.ts +56 -0
- package/dist/ui/entries/RetryCard.js +60 -0
- package/dist/ui/entries/TeamCard.js +6 -1
- package/dist/ui/entries/ToolCard.d.ts +38 -1
- package/dist/ui/entries/ToolCard.js +68 -5
- package/dist/ui/glyphs.d.ts +9 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/AppShell.d.ts +13 -22
- package/dist/ui/layout/AppShell.js +3 -38
- package/dist/ui/layout/virtual-window.d.ts +1 -1
- package/dist/ui/layout/virtual-window.js +109 -5
- package/dist/ui/overlays/HelpOverlay.js +16 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +82 -1
- package/dist/ui/overlays/SettingsScreen.js +147 -1
- package/dist/ui/transcript-text.js +27 -0
- package/dist/ui/use-wheel-routing.d.ts +16 -32
- package/dist/ui/use-wheel-routing.js +21 -38
- package/package.json +2 -2
- package/dist/ui/layout/regions.d.ts +0 -55
- package/dist/ui/layout/regions.js +0 -52
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A `FilePatch` as a diff (agent-activity-presentation §3.4): a `+6 -5` summary,
|
|
4
|
+
* then hunk-separated rows with a right-aligned line-number gutter, a `+`/`-`/
|
|
5
|
+
* space sign column and `theme.diff` colors.
|
|
6
|
+
*
|
|
7
|
+
* `wrap="truncate"` ON EVERY ROW IS A CORRECTNESS REQUIREMENT, NOT A TASTE ONE.
|
|
8
|
+
* `estimateEntryRows` computes a diff card's height by COUNTING rows, so a
|
|
9
|
+
* wrapped patch line would occupy two rows while the estimate charged one — an
|
|
10
|
+
* under-estimate, which `virtual-window.ts` names as the unsafe direction. One
|
|
11
|
+
* patch line is one terminal row, always.
|
|
12
|
+
*
|
|
13
|
+
* No new keybinding and no new state: collapse rides on `expandedToolIds` and
|
|
14
|
+
* the existing `Ctrl+O` handler, which targets the MOST RECENT tool card (P2-4).
|
|
15
|
+
*
|
|
16
|
+
* `caps` is deliberately NOT a prop. Every character this component emits is
|
|
17
|
+
* ASCII — `+`, `-`, `@@`, digits — because `+` and `-` are the diff's own
|
|
18
|
+
* vocabulary and `renderUnifiedDiff` already spells them literally. A glyph
|
|
19
|
+
* lookup here would buy nothing and add a memo dependency.
|
|
20
|
+
*/
|
|
21
|
+
import React from 'react';
|
|
22
|
+
import { Box, Text } from 'ink';
|
|
23
|
+
/**
|
|
24
|
+
* Diff rows shown before collapse.
|
|
25
|
+
*
|
|
26
|
+
* Higher than the generic preview's 8 (D-11): a diff's rows are half as tall in
|
|
27
|
+
* information terms, and 8 rows of a 3-context hunk is barely one change.
|
|
28
|
+
* DUPLICATED AS A NUMBER in `virtual-window.ts`, matching what
|
|
29
|
+
* `TOOL_COLLAPSED_LINES` already does and the comment that justifies it.
|
|
30
|
+
*/
|
|
31
|
+
export const DIFF_COLLAPSED_LINES = 12;
|
|
32
|
+
/** Gutter width bounds; three digits is the floor so narrow files still align. */
|
|
33
|
+
const GUTTER_MIN = 3;
|
|
34
|
+
const GUTTER_MAX = 6;
|
|
35
|
+
/**
|
|
36
|
+
* Flatten the hunks into rows, inserting one `@@` separator BETWEEN hunks and
|
|
37
|
+
* none before the first.
|
|
38
|
+
*
|
|
39
|
+
* Exported for `diff-view.test.tsx`, and because the row count it produces is
|
|
40
|
+
* the number `estimateEntryRows` reproduces arithmetically — the two must not
|
|
41
|
+
* disagree.
|
|
42
|
+
*/
|
|
43
|
+
export function diffRows(patch) {
|
|
44
|
+
const rows = [];
|
|
45
|
+
patch.hunks.forEach((hunk, index) => {
|
|
46
|
+
if (index > 0) {
|
|
47
|
+
rows.push({
|
|
48
|
+
kind: 'meta',
|
|
49
|
+
text: `@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
for (const line of hunk.lines) {
|
|
53
|
+
rows.push({
|
|
54
|
+
kind: line.kind,
|
|
55
|
+
// An `add` shows its new number, a `del` its old, a `ctx` its new.
|
|
56
|
+
...(pickNumber(line) !== undefined ? { num: pickNumber(line) } : {}),
|
|
57
|
+
text: line.text,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return rows;
|
|
62
|
+
}
|
|
63
|
+
function pickNumber(line) {
|
|
64
|
+
return line.kind === 'del' ? line.oldLine : line.newLine ?? line.oldLine;
|
|
65
|
+
}
|
|
66
|
+
function signOf(kind) {
|
|
67
|
+
if (kind === 'add')
|
|
68
|
+
return '+';
|
|
69
|
+
if (kind === 'del')
|
|
70
|
+
return '-';
|
|
71
|
+
return ' ';
|
|
72
|
+
}
|
|
73
|
+
function colorOf(kind, theme) {
|
|
74
|
+
if (kind === 'add')
|
|
75
|
+
return theme.diff.add;
|
|
76
|
+
if (kind === 'del')
|
|
77
|
+
return theme.diff.remove;
|
|
78
|
+
if (kind === 'meta')
|
|
79
|
+
return theme.diff.meta;
|
|
80
|
+
return theme.diff.context;
|
|
81
|
+
}
|
|
82
|
+
function gutterWidth(rows) {
|
|
83
|
+
let max = 0;
|
|
84
|
+
for (const row of rows)
|
|
85
|
+
if (row.num !== undefined && row.num > max)
|
|
86
|
+
max = row.num;
|
|
87
|
+
return Math.min(GUTTER_MAX, Math.max(GUTTER_MIN, String(max).length));
|
|
88
|
+
}
|
|
89
|
+
function DiffViewImpl({ patch, expanded, liveClampRows, theme, }) {
|
|
90
|
+
const rows = diffRows(patch);
|
|
91
|
+
const ceiling = Math.min(expanded ? rows.length : DIFF_COLLAPSED_LINES, liveClampRows ?? Number.POSITIVE_INFINITY);
|
|
92
|
+
const visible = ceiling >= rows.length ? rows : rows.slice(0, Math.max(0, ceiling));
|
|
93
|
+
const hidden = rows.length - visible.length;
|
|
94
|
+
const width = gutterWidth(rows);
|
|
95
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: theme.diff.add, children: ["+", patch.added] }), _jsx(Text, { color: theme.muted, children: " " }), _jsxs(Text, { color: theme.diff.remove, children: ["-", patch.removed] }), patch.truncated && _jsx(Text, { color: theme.muted, children: " (truncated)" }), patch.degraded && (_jsxs(Text, { color: theme.muted, children: [" old side not read: ", patch.degraded] }))] }), visible.map((row, i) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: theme.muted, children: String(row.num ?? '').padStart(width) }), _jsxs(Text, { color: colorOf(row.kind, theme), children: [' ', signOf(row.kind), " ", row.text] })] }, i))), hidden > 0 ? (_jsxs(Text, { color: theme.muted, children: ["+", hidden, " ", hidden === 1 ? 'line' : 'lines', " (Ctrl+O)"] })) : (rows.length > DIFF_COLLAPSED_LINES && _jsx(Text, { color: theme.muted, children: "(Ctrl+O to collapse)" }))] }));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* `React.memo` with the DEFAULT comparator, matching `ToolCard`'s boundary.
|
|
99
|
+
* `patch` qualifies as a prop only because the reducer writes it ONCE at
|
|
100
|
+
* `toolExecEnd` and never rebuilds it (P2-9); `theme` is `useMemo`d in `App.tsx`
|
|
101
|
+
* (I-L2-1).
|
|
102
|
+
*/
|
|
103
|
+
export const DiffView = React.memo(DiffViewImpl);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FastCard — one fast-model review in the transcript (fast-model-tier §6).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY: every glyph comes from `pickGlyphs`.
|
|
5
|
+
*
|
|
6
|
+
* SINGLE AND QUIET, NEVER MORE THAN SIX ROWS. It renders through the shared
|
|
7
|
+
* `EntryFrame` supplied by `EntryView`, so a review reads as one more step in
|
|
8
|
+
* the transcript rather than a second kind of object.
|
|
9
|
+
*
|
|
10
|
+
* COLLAPSED BY CONSTRUCTION rather than by an expand toggle: the critique is
|
|
11
|
+
* already clamped to `fast.reviewMaxChars` before it ever reaches here, so there
|
|
12
|
+
* is nothing hidden for a `Ctrl+O` to reveal.
|
|
13
|
+
*
|
|
14
|
+
* `ok`, `failed` and `dropped` each render ONE muted line. `ok` is the common
|
|
15
|
+
* case and must stay visually cheap — a full card every few turns saying
|
|
16
|
+
* "nothing to report" would be the feature's most annoying possible surface —
|
|
17
|
+
* while `failed` and `dropped` exist so that "it did nothing" is never the
|
|
18
|
+
* observable outcome (R-8).
|
|
19
|
+
*/
|
|
20
|
+
import React from 'react';
|
|
21
|
+
import type { Theme } from '../theme.js';
|
|
22
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
23
|
+
export type FastCardStatus = 'running' | 'ok' | 'advice' | 'empty' | 'failed' | 'dropped';
|
|
24
|
+
export interface FastCardProps {
|
|
25
|
+
reviewIndex: number;
|
|
26
|
+
model: string;
|
|
27
|
+
status: FastCardStatus;
|
|
28
|
+
text?: string;
|
|
29
|
+
detail?: string;
|
|
30
|
+
turn: number;
|
|
31
|
+
durationMs?: number;
|
|
32
|
+
live: boolean;
|
|
33
|
+
reducedMotion?: boolean;
|
|
34
|
+
theme: Theme;
|
|
35
|
+
caps: TermCapabilities;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The card's colour.
|
|
39
|
+
*
|
|
40
|
+
* `advice` is the ACCENT rather than a warning colour: a second opinion is not
|
|
41
|
+
* an error, and colouring it like one would teach the user to read every review
|
|
42
|
+
* as something having gone wrong.
|
|
43
|
+
*/
|
|
44
|
+
export declare function fastCardColor(status: FastCardStatus, theme: Theme): string | undefined;
|
|
45
|
+
declare function FastCardImpl(props: FastCardProps): React.ReactElement;
|
|
46
|
+
/**
|
|
47
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
48
|
+
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
49
|
+
* props below are stable references on a settled card, and `theme` / `caps` are
|
|
50
|
+
* `useMemo`d in `App.tsx` (I-L2-1).
|
|
51
|
+
*/
|
|
52
|
+
export declare const FastCard: React.MemoExoticComponent<typeof FastCardImpl>;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* FastCard — one fast-model review in the transcript (fast-model-tier §6).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: every glyph comes from `pickGlyphs`.
|
|
6
|
+
*
|
|
7
|
+
* SINGLE AND QUIET, NEVER MORE THAN SIX ROWS. It renders through the shared
|
|
8
|
+
* `EntryFrame` supplied by `EntryView`, so a review reads as one more step in
|
|
9
|
+
* the transcript rather than a second kind of object.
|
|
10
|
+
*
|
|
11
|
+
* COLLAPSED BY CONSTRUCTION rather than by an expand toggle: the critique is
|
|
12
|
+
* already clamped to `fast.reviewMaxChars` before it ever reaches here, so there
|
|
13
|
+
* is nothing hidden for a `Ctrl+O` to reveal.
|
|
14
|
+
*
|
|
15
|
+
* `ok`, `failed` and `dropped` each render ONE muted line. `ok` is the common
|
|
16
|
+
* case and must stay visually cheap — a full card every few turns saying
|
|
17
|
+
* "nothing to report" would be the feature's most annoying possible surface —
|
|
18
|
+
* while `failed` and `dropped` exist so that "it did nothing" is never the
|
|
19
|
+
* observable outcome (R-8).
|
|
20
|
+
*/
|
|
21
|
+
import React from 'react';
|
|
22
|
+
import { Box, Text } from 'ink';
|
|
23
|
+
import Spinner from 'ink-spinner';
|
|
24
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
25
|
+
import { railBorderProps } from '../layout/Gutter.js';
|
|
26
|
+
import { formatDuration } from '../../agent/usage.js';
|
|
27
|
+
import { FAST_LIMITS } from '../../fast/limits.js';
|
|
28
|
+
/**
|
|
29
|
+
* The card's colour.
|
|
30
|
+
*
|
|
31
|
+
* `advice` is the ACCENT rather than a warning colour: a second opinion is not
|
|
32
|
+
* an error, and colouring it like one would teach the user to read every review
|
|
33
|
+
* as something having gone wrong.
|
|
34
|
+
*/
|
|
35
|
+
export function fastCardColor(status, theme) {
|
|
36
|
+
if (status === 'running')
|
|
37
|
+
return theme.toolRunning;
|
|
38
|
+
if (status === 'advice')
|
|
39
|
+
return theme.accent;
|
|
40
|
+
if (status === 'failed')
|
|
41
|
+
return theme.toolError;
|
|
42
|
+
return theme.muted;
|
|
43
|
+
}
|
|
44
|
+
/** The one-line summary that follows the header on every non-advice status. */
|
|
45
|
+
function statusLine(props) {
|
|
46
|
+
switch (props.status) {
|
|
47
|
+
case 'running':
|
|
48
|
+
return 'reviewing...';
|
|
49
|
+
case 'ok':
|
|
50
|
+
return 'on track';
|
|
51
|
+
case 'empty':
|
|
52
|
+
return 'no answer';
|
|
53
|
+
case 'failed':
|
|
54
|
+
return props.detail ? `failed: ${props.detail}` : 'failed';
|
|
55
|
+
case 'dropped':
|
|
56
|
+
return props.detail ? `dropped: ${props.detail}` : 'dropped';
|
|
57
|
+
default:
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Split the critique into at most `FAST_LIMITS.cardTextRows` lines.
|
|
63
|
+
*
|
|
64
|
+
* The clamp is a ROW budget, not a character one: `reviewMaxChars` already
|
|
65
|
+
* bounded the text, and this only stops a critique with no whitespace at all
|
|
66
|
+
* from turning into a wall on a narrow terminal. Ink wraps the rest.
|
|
67
|
+
*/
|
|
68
|
+
function textRows(text) {
|
|
69
|
+
const lines = text.split('\n').filter((l) => l.trim().length > 0);
|
|
70
|
+
return lines.slice(0, FAST_LIMITS.cardTextRows);
|
|
71
|
+
}
|
|
72
|
+
function FastCardImpl(props) {
|
|
73
|
+
const { reviewIndex, model, status, text, turn, durationMs, live, reducedMotion, theme, caps } = props;
|
|
74
|
+
const glyphs = pickGlyphs(caps);
|
|
75
|
+
const color = fastCardColor(status, theme);
|
|
76
|
+
const duration = durationMs === undefined ? '' : ` ${glyphs.midDot} ${formatDuration(durationMs)}`;
|
|
77
|
+
const head = `fast review #${reviewIndex} ${glyphs.midDot} ${model} ${glyphs.midDot} turn ${turn}${duration}`;
|
|
78
|
+
const showBody = status === 'advice' && text !== undefined && text.trim().length > 0;
|
|
79
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.accent, bold: true, children: "fast" }), live && !reducedMotion && caps.unicode ? (_jsxs(Text, { color: color, children: [' ', _jsx(Spinner, { type: "dots" }), " ", head] })) : (_jsxs(Text, { color: theme.muted, wrap: "truncate", children: [' ', live ? `${glyphs.spinnerStill} ` : '', head] }))] }), showBody ? (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, ...railBorderProps(glyphs.railVertical, theme.border), children: textRows(text).map((line, index) => (_jsx(Text, { color: theme.primary, children: line }, index))) })) : (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, children: _jsx(Text, { color: color, wrap: "truncate", children: statusLine(props) }) }))] }));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
83
|
+
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
84
|
+
* props below are stable references on a settled card, and `theme` / `caps` are
|
|
85
|
+
* `useMemo`d in `App.tsx` (I-L2-1).
|
|
86
|
+
*/
|
|
87
|
+
export const FastCard = React.memo(FastCardImpl);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RetryCard — the one-line retry entry (llm-api-retry-backoff §6.5).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY: every glyph comes from `pickGlyphs`, and the line itself is built by
|
|
5
|
+
* the pure `formatRetryLine` in `agent/retry-view.ts` so it can be asserted
|
|
6
|
+
* without a terminal.
|
|
7
|
+
*
|
|
8
|
+
* ONE LINE. ALWAYS ONE LINE. Ten retries rendered as ten notices would bury the
|
|
9
|
+
* transcript in the one moment the user most needs it legible, so this card is
|
|
10
|
+
* rewritten in place for the whole turn and settles into a single row of history.
|
|
11
|
+
* Amber while in flight, muted once recovered, red once the ladder is exhausted.
|
|
12
|
+
*/
|
|
13
|
+
import React from 'react';
|
|
14
|
+
import type { Theme } from '../theme.js';
|
|
15
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
16
|
+
import { type RetryPhase } from '../../agent/retry-view.js';
|
|
17
|
+
export interface RetryCardProps {
|
|
18
|
+
attempt: number;
|
|
19
|
+
maxRetries: number;
|
|
20
|
+
errorType: string;
|
|
21
|
+
phase: RetryPhase;
|
|
22
|
+
resumeAt?: number;
|
|
23
|
+
totalRetries?: number;
|
|
24
|
+
elapsedMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* `Date.now()` at render, injected so the component stays testable.
|
|
27
|
+
*
|
|
28
|
+
* The countdown is recomputed from `resumeAt - now` on every frame; `App` runs a
|
|
29
|
+
* 1 Hz tick ONLY while the phase is `waiting`, so an idle session has no timer.
|
|
30
|
+
*/
|
|
31
|
+
now: number;
|
|
32
|
+
/**
|
|
33
|
+
* Whether Esc would actually be felt. `App` passes `true` only for an
|
|
34
|
+
* interactive session, because the hint has to be the truth: the wait is
|
|
35
|
+
* signal-aware in `withRetry`, so Esc lands within a frame — but there is no Esc
|
|
36
|
+
* in headless mode to promise.
|
|
37
|
+
*/
|
|
38
|
+
interactive?: boolean;
|
|
39
|
+
reducedMotion?: boolean;
|
|
40
|
+
theme: Theme;
|
|
41
|
+
caps: TermCapabilities;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The card's tone. `exhausted` is the only failure: an `interrupted` card records
|
|
45
|
+
* a user decision, and colouring that red would blame them for it.
|
|
46
|
+
*/
|
|
47
|
+
export declare function retryCardColor(phase: RetryPhase, theme: Theme): string | undefined;
|
|
48
|
+
declare function RetryCardImpl(props: RetryCardProps): React.ReactElement;
|
|
49
|
+
/**
|
|
50
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
51
|
+
* `mapEntry` preserves object identity for untouched entries, and `theme` / `caps`
|
|
52
|
+
* are `useMemo`d in `App.tsx` (I-L2-1). `now` changes once a second while the card
|
|
53
|
+
* is waiting, which is exactly when it must re-render.
|
|
54
|
+
*/
|
|
55
|
+
export declare const RetryCard: React.MemoExoticComponent<typeof RetryCardImpl>;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* RetryCard — the one-line retry entry (llm-api-retry-backoff §6.5).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: every glyph comes from `pickGlyphs`, and the line itself is built by
|
|
6
|
+
* the pure `formatRetryLine` in `agent/retry-view.ts` so it can be asserted
|
|
7
|
+
* without a terminal.
|
|
8
|
+
*
|
|
9
|
+
* ONE LINE. ALWAYS ONE LINE. Ten retries rendered as ten notices would bury the
|
|
10
|
+
* transcript in the one moment the user most needs it legible, so this card is
|
|
11
|
+
* rewritten in place for the whole turn and settles into a single row of history.
|
|
12
|
+
* Amber while in flight, muted once recovered, red once the ladder is exhausted.
|
|
13
|
+
*/
|
|
14
|
+
import React from 'react';
|
|
15
|
+
import { Box, Text } from 'ink';
|
|
16
|
+
import Spinner from 'ink-spinner';
|
|
17
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
18
|
+
import { formatRetryLine } from '../../agent/retry-view.js';
|
|
19
|
+
/**
|
|
20
|
+
* The card's tone. `exhausted` is the only failure: an `interrupted` card records
|
|
21
|
+
* a user decision, and colouring that red would blame them for it.
|
|
22
|
+
*/
|
|
23
|
+
export function retryCardColor(phase, theme) {
|
|
24
|
+
if (phase === 'waiting' || phase === 'retrying')
|
|
25
|
+
return theme.noticeWarn;
|
|
26
|
+
if (phase === 'exhausted')
|
|
27
|
+
return theme.noticeError;
|
|
28
|
+
return theme.muted;
|
|
29
|
+
}
|
|
30
|
+
function RetryCardImpl(props) {
|
|
31
|
+
const { attempt, maxRetries, errorType, phase, resumeAt, totalRetries, elapsedMs, now,
|
|
32
|
+
// DEFAULTS TO TRUE: reaching this component means a TUI is mounted, and the
|
|
33
|
+
// TUI always binds Esc to abort. The prop exists so headless-shaped callers
|
|
34
|
+
// and the render tests can assert the suppressed form.
|
|
35
|
+
interactive = true, reducedMotion, theme, caps, } = props;
|
|
36
|
+
const glyphs = pickGlyphs(caps);
|
|
37
|
+
const color = retryCardColor(phase, theme);
|
|
38
|
+
const inFlight = phase === 'waiting' || phase === 'retrying';
|
|
39
|
+
const line = formatRetryLine({
|
|
40
|
+
attempt,
|
|
41
|
+
maxRetries,
|
|
42
|
+
errorType,
|
|
43
|
+
phase,
|
|
44
|
+
...(resumeAt !== undefined ? { resumeAt } : {}),
|
|
45
|
+
...(totalRetries !== undefined ? { totalRetries } : {}),
|
|
46
|
+
...(elapsedMs !== undefined ? { elapsedMs } : {}),
|
|
47
|
+
}, glyphs, now);
|
|
48
|
+
// REDUCED MOTION SUPPRESSES THE SPINNER AND KEEPS THE COUNTDOWN. A frozen number
|
|
49
|
+
// is not calmer, it is broken: reduced motion is about animation, and a
|
|
50
|
+
// countdown is information.
|
|
51
|
+
const animate = inFlight && !reducedMotion && caps.unicode;
|
|
52
|
+
return (_jsxs(Box, { flexDirection: "row", children: [animate ? (_jsxs(Text, { color: color, children: [_jsx(Spinner, { type: "dots" }), ' '] })) : null, _jsx(Text, { color: color, wrap: "truncate", children: line }), phase === 'waiting' && interactive && (_jsxs(Text, { color: theme.muted, children: [' ', "Esc to cancel"] }))] }));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
56
|
+
* `mapEntry` preserves object identity for untouched entries, and `theme` / `caps`
|
|
57
|
+
* are `useMemo`d in `App.tsx` (I-L2-1). `now` changes once a second while the card
|
|
58
|
+
* is waiting, which is exactly when it must re-render.
|
|
59
|
+
*/
|
|
60
|
+
export const RetryCard = React.memo(RetryCardImpl);
|
|
@@ -48,7 +48,12 @@ function TeamCardImpl(props) {
|
|
|
48
48
|
: `${formatDuration(durationMs ?? 0)} (${ok} ok, ${failed} failed)`;
|
|
49
49
|
const count = `${runs.length} ${runs.length === 1 ? 'subagent' : 'subagents'}`;
|
|
50
50
|
const dropped = requested > runs.length ? ` (${runs.length} of ${requested} requested)` : '';
|
|
51
|
-
|
|
51
|
+
// ABSENT WHEN NO FAST CHILD RAN, so an ordinary dispatch's card is unchanged
|
|
52
|
+
// (fast-model-tier §6). `n fast` rather than a ratio: the total is already one
|
|
53
|
+
// column to the left.
|
|
54
|
+
const fastCount = runs.filter((r) => r.tier === 'fast').length;
|
|
55
|
+
const fastNote = fastCount > 0 ? ` ${fastCount} fast` : '';
|
|
56
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.accent, bold: true, children: "team" }), _jsxs(Text, { color: theme.muted, children: [' ', count, dropped, fastNote, ' '] }), active && !reducedMotion && caps.unicode ? (_jsxs(Text, { color: color, children: [_jsx(Spinner, { type: "dots" }), " ", headline] })) : (_jsxs(Text, { color: color, children: [active ? `${glyphs.spinnerStill} ` : '', headline] }))] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, ...railBorderProps(glyphs.railVertical, theme.border), children: [runs.map((run) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: isOk(run) ? theme.toolDone : theme.toolError, children: isOk(run) ? glyphs.toolDone : glyphs.toolError }), _jsxs(Text, { color: theme.primary, children: [' ', `${run.label}${run.tier === 'fast' ? '~' : ''}`.padEnd(8).slice(0, 8)] }), _jsxs(Text, { color: theme.muted, children: [' ', run.description, ' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? 0) - run.startedAt)) : '', ` ${run.turns} turns`, run.filesTouched.length > 0 ? ` ${run.filesTouched.length} files` : '', run.error ? ` ${run.error}` : ''] }), run.retry && (_jsxs(Text, { color: theme.noticeWarn, bold: true, children: [' ', "retry ", run.retry.attempt, "/", run.retry.maxRetries] }))] }, run.label))), expanded &&
|
|
52
57
|
runs.map((run) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: theme.accent, children: [run.label, " ", glyphs.midDot, " ", run.description] }), _jsx(Text, { color: theme.muted, children: run.summary ?? '(no summary)' })] }, `s-${run.label}`))), !expanded && !active && runs.length > 0 && (_jsxs(Text, { color: theme.muted, children: ["+", runs.length, " ", runs.length === 1 ? 'summary' : 'summaries', " (Ctrl+O)"] })), expanded && _jsx(Text, { color: theme.muted, children: "(Ctrl+O to collapse)" })] })] }));
|
|
53
58
|
}
|
|
54
59
|
/**
|
|
@@ -17,6 +17,7 @@ import React from 'react';
|
|
|
17
17
|
import type { Theme } from '../theme.js';
|
|
18
18
|
import type { TermCapabilities } from '../capabilities.js';
|
|
19
19
|
import type { ToolStatus } from '../../agent/reducer.js';
|
|
20
|
+
import type { FilePatch } from '../../tools/patch.js';
|
|
20
21
|
interface ToolCardProps {
|
|
21
22
|
name: string;
|
|
22
23
|
label: string;
|
|
@@ -24,6 +25,14 @@ interface ToolCardProps {
|
|
|
24
25
|
args?: Record<string, unknown>;
|
|
25
26
|
status: ToolStatus;
|
|
26
27
|
preview?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The structured file diff, when the tool produced one (§3.4). Present it and
|
|
30
|
+
* `DiffView` draws INSTEAD of `ToolPreview`; absent — a session saved by an
|
|
31
|
+
* older build, an entry the store's 64-write ring evicted, or a toolset built
|
|
32
|
+
* without a recorder — and the text preview renders exactly what it does
|
|
33
|
+
* today. Nothing ever renders blank.
|
|
34
|
+
*/
|
|
35
|
+
patch?: FilePatch;
|
|
27
36
|
durationMs?: number;
|
|
28
37
|
isError?: boolean;
|
|
29
38
|
expanded?: boolean;
|
|
@@ -34,6 +43,28 @@ interface ToolCardProps {
|
|
|
34
43
|
* means no clamp and byte-identical output.
|
|
35
44
|
*/
|
|
36
45
|
liveClampRows?: number;
|
|
46
|
+
/**
|
|
47
|
+
* The sanitised live tail while the tool runs
|
|
48
|
+
* (agent-activity-presentation-live §3.3.4).
|
|
49
|
+
*
|
|
50
|
+
* PASSED AS `live={entry.live}` AND NEVER `live={entry.live ?? []}` (P2-4).
|
|
51
|
+
* This component is `React.memo` with the DEFAULT comparator, so a fresh `[]`
|
|
52
|
+
* per render is a changed prop and defeats the boundary for every settled card
|
|
53
|
+
* in the transcript — which is exactly why `EMPTY_LINES` above exists.
|
|
54
|
+
*/
|
|
55
|
+
live?: readonly string[];
|
|
56
|
+
/** Epoch ms of the last output row; with `nowSec`, the stall row's clock. */
|
|
57
|
+
lastOutputAt?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Wall-clock SECONDS, passed ONLY to running entries (D-29 / D-36).
|
|
60
|
+
*
|
|
61
|
+
* Seconds and not milliseconds, and `undefined` on every other card, for the
|
|
62
|
+
* same reason: a prop that changed five times a second on every entry would
|
|
63
|
+
* defeat this memo boundary transcript-wide. The value is wall-clock rather
|
|
64
|
+
* than the ticker's `elapsedMs` because the stall is `now - lastOutputAt` and
|
|
65
|
+
* `lastOutputAt` is epoch ms; the ticker's job is to cause the render.
|
|
66
|
+
*/
|
|
67
|
+
nowSec?: number;
|
|
37
68
|
theme: Theme;
|
|
38
69
|
caps: TermCapabilities;
|
|
39
70
|
}
|
|
@@ -44,7 +75,13 @@ declare function ToolCardImpl(props: ToolCardProps): React.ReactElement;
|
|
|
44
75
|
/**
|
|
45
76
|
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
46
77
|
* Every prop is a scalar except `args` (a stable reference on a settled entry),
|
|
47
|
-
* `
|
|
78
|
+
* `patch` and `theme` / `caps` (both `useMemo`d in `App.tsx` — I-L2-1).
|
|
79
|
+
*
|
|
80
|
+
* `patch` QUALIFIES ONLY BECAUSE THE REDUCER WRITES IT ONCE, at `toolExecEnd`,
|
|
81
|
+
* and never rebuilds it — `mapEntry` preserves object identity for untouched
|
|
82
|
+
* entries. A future action that recomputed or normalized a patch would turn this
|
|
83
|
+
* boundary into a no-op WITH NOTHING FAILING, which is the same trap I-L2-1
|
|
84
|
+
* exists to catch for `theme` and `caps` (P2-9).
|
|
48
85
|
*/
|
|
49
86
|
export declare const ToolCard: React.MemoExoticComponent<typeof ToolCardImpl>;
|
|
50
87
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Tool-call card (spec §3.5): a per-tool glyph, the tool name, a compact args
|
|
4
4
|
* summary, a colored status badge, and a duration, over a collapsible rich
|
|
@@ -22,6 +22,8 @@ import { railBorderProps } from '../layout/Gutter.js';
|
|
|
22
22
|
import { splitLinesCached } from '../render-cache.js';
|
|
23
23
|
import { formatDuration } from '../../agent/usage.js';
|
|
24
24
|
import { ToolPreview, bashBadge } from './ToolPreview.js';
|
|
25
|
+
import { DiffView } from './DiffView.js';
|
|
26
|
+
import { STALL_AFTER_MS } from '../../tools/tool-output-store.js';
|
|
25
27
|
/** Number of preview lines shown before collapse. */
|
|
26
28
|
const COLLAPSED_LINES = 8;
|
|
27
29
|
/** Shared empty slice, so an argument-less card allocates nothing per frame. */
|
|
@@ -78,7 +80,7 @@ export function toolEntryGlyph(name, caps) {
|
|
|
78
80
|
return toolGlyph(pickGlyphs(caps), name);
|
|
79
81
|
}
|
|
80
82
|
function ToolCardImpl(props) {
|
|
81
|
-
const { name, argsRaw, args, status, preview, durationMs, isError, expanded, reducedMotion, liveClampRows, theme, caps, } = props;
|
|
83
|
+
const { name, argsRaw, args, status, preview, patch, durationMs, isError, expanded, reducedMotion, liveClampRows, live, lastOutputAt, nowSec, theme, caps, } = props;
|
|
82
84
|
const glyphs = pickGlyphs(caps);
|
|
83
85
|
const color = statusColor(status, theme);
|
|
84
86
|
const previewText = preview ?? '';
|
|
@@ -94,13 +96,74 @@ function ToolCardImpl(props) {
|
|
|
94
96
|
const ceiling = Math.min(expanded ? previewLines.length : COLLAPSED_LINES, liveClampRows ?? Number.POSITIVE_INFINITY);
|
|
95
97
|
const visibleLines = ceiling >= previewLines.length ? previewLines : previewLines.slice(0, ceiling);
|
|
96
98
|
const hidden = previewLines.length - visibleLines.length;
|
|
97
|
-
const
|
|
99
|
+
const settled = status === 'done' || status === 'error';
|
|
100
|
+
const showPatch = !!patch && settled;
|
|
101
|
+
// The live tail (agent-activity-presentation-live §3.3.4). `expanded` is
|
|
102
|
+
// deliberately ABSENT from this arithmetic: `Ctrl+O` is inert on a live card
|
|
103
|
+
// (D-26), because expansion is a promise about STORED content and the store
|
|
104
|
+
// deliberately holds eight rows. A key that silently does nothing is better
|
|
105
|
+
// than a key that promises the rest of a 400 MB stream.
|
|
106
|
+
//
|
|
107
|
+
// THE TAIL IS THE **LAST** ROWS, and the settled preview above keeps its
|
|
108
|
+
// head-first slice (E-21). Two different ends because they answer two
|
|
109
|
+
// different questions — "what did this command do" versus "what is it doing
|
|
110
|
+
// NOW" (D-25). Head-first while live would freeze on the first eight lines a
|
|
111
|
+
// build ever printed and then never change again.
|
|
112
|
+
const liveRows = live ?? EMPTY_LINES;
|
|
113
|
+
const liveCeiling = Math.min(COLLAPSED_LINES,
|
|
114
|
+
// Inline only: `liveClampRows` already subtracts one row per live entry to
|
|
115
|
+
// pay for a marker (`Transcript.tsx:534-538`), which is what this card's
|
|
116
|
+
// unconditional footer spends (AC-36 / P2-7).
|
|
117
|
+
liveClampRows ?? Number.POSITIVE_INFINITY);
|
|
118
|
+
const visibleLive = liveRows.length > liveCeiling ? liveRows.slice(-Math.max(1, liveCeiling)) : liveRows;
|
|
119
|
+
const showLive = !settled && visibleLive.length > 0;
|
|
120
|
+
// THE OUTER GUARD WIDENS WITH THE BRANCH (P2-2). It was `previewLines.length >
|
|
121
|
+
// 0 && ...`, and the reducer now truncates the stored preview to ONE LINE when
|
|
122
|
+
// a patch is attached (D-19) — so a card whose preview were ever empty would
|
|
123
|
+
// render its header and swallow the diff, silently. It widens a second time
|
|
124
|
+
// here, for the third branch: without `|| showLive` the tail would be computed
|
|
125
|
+
// and never drawn.
|
|
126
|
+
const showBody = ((previewLines.length > 0 || showPatch) && settled) || showLive;
|
|
127
|
+
// ONE FOOTER ROW, ALWAYS, matching `estimateEntryRows`'s arithmetic exactly: a
|
|
128
|
+
// CONSTANT row count is what lets the estimate be exact rather than an upper
|
|
129
|
+
// bound. `nowSec` is absent until the first tick, which reads as "not stalled".
|
|
130
|
+
const stalledFor = nowSec !== undefined && lastOutputAt !== undefined
|
|
131
|
+
? nowSec * 1000 - lastOutputAt
|
|
132
|
+
: 0;
|
|
133
|
+
const liveFooter = stalledFor >= STALL_AFTER_MS
|
|
134
|
+
? `no output for ${Math.floor(stalledFor / 1000)}s`
|
|
135
|
+
: '(running)';
|
|
98
136
|
const badge = status === 'running' && !reducedMotion && caps.unicode ? (_jsxs(Text, { color: color, children: [_jsx(Spinner, { type: "dots" }), " running"] })) : (_jsxs(Text, { color: color, children: [status === 'running' ? `${glyphs.spinnerStill} ` : '', badgeLabel(name, status, previewText)] }));
|
|
99
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.accent, bold: true, children: name }), _jsxs(Text, { color: theme.muted, children: [" ", summarizeArgs(name, glyphs.ellipsis, args, argsRaw), " "] }), badge, durationMs !== undefined && (_jsxs(Text, { color: theme.muted, children: [" (", formatDuration(durationMs), ")"] }))] }), showBody && (
|
|
137
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.accent, bold: true, children: name }), _jsxs(Text, { color: theme.muted, children: [" ", summarizeArgs(name, glyphs.ellipsis, args, argsRaw), " "] }), badge, durationMs !== undefined && (_jsxs(Text, { color: theme.muted, children: [" (", formatDuration(durationMs), ")"] }))] }), showBody && (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, ...railBorderProps(glyphs.railVertical, isError ? theme.toolError : theme.border), children: showLive ? (_jsxs(_Fragment, { children: [visibleLive.map((row, i) => (
|
|
138
|
+
// The row is already terminal-safe and clipped at
|
|
139
|
+
// `LIVE_ROW_MAX_CHARS` by `sanitizeChunk`, so `wrap="truncate"`
|
|
140
|
+
// is an exact height rather than a hope — which is the coupling
|
|
141
|
+
// `estimateEntryRows`'s live branch relies on.
|
|
142
|
+
//
|
|
143
|
+
// The index IS the key: these rows are positional slots in a
|
|
144
|
+
// ring, not identities, and the whole array is replaced on every
|
|
145
|
+
// chunk anyway.
|
|
146
|
+
//
|
|
147
|
+
// AN EMPTY ROW RENDERS AS A SPACE, and that is not cosmetic:
|
|
148
|
+
// Ink measures a `<Text>` with an empty child as ZERO rows, so a
|
|
149
|
+
// blank separator line — which `npm test`, `cargo` and every
|
|
150
|
+
// build tool print — would make the card shorter than
|
|
151
|
+
// `estimateEntryRows` charged for it. That is an over-estimate,
|
|
152
|
+
// the safe direction, but it would quietly cost §3.3.4's
|
|
153
|
+
// "constant row count" the exactness its arithmetic claims. A
|
|
154
|
+
// space is also what the terminal itself shows for a blank line.
|
|
155
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
156
|
+
_jsx(Text, { wrap: "truncate", color: theme.muted, children: row.length > 0 ? row : ' ' }, i))), _jsx(Text, { color: theme.muted, children: liveFooter })] })) : showPatch ? (_jsx(DiffView, { patch: patch, expanded: expanded, liveClampRows: liveClampRows, theme: theme })) : (_jsxs(_Fragment, { children: [_jsx(ToolPreview, { name: name, lines: visibleLines, theme: theme, isError: isError }), !expanded && hidden > 0 && (_jsxs(Text, { color: theme.muted, children: ["+", hidden, " ", hidden === 1 ? 'line' : 'lines', " (Ctrl+O)"] })), expanded && hidden > 0 && (_jsxs(Text, { color: theme.muted, children: ["+", hidden, " ", hidden === 1 ? 'line' : 'lines', " ", glyphs.midDot, " shown in full when this entry finishes"] })), expanded && previewLines.length > COLLAPSED_LINES && (_jsx(Text, { color: theme.muted, children: "(Ctrl+O to collapse)" }))] })) }))] }));
|
|
100
157
|
}
|
|
101
158
|
/**
|
|
102
159
|
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
103
160
|
* Every prop is a scalar except `args` (a stable reference on a settled entry),
|
|
104
|
-
* `
|
|
161
|
+
* `patch` and `theme` / `caps` (both `useMemo`d in `App.tsx` — I-L2-1).
|
|
162
|
+
*
|
|
163
|
+
* `patch` QUALIFIES ONLY BECAUSE THE REDUCER WRITES IT ONCE, at `toolExecEnd`,
|
|
164
|
+
* and never rebuilds it — `mapEntry` preserves object identity for untouched
|
|
165
|
+
* entries. A future action that recomputed or normalized a patch would turn this
|
|
166
|
+
* boundary into a no-op WITH NOTHING FAILING, which is the same trap I-L2-1
|
|
167
|
+
* exists to catch for `theme` and `caps` (P2-9).
|
|
105
168
|
*/
|
|
106
169
|
export const ToolCard = React.memo(ToolCardImpl);
|
package/dist/ui/glyphs.d.ts
CHANGED
|
@@ -108,6 +108,15 @@ export interface Glyphs {
|
|
|
108
108
|
todoPending: string;
|
|
109
109
|
todoActive: string;
|
|
110
110
|
todoDone: string;
|
|
111
|
+
/**
|
|
112
|
+
* API retry (llm-api-retry-backoff §6.5). The rail marker on the retry card.
|
|
113
|
+
*
|
|
114
|
+
* HERE RATHER THAN IN `RetryCard`, for the reason this file's header gives: a
|
|
115
|
+
* literal in a component bypasses `pickGlyphs` entirely and shows mojibake on a
|
|
116
|
+
* legacy console whatever the capability probe said. `glyphs.test.ts`'s static
|
|
117
|
+
* scan fails the build if either spelling appears anywhere else.
|
|
118
|
+
*/
|
|
119
|
+
retry: string;
|
|
111
120
|
/**
|
|
112
121
|
* Ink `borderStyle` name for every boxed region (composer, completion popup,
|
|
113
122
|
* overlay frame).
|
package/dist/ui/glyphs.js
CHANGED
|
@@ -64,6 +64,7 @@ const UNICODE_GLYPHS = {
|
|
|
64
64
|
todoPending: '○',
|
|
65
65
|
todoActive: '▸',
|
|
66
66
|
todoDone: '✔',
|
|
67
|
+
retry: '↻',
|
|
67
68
|
tool: {
|
|
68
69
|
read_file: '▤',
|
|
69
70
|
write_file: '✎',
|
|
@@ -125,6 +126,7 @@ const ASCII_GLYPHS = {
|
|
|
125
126
|
todoPending: '[ ]',
|
|
126
127
|
todoActive: '>',
|
|
127
128
|
todoDone: '[x]',
|
|
129
|
+
retry: '[r]',
|
|
128
130
|
tool: {
|
|
129
131
|
read_file: '=',
|
|
130
132
|
write_file: '+',
|