@aragon-agent/cli 0.5.8 → 0.5.10
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 +1157 -747
- package/README.md +1552 -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/boot/guard.d.ts +135 -0
- package/dist/boot/guard.js +218 -0
- package/dist/boot/rollback.d.ts +86 -0
- package/dist/boot/rollback.js +185 -0
- package/dist/cli.js +368 -3
- package/dist/commands/builtins.d.ts +1 -1
- package/dist/commands/builtins.js +426 -2
- package/dist/commands/registry.d.ts +17 -0
- package/dist/config/app-paths.d.ts +31 -0
- package/dist/config/app-paths.js +37 -0
- package/dist/config/cli-commands.d.ts +79 -2
- package/dist/config/cli-commands.js +185 -3
- package/dist/config/env.d.ts +15 -3
- package/dist/config/env.js +143 -3
- package/dist/config/load.d.ts +66 -0
- package/dist/config/load.js +107 -1
- package/dist/config/schema.d.ts +268 -2
- package/dist/config/schema.js +288 -1
- package/dist/config/store.js +32 -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/launcher.d.ts +14 -0
- package/dist/launcher.js +43 -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 +11 -4
- package/dist/ui/App.js +359 -37
- package/dist/ui/BottomStatusRow.d.ts +65 -0
- package/dist/ui/BottomStatusRow.js +17 -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/UpdateLine.d.ts +44 -0
- package/dist/ui/UpdateLine.js +129 -0
- 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/dist/update/classify-failure.d.ts +32 -0
- package/dist/update/classify-failure.js +60 -0
- package/dist/update/install-lock.d.ts +55 -0
- package/dist/update/install-lock.js +184 -0
- package/dist/update/install-source.d.ts +97 -0
- package/dist/update/install-source.js +218 -0
- package/dist/update/installer.d.ts +108 -0
- package/dist/update/installer.js +242 -0
- package/dist/update/limits.d.ts +111 -0
- package/dist/update/limits.js +111 -0
- package/dist/update/npm-view.d.ts +62 -0
- package/dist/update/npm-view.js +133 -0
- package/dist/update/registry.d.ts +52 -0
- package/dist/update/registry.js +161 -0
- package/dist/update/semver.d.ts +89 -0
- package/dist/update/semver.js +295 -0
- package/dist/update/service.d.ts +193 -0
- package/dist/update/service.js +636 -0
- package/dist/update/state.d.ts +103 -0
- package/dist/update/state.js +142 -0
- package/dist/update/types.d.ts +169 -0
- package/dist/update/types.js +68 -0
- package/package.json +3 -3
- package/dist/ui/layout/regions.d.ts +0 -55
- package/dist/ui/layout/regions.js +0 -52
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { pickGlyphs } from './glyphs.js';
|
|
4
|
+
export function UpdateLine({ snapshot, compact, theme, caps }) {
|
|
5
|
+
const glyphs = pickGlyphs(caps);
|
|
6
|
+
const version = snapshot.latestVersion ?? snapshot.currentVersion;
|
|
7
|
+
const advice = snapshot.advice ?? '';
|
|
8
|
+
const dot = glyphs.midDot;
|
|
9
|
+
const line = (() => {
|
|
10
|
+
if (snapshot.phase === 'installing') {
|
|
11
|
+
return {
|
|
12
|
+
glyph: glyphs.arrowUp,
|
|
13
|
+
text: compact
|
|
14
|
+
? `${version}${glyphs.ellipsis}`
|
|
15
|
+
: `updating to ${version}${glyphs.ellipsis}`,
|
|
16
|
+
color: theme.muted,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (snapshot.phase === 'ready') {
|
|
20
|
+
return {
|
|
21
|
+
glyph: glyphs.check,
|
|
22
|
+
text: compact ? `${version} ready` : `${version} installed ${dot} restart aragon to apply`,
|
|
23
|
+
color: theme.toast.success,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (snapshot.phase === 'failed') {
|
|
27
|
+
// H2's three classified failures (cli-auto-update-hardening section 8).
|
|
28
|
+
// Each names the REAL obstacle rather than the generic one, because each
|
|
29
|
+
// is allowed to break the three-strike silence and a notice that breaks
|
|
30
|
+
// silence must be worth the interruption.
|
|
31
|
+
if (snapshot.reason === 'blocked-by-os') {
|
|
32
|
+
// `npm i -g` FROM THE SAME SHELL HITS THE SAME HANDLE. On Windows the
|
|
33
|
+
// shim that launched us is being read by the `cmd.exe` running it for
|
|
34
|
+
// as long as our process lives, and npm's install rewrites that shim -
|
|
35
|
+
// so the generic advice is not merely unhelpful here, it is a wrong
|
|
36
|
+
// instruction (R-3 / section 5.3.1).
|
|
37
|
+
return {
|
|
38
|
+
glyph: glyphs.warn,
|
|
39
|
+
text: compact || !advice
|
|
40
|
+
? 'update blocked'
|
|
41
|
+
: `update blocked ${dot} close other aragon windows, then: ${advice}`,
|
|
42
|
+
color: theme.noticeWarn,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (snapshot.reason === 'no-space') {
|
|
46
|
+
return {
|
|
47
|
+
glyph: glyphs.warn,
|
|
48
|
+
text: compact ? 'no disk space' : `update failed ${dot} no disk space`,
|
|
49
|
+
color: theme.noticeWarn,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (snapshot.reason === 'not-writable') {
|
|
53
|
+
return {
|
|
54
|
+
glyph: glyphs.warn,
|
|
55
|
+
text: compact || !advice
|
|
56
|
+
? 'update blocked'
|
|
57
|
+
: `update needs write access ${dot} ${advice}`,
|
|
58
|
+
color: theme.noticeWarn,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
glyph: glyphs.warn,
|
|
63
|
+
text: compact || !advice ? 'update failed' : `update failed ${dot} ${advice}`,
|
|
64
|
+
color: theme.noticeWarn,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (snapshot.phase === 'available') {
|
|
68
|
+
if (snapshot.reason === 'node-too-old') {
|
|
69
|
+
// Both numbers or neither: "needs a newer Node" without saying which is
|
|
70
|
+
// a notice the user cannot act on.
|
|
71
|
+
const detail = snapshot.requiredNode && snapshot.runningNode
|
|
72
|
+
? `${version} needs Node ${snapshot.requiredNode} (running ${snapshot.runningNode})`
|
|
73
|
+
: `${version} needs a newer Node`;
|
|
74
|
+
return {
|
|
75
|
+
glyph: glyphs.warn,
|
|
76
|
+
text: compact ? 'needs newer Node' : detail,
|
|
77
|
+
color: theme.noticeWarn,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (snapshot.reason === 'install-ineffective') {
|
|
81
|
+
return {
|
|
82
|
+
glyph: glyphs.warn,
|
|
83
|
+
text: compact
|
|
84
|
+
? 'check npm prefix'
|
|
85
|
+
: `${version} installed elsewhere${advice ? ` ${dot} ${advice}` : ''}`,
|
|
86
|
+
color: theme.noticeWarn,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
glyph: glyphs.arrowUp,
|
|
91
|
+
text: compact
|
|
92
|
+
? version
|
|
93
|
+
: `${version} available${advice ? ` ${dot} ${advice}` : ''}`,
|
|
94
|
+
color: theme.muted,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// H1's rollback notice, and it goes HERE rather than in a phase branch
|
|
98
|
+
// (cli-auto-update-hardening section 8 / P0-1). It is true for a whole
|
|
99
|
+
// SESSION rather than for a phase, so `shouldRenderUpdateLine` decides its
|
|
100
|
+
// presence from `snapshot.rolledBackFrom` outside `phase` entirely - and the
|
|
101
|
+
// phases that leaves are exactly `idle` / `checking`, which is this
|
|
102
|
+
// fallthrough. An earlier draft put it in the `failed` / `available`
|
|
103
|
+
// branches and keyed it on phase `idle`, a phase neither branch handles;
|
|
104
|
+
// three independent suppressions, so the one line telling a user their CLI
|
|
105
|
+
// had been silently downgraded could never have appeared.
|
|
106
|
+
//
|
|
107
|
+
// NOT `version` ABOVE: that is `latestVersion ?? currentVersion`, and after
|
|
108
|
+
// a rollback `latestVersion` may well be the BAD release we just escaped.
|
|
109
|
+
if (snapshot.rolledBackFrom) {
|
|
110
|
+
const to = snapshot.rolledBackTo ?? snapshot.currentVersion;
|
|
111
|
+
return {
|
|
112
|
+
glyph: glyphs.warn,
|
|
113
|
+
text: compact
|
|
114
|
+
? 'rolled back'
|
|
115
|
+
: `rolled back to ${to} after ${snapshot.rolledBackFrom} failed to start`,
|
|
116
|
+
color: theme.noticeWarn,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// `idle` / `checking`, and a `failed` below the notice threshold. The CALLER
|
|
120
|
+
// never mounts us here (`shouldRenderUpdateLine` is false), but the row must
|
|
121
|
+
// still be exactly one row if it ever is - the same blank-row discipline
|
|
122
|
+
// `ToastStack` applies to its own budgeted row.
|
|
123
|
+
return null;
|
|
124
|
+
})();
|
|
125
|
+
if (!line) {
|
|
126
|
+
return (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { children: " " }) }));
|
|
127
|
+
}
|
|
128
|
+
return (_jsx(Box, { flexShrink: 0, children: _jsxs(Text, { wrap: "truncate", color: line.color, children: [' ', line.glyph, " ", line.text] }) }));
|
|
129
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The working line's vocabulary (agent-activity-presentation §3.2.1).
|
|
3
|
+
*
|
|
4
|
+
* PURE: no React, no Ink, no clock. Everything here is a function of the two
|
|
5
|
+
* numbers the caller already has, which is what makes the rotation testable
|
|
6
|
+
* without a terminal and without fake timers.
|
|
7
|
+
*/
|
|
8
|
+
/** Rotation cadence. Long enough to read, short enough to prove liveness. */
|
|
9
|
+
export declare const PHRASE_ROTATE_MS = 4000;
|
|
10
|
+
/**
|
|
11
|
+
* ASCII ONLY, by the rule this tree is under (`glyphs.ts` header, enforced by
|
|
12
|
+
* `glyphs.test.ts`'s static scan). Present participles, so the line reads as an
|
|
13
|
+
* action in progress; twelve characters or fewer so the row never fights the
|
|
14
|
+
* terminal's width.
|
|
15
|
+
*/
|
|
16
|
+
export declare const ACTIVITY_PHRASES: readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* The phrase for a run at a moment.
|
|
19
|
+
*
|
|
20
|
+
* PURE AND TOTAL: the same inputs give the same word, for any finite numbers,
|
|
21
|
+
* including a `now` before `startedAt`.
|
|
22
|
+
*
|
|
23
|
+
* The run's start seeds the sequence, so two consecutive runs do not open on the
|
|
24
|
+
* same word — which is what makes the line read as alive rather than as a fixed
|
|
25
|
+
* label. (The seed's granularity is one second, so two runs starting inside the
|
|
26
|
+
* same second do share an opening word — P2-6.)
|
|
27
|
+
*
|
|
28
|
+
* `rotate: false` is the reduced-motion path: the word is chosen once for the run
|
|
29
|
+
* and then holds. Rotating text IS motion, and a user who asked for
|
|
30
|
+
* `reducedMotion` asked for the screen to stop changing under them; degrading
|
|
31
|
+
* only the spinner would honor the letter of the setting and not its point.
|
|
32
|
+
*/
|
|
33
|
+
export declare function pickActivityPhrase(startedAt: number, now: number, rotate?: boolean): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The working line's vocabulary (agent-activity-presentation §3.2.1).
|
|
3
|
+
*
|
|
4
|
+
* PURE: no React, no Ink, no clock. Everything here is a function of the two
|
|
5
|
+
* numbers the caller already has, which is what makes the rotation testable
|
|
6
|
+
* without a terminal and without fake timers.
|
|
7
|
+
*/
|
|
8
|
+
/** Rotation cadence. Long enough to read, short enough to prove liveness. */
|
|
9
|
+
export const PHRASE_ROTATE_MS = 4_000;
|
|
10
|
+
/**
|
|
11
|
+
* ASCII ONLY, by the rule this tree is under (`glyphs.ts` header, enforced by
|
|
12
|
+
* `glyphs.test.ts`'s static scan). Present participles, so the line reads as an
|
|
13
|
+
* action in progress; twelve characters or fewer so the row never fights the
|
|
14
|
+
* terminal's width.
|
|
15
|
+
*/
|
|
16
|
+
export const ACTIVITY_PHRASES = [
|
|
17
|
+
'Assembling', 'Brewing', 'Calibrating', 'Cogitating', 'Composing',
|
|
18
|
+
'Computing', 'Concocting', 'Conjuring', 'Considering', 'Crunching',
|
|
19
|
+
'Distilling', 'Divining', 'Drafting', 'Excavating', 'Fathoming',
|
|
20
|
+
'Fermenting', 'Finessing', 'Foraging', 'Germinating', 'Herding',
|
|
21
|
+
'Incubating', 'Inferring', 'Marinating', 'Meandering', 'Mulling',
|
|
22
|
+
'Musing', 'Noodling', 'Percolating', 'Pondering', 'Puzzling',
|
|
23
|
+
'Ruminating', 'Scheming', 'Sculpting', 'Simmering', 'Sketching',
|
|
24
|
+
'Spelunking', 'Steeping', 'Stitching', 'Summoning', 'Tinkering',
|
|
25
|
+
'Unfurling', 'Untangling', 'Weaving', 'Whittling', 'Wrangling',
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* The phrase for a run at a moment.
|
|
29
|
+
*
|
|
30
|
+
* PURE AND TOTAL: the same inputs give the same word, for any finite numbers,
|
|
31
|
+
* including a `now` before `startedAt`.
|
|
32
|
+
*
|
|
33
|
+
* The run's start seeds the sequence, so two consecutive runs do not open on the
|
|
34
|
+
* same word — which is what makes the line read as alive rather than as a fixed
|
|
35
|
+
* label. (The seed's granularity is one second, so two runs starting inside the
|
|
36
|
+
* same second do share an opening word — P2-6.)
|
|
37
|
+
*
|
|
38
|
+
* `rotate: false` is the reduced-motion path: the word is chosen once for the run
|
|
39
|
+
* and then holds. Rotating text IS motion, and a user who asked for
|
|
40
|
+
* `reducedMotion` asked for the screen to stop changing under them; degrading
|
|
41
|
+
* only the spinner would honor the letter of the setting and not its point.
|
|
42
|
+
*/
|
|
43
|
+
export function pickActivityPhrase(startedAt, now, rotate = true) {
|
|
44
|
+
const n = ACTIVITY_PHRASES.length;
|
|
45
|
+
if (!Number.isFinite(startedAt) || !Number.isFinite(now))
|
|
46
|
+
return ACTIVITY_PHRASES[0];
|
|
47
|
+
const seed = Math.floor(startedAt / 1000);
|
|
48
|
+
const step = rotate ? Math.floor(Math.max(0, now - startedAt) / PHRASE_ROTATE_MS) : 0;
|
|
49
|
+
return ACTIVITY_PHRASES[(((seed + step) % n) + n) % n];
|
|
50
|
+
}
|
|
@@ -23,6 +23,26 @@ interface AssistantEntryProps {
|
|
|
23
23
|
thinkingVisible: boolean;
|
|
24
24
|
streaming: boolean;
|
|
25
25
|
aborted?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Sealed reasoning time (§3.1.2). OMITTED WHEN UNKNOWN rather than printed as
|
|
28
|
+
* `0s`: a session restored from a file written by an older build has none, and
|
|
29
|
+
* `thought for 0s` is a lie where a bare `thought` is merely terse.
|
|
30
|
+
*/
|
|
31
|
+
thinkingMs?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Whether pressing `Ctrl+T` would actually reveal THIS entry's body — i.e.
|
|
34
|
+
* `mode === 'fullscreen'` (D-16 / P0-2).
|
|
35
|
+
*
|
|
36
|
+
* In inline mode a settled entry has already been printed into Ink's
|
|
37
|
+
* `<Static>`, which cannot un-print or re-print (`Transcript.tsx:9-10`), and
|
|
38
|
+
* the settled boundary is held monotonic precisely so an entry never flows back
|
|
39
|
+
* out. With thinking hidden by default the body is never printed there AT ALL,
|
|
40
|
+
* so a row reading `ctrl+t to show` would be an instruction that provably does
|
|
41
|
+
* nothing. The row degrades to `* thought for 12s` instead — still honest that
|
|
42
|
+
* reasoning happened and how long it took, and silent about a key that cannot
|
|
43
|
+
* deliver.
|
|
44
|
+
*/
|
|
45
|
+
revealable?: boolean;
|
|
26
46
|
/**
|
|
27
47
|
* Inline mode only (L5): keep at most this many rows of a LIVE entry, so the
|
|
28
48
|
* non-`<Static>` region cannot reach `stdout.rows`. `undefined` means no
|
|
@@ -20,8 +20,9 @@ import { pickGlyphs } from '../glyphs.js';
|
|
|
20
20
|
import { Markdown } from '../Markdown.js';
|
|
21
21
|
import { splitLinesCached } from '../render-cache.js';
|
|
22
22
|
import { clampLiveText } from '../live-clamp.js';
|
|
23
|
+
import { formatDuration } from '../../agent/usage.js';
|
|
23
24
|
function AssistantEntryImpl(props) {
|
|
24
|
-
const { text, thinking, thinkingOpen, thinkingVisible, streaming, aborted, liveClampRows, theme, caps, } = props;
|
|
25
|
+
const { text, thinking, thinkingOpen, thinkingVisible, streaming, aborted, thinkingMs, revealable, liveClampRows, theme, caps, } = props;
|
|
25
26
|
const glyphs = pickGlyphs(caps);
|
|
26
27
|
const hasThinking = !!thinking && thinking.trim().length > 0;
|
|
27
28
|
const hasText = text.trim().length > 0;
|
|
@@ -36,7 +37,7 @@ function AssistantEntryImpl(props) {
|
|
|
36
37
|
? clampLiveText(thinking, thinkingRows)
|
|
37
38
|
: { text: thinking ?? '', hiddenRows: 0 };
|
|
38
39
|
const body = liveClampRows === undefined ? { text, hiddenRows: 0 } : clampLiveText(text, liveClampRows);
|
|
39
|
-
return (_jsxs(Box, { flexDirection: "column", children: [hasThinking && thinkingVisible && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { wrap: "truncate", color: theme.thinking, children: [glyphs.thinking, " thinking", thinkingOpen ? glyphs.ellipsis : ''] }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [clampedThinking.hiddenRows > 0 && (_jsxs(Text, { color: theme.muted, children: [glyphs.ellipsis, " ", clampedThinking.hiddenRows, " earlier", ' ', clampedThinking.hiddenRows === 1 ? 'line' : 'lines'] })), splitLinesCached(clampedThinking.text).map((line, i) => (_jsx(Text, { color: theme.thinking, dimColor: true, children: line }, i)))] })] })), (hasText || streaming) && (_jsxs(Box, { flexDirection: "column", children: [body.hiddenRows > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [glyphs.ellipsis, " ", body.hiddenRows, " earlier", ' ', body.hiddenRows === 1 ? 'line' : 'lines', " ", glyphs.midDot, " shown in full when this entry finishes"] })), hasText ? (_jsx(Markdown, { text: body.text, theme: theme, caps: caps })) : (_jsx(Text, { color: theme.muted, children: glyphs.ellipsis })), aborted && _jsx(Text, { color: theme.noticeWarn, children: "[aborted]" })] }))] }));
|
|
40
|
+
return (_jsxs(Box, { flexDirection: "column", children: [hasThinking && thinkingVisible && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { wrap: "truncate", color: theme.thinking, children: [glyphs.thinking, " thinking", thinkingOpen ? glyphs.ellipsis : ''] }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [clampedThinking.hiddenRows > 0 && (_jsxs(Text, { color: theme.muted, children: [glyphs.ellipsis, " ", clampedThinking.hiddenRows, " earlier", ' ', clampedThinking.hiddenRows === 1 ? 'line' : 'lines'] })), splitLinesCached(clampedThinking.text).map((line, i) => (_jsx(Text, { color: theme.thinking, dimColor: true, children: line }, i)))] })] })), hasThinking && !thinkingVisible && !streaming && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [glyphs.thinking, " thought", thinkingMs !== undefined ? ` for ${formatDuration(thinkingMs)}` : '', revealable ? ` ${glyphs.midDot} ctrl+t to show` : ''] })), (hasText || streaming) && (_jsxs(Box, { flexDirection: "column", children: [body.hiddenRows > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [glyphs.ellipsis, " ", body.hiddenRows, " earlier", ' ', body.hiddenRows === 1 ? 'line' : 'lines', " ", glyphs.midDot, " shown in full when this entry finishes"] })), hasText ? (_jsx(Markdown, { text: body.text, theme: theme, caps: caps })) : (_jsx(Text, { color: theme.muted, children: glyphs.ellipsis })), aborted && _jsx(Text, { color: theme.noticeWarn, children: "[aborted]" })] }))] }));
|
|
40
41
|
}
|
|
41
42
|
/** Rows of live thinking kept on screen while an answer streams. */
|
|
42
43
|
const STREAMING_THINKING_ROWS = 24;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `FilePatch` as a diff (agent-activity-presentation §3.4): a `+6 -5` summary,
|
|
3
|
+
* then hunk-separated rows with a right-aligned line-number gutter, a `+`/`-`/
|
|
4
|
+
* space sign column and `theme.diff` colors.
|
|
5
|
+
*
|
|
6
|
+
* `wrap="truncate"` ON EVERY ROW IS A CORRECTNESS REQUIREMENT, NOT A TASTE ONE.
|
|
7
|
+
* `estimateEntryRows` computes a diff card's height by COUNTING rows, so a
|
|
8
|
+
* wrapped patch line would occupy two rows while the estimate charged one — an
|
|
9
|
+
* under-estimate, which `virtual-window.ts` names as the unsafe direction. One
|
|
10
|
+
* patch line is one terminal row, always.
|
|
11
|
+
*
|
|
12
|
+
* No new keybinding and no new state: collapse rides on `expandedToolIds` and
|
|
13
|
+
* the existing `Ctrl+O` handler, which targets the MOST RECENT tool card (P2-4).
|
|
14
|
+
*
|
|
15
|
+
* `caps` is deliberately NOT a prop. Every character this component emits is
|
|
16
|
+
* ASCII — `+`, `-`, `@@`, digits — because `+` and `-` are the diff's own
|
|
17
|
+
* vocabulary and `renderUnifiedDiff` already spells them literally. A glyph
|
|
18
|
+
* lookup here would buy nothing and add a memo dependency.
|
|
19
|
+
*/
|
|
20
|
+
import React from 'react';
|
|
21
|
+
import type { Theme } from '../theme.js';
|
|
22
|
+
import type { FilePatch, PatchLine } from '../../tools/patch.js';
|
|
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 declare const DIFF_COLLAPSED_LINES = 12;
|
|
32
|
+
export interface DiffViewProps {
|
|
33
|
+
patch: FilePatch;
|
|
34
|
+
expanded?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Inline mode only (L5): ceiling on the rows a LIVE card contributes to the
|
|
37
|
+
* non-`<Static>` region. `undefined` — what the full-screen branch passes —
|
|
38
|
+
* means no clamp.
|
|
39
|
+
*/
|
|
40
|
+
liveClampRows?: number;
|
|
41
|
+
theme: Theme;
|
|
42
|
+
}
|
|
43
|
+
/** One rendered row: a patch line, or the `@@` separator between two hunks. */
|
|
44
|
+
interface DiffRow {
|
|
45
|
+
kind: PatchLine['kind'] | 'meta';
|
|
46
|
+
/** The gutter number, or `undefined` for a separator and the newline note. */
|
|
47
|
+
num?: number;
|
|
48
|
+
text: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Flatten the hunks into rows, inserting one `@@` separator BETWEEN hunks and
|
|
52
|
+
* none before the first.
|
|
53
|
+
*
|
|
54
|
+
* Exported for `diff-view.test.tsx`, and because the row count it produces is
|
|
55
|
+
* the number `estimateEntryRows` reproduces arithmetically — the two must not
|
|
56
|
+
* disagree.
|
|
57
|
+
*/
|
|
58
|
+
export declare function diffRows(patch: FilePatch): DiffRow[];
|
|
59
|
+
declare function DiffViewImpl({ patch, expanded, liveClampRows, theme, }: DiffViewProps): React.ReactElement;
|
|
60
|
+
/**
|
|
61
|
+
* `React.memo` with the DEFAULT comparator, matching `ToolCard`'s boundary.
|
|
62
|
+
* `patch` qualifies as a prop only because the reducer writes it ONCE at
|
|
63
|
+
* `toolExecEnd` and never rebuilds it (P2-9); `theme` is `useMemo`d in `App.tsx`
|
|
64
|
+
* (I-L2-1).
|
|
65
|
+
*/
|
|
66
|
+
export declare const DiffView: React.MemoExoticComponent<typeof DiffViewImpl>;
|
|
67
|
+
export {};
|
|
@@ -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 {};
|