@aragon-agent/cli 0.5.6 → 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 +356 -0
- package/README.md +1429 -1105
- 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/entry-limits.d.ts +65 -0
- package/dist/agent/entry-limits.js +133 -0
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +40 -0
- package/dist/agent/reducer.d.ts +253 -3
- package/dist/agent/reducer.js +553 -28
- 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 +122 -4
- package/dist/commands/builtins.d.ts +1 -1
- package/dist/commands/builtins.js +313 -2
- package/dist/commands/perf.d.ts +45 -0
- package/dist/commands/perf.js +78 -0
- package/dist/config/cli-commands.d.ts +53 -2
- package/dist/config/cli-commands.js +136 -3
- package/dist/config/env.d.ts +6 -1
- package/dist/config/env.js +131 -1
- package/dist/config/load.d.ts +63 -0
- package/dist/config/load.js +125 -2
- package/dist/config/schema.d.ts +273 -10
- package/dist/config/schema.js +250 -7
- 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 +357 -44
- 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/Markdown.d.ts +7 -1
- package/dist/ui/Markdown.js +15 -48
- package/dist/ui/PromptInput.d.ts +1 -15
- package/dist/ui/PromptInput.js +1 -38
- package/dist/ui/StatusBar.d.ts +50 -0
- package/dist/ui/StatusBar.js +7 -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 +80 -5
- package/dist/ui/Transcript.js +179 -11
- package/dist/ui/activity-phrases.d.ts +33 -0
- package/dist/ui/activity-phrases.js +50 -0
- package/dist/ui/entries/AssistantEntry.d.ts +38 -1
- package/dist/ui/entries/AssistantEntry.js +34 -3
- 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.d.ts +9 -1
- package/dist/ui/entries/TeamCard.js +28 -2
- package/dist/ui/entries/TodoCard.d.ts +9 -1
- package/dist/ui/entries/TodoCard.js +23 -1
- package/dist/ui/entries/ToolCard.d.ts +50 -1
- package/dist/ui/entries/ToolCard.js +104 -7
- package/dist/ui/entries/ToolPreview.d.ts +2 -2
- package/dist/ui/entries/UserEntry.d.ts +9 -1
- package/dist/ui/entries/UserEntry.js +17 -1
- package/dist/ui/exit-snapshot.d.ts +7 -0
- 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/MeasuredEntry.d.ts +21 -0
- package/dist/ui/layout/MeasuredEntry.js +30 -0
- package/dist/ui/layout/ScrollViewport.js +8 -2
- package/dist/ui/layout/viewport-geometry.d.ts +28 -0
- package/dist/ui/layout/viewport-geometry.js +27 -0
- package/dist/ui/layout/virtual-window.d.ts +100 -0
- package/dist/ui/layout/virtual-window.js +308 -0
- package/dist/ui/live-clamp.d.ts +42 -0
- package/dist/ui/live-clamp.js +74 -0
- package/dist/ui/markdown-blocks.d.ts +25 -1
- package/dist/ui/markdown-blocks.js +43 -0
- 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/render-cache.d.ts +81 -0
- package/dist/ui/render-cache.js +195 -0
- package/dist/ui/render-governor.d.ts +54 -0
- package/dist/ui/render-governor.js +78 -0
- package/dist/ui/transcript-text.d.ts +11 -0
- package/dist/ui/transcript-text.js +32 -0
- package/dist/ui/use-height-store.d.ts +31 -0
- package/dist/ui/use-height-store.js +76 -0
- package/dist/ui/use-render-governor.d.ts +39 -0
- package/dist/ui/use-render-governor.js +57 -0
- package/dist/ui/use-startup-notices.d.ts +9 -1
- package/dist/ui/use-startup-notices.js +35 -1
- 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,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);
|
|
@@ -28,4 +28,12 @@ export interface TeamCardProps {
|
|
|
28
28
|
}
|
|
29
29
|
/** The card's aggregate colour: any failure dominates. */
|
|
30
30
|
export declare function teamCardColor(runs: SubagentRun[], active: boolean, aborted: boolean, theme: Theme): string | undefined;
|
|
31
|
-
|
|
31
|
+
declare function TeamCardImpl(props: TeamCardProps): React.ReactElement;
|
|
32
|
+
/**
|
|
33
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
34
|
+
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
35
|
+
* array props below are stable references on a settled card, and `theme` /
|
|
36
|
+
* `caps` are `useMemo`d in `App.tsx` (I-L2-1).
|
|
37
|
+
*/
|
|
38
|
+
export declare const TeamCard: React.MemoExoticComponent<typeof TeamCardImpl>;
|
|
39
|
+
export {};
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* TeamCard — the settled-dispatch transcript entry (team-subagents §6.3).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: every glyph comes from `pickGlyphs`.
|
|
6
|
+
*
|
|
7
|
+
* The panel is EPHEMERAL and this card is the HISTORY. It renders like a
|
|
8
|
+
* `ToolCard` — same `EntryFrame` supplied by `EntryView`, same left rail for the
|
|
9
|
+
* expanded body — so a dispatch reads as one more step in the transcript rather
|
|
10
|
+
* than a second kind of object.
|
|
11
|
+
*
|
|
12
|
+
* `Ctrl+O` expands it to the per-agent summaries, which is why `App.tsx`'s
|
|
13
|
+
* Ctrl+O finder had to widen from `kind === 'tool'` to `tool || team`.
|
|
14
|
+
*/
|
|
15
|
+
import React from 'react';
|
|
2
16
|
import { Box, Text } from 'ink';
|
|
3
17
|
import Spinner from 'ink-spinner';
|
|
4
18
|
import { pickGlyphs } from '../glyphs.js';
|
|
@@ -15,7 +29,7 @@ export function teamCardColor(runs, active, aborted, theme) {
|
|
|
15
29
|
return theme.toolError;
|
|
16
30
|
return theme.toolDone;
|
|
17
31
|
}
|
|
18
|
-
|
|
32
|
+
function TeamCardImpl(props) {
|
|
19
33
|
const { requested, runs, aborted, durationMs, active, expanded, reducedMotion, theme, caps } = props;
|
|
20
34
|
const glyphs = pickGlyphs(caps);
|
|
21
35
|
const color = teamCardColor(runs, active, aborted, theme);
|
|
@@ -34,6 +48,18 @@ export function TeamCard(props) {
|
|
|
34
48
|
: `${formatDuration(durationMs ?? 0)} (${ok} ok, ${failed} failed)`;
|
|
35
49
|
const count = `${runs.length} ${runs.length === 1 ? 'subagent' : 'subagents'}`;
|
|
36
50
|
const dropped = requested > runs.length ? ` (${runs.length} of ${requested} requested)` : '';
|
|
37
|
-
|
|
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 &&
|
|
38
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)" })] })] }));
|
|
39
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
61
|
+
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
62
|
+
* array props below are stable references on a settled card, and `theme` /
|
|
63
|
+
* `caps` are `useMemo`d in `App.tsx` (I-L2-1).
|
|
64
|
+
*/
|
|
65
|
+
export const TeamCard = React.memo(TeamCardImpl);
|
|
@@ -28,4 +28,12 @@ export interface TodoCardProps {
|
|
|
28
28
|
}
|
|
29
29
|
/** The card's aggregate colour: complete is done, anything else is in flight. */
|
|
30
30
|
export declare function todoCardColor(doneCount: number, total: number, live: boolean, theme: Theme): string | undefined;
|
|
31
|
-
|
|
31
|
+
declare function TodoCardImpl(props: TodoCardProps): React.ReactElement;
|
|
32
|
+
/**
|
|
33
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
34
|
+
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
35
|
+
* array props below are stable references on a settled card, and `theme` /
|
|
36
|
+
* `caps` are `useMemo`d in `App.tsx` (I-L2-1).
|
|
37
|
+
*/
|
|
38
|
+
export declare const TodoCard: React.MemoExoticComponent<typeof TodoCardImpl>;
|
|
39
|
+
export {};
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* TodoCard — the transcript checklist entry (todo-plan-execution §6.2).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: every glyph comes from `pickGlyphs`.
|
|
6
|
+
*
|
|
7
|
+
* ONE CARD PER USER TURN, REWRITTEN IN PLACE. The rail is EPHEMERAL and this is
|
|
8
|
+
* the HISTORY, exactly as `TeamPanel` and `TeamCard` divide the same work. It
|
|
9
|
+
* renders through the shared `EntryFrame` supplied by `EntryView`, so a plan
|
|
10
|
+
* reads as one more step in the transcript rather than a second kind of object.
|
|
11
|
+
*
|
|
12
|
+
* THE FULL LIST, NO WINDOWING (the transcript scrolls; the rail does not), and
|
|
13
|
+
* NEVER ANIMATED even while `live` — a resumed session would otherwise spin
|
|
14
|
+
* forever on a card whose run ended days ago.
|
|
15
|
+
*/
|
|
16
|
+
import React from 'react';
|
|
2
17
|
import { Box, Text } from 'ink';
|
|
3
18
|
import { pickGlyphs } from '../glyphs.js';
|
|
4
19
|
import { railBorderProps } from '../layout/Gutter.js';
|
|
@@ -24,7 +39,7 @@ function rowColor(item, theme) {
|
|
|
24
39
|
return theme.primary;
|
|
25
40
|
return theme.hintFg ?? theme.muted;
|
|
26
41
|
}
|
|
27
|
-
|
|
42
|
+
function TodoCardImpl(props) {
|
|
28
43
|
const { items, doneCount, total, live, interrupted, theme, caps } = props;
|
|
29
44
|
const glyphs = pickGlyphs(caps);
|
|
30
45
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.accent, bold: true, children: "Todos" }), _jsxs(Text, { color: todoCardColor(doneCount, total, live, theme), children: [' ', doneCount, "/", total] }), interrupted && (
|
|
@@ -33,3 +48,10 @@ export function TodoCard(props) {
|
|
|
33
48
|
// dispatch that never got a duration.
|
|
34
49
|
_jsxs(Text, { color: theme.muted, children: [' ', "interrupted (session resumed)"] }))] }), _jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, ...railBorderProps(glyphs.railVertical, theme.border), children: items.map((item, index) => (_jsxs(Text, { wrap: "truncate", color: rowColor(item, theme), children: [marker(item, glyphs).padEnd(3), item.status === 'in_progress' ? item.activeForm : item.content] }, `${index}-${item.content}`))) })] }));
|
|
35
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
53
|
+
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
54
|
+
* array props below are stable references on a settled card, and `theme` /
|
|
55
|
+
* `caps` are `useMemo`d in `App.tsx` (I-L2-1).
|
|
56
|
+
*/
|
|
57
|
+
export const TodoCard = React.memo(TodoCardImpl);
|
|
@@ -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,15 +25,63 @@ 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;
|
|
30
39
|
reducedMotion?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Inline mode only (L5): ceiling on the rows a LIVE card contributes to the
|
|
42
|
+
* non-`<Static>` region. `undefined` — what the full-screen branch passes —
|
|
43
|
+
* means no clamp and byte-identical output.
|
|
44
|
+
*/
|
|
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;
|
|
31
68
|
theme: Theme;
|
|
32
69
|
caps: TermCapabilities;
|
|
33
70
|
}
|
|
34
71
|
export declare function statusColor(status: ToolStatus, theme: Theme): string | undefined;
|
|
35
72
|
/** The tool's own glyph, used by `EntryView` as the entry's rail marker. */
|
|
36
73
|
export declare function toolEntryGlyph(name: string, caps: TermCapabilities): string;
|
|
37
|
-
|
|
74
|
+
declare function ToolCardImpl(props: ToolCardProps): React.ReactElement;
|
|
75
|
+
/**
|
|
76
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
77
|
+
* Every prop is a scalar except `args` (a stable reference on a settled entry),
|
|
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).
|
|
85
|
+
*/
|
|
86
|
+
export declare const ToolCard: React.MemoExoticComponent<typeof ToolCardImpl>;
|
|
38
87
|
export {};
|
|
@@ -1,12 +1,33 @@
|
|
|
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
|
+
/**
|
|
3
|
+
* Tool-call card (spec §3.5): a per-tool glyph, the tool name, a compact args
|
|
4
|
+
* summary, a colored status badge, and a duration, over a collapsible rich
|
|
5
|
+
* preview delegated to `<ToolPreview>`. Collapsed to the first 8 lines with a
|
|
6
|
+
* `+M lines (Ctrl+O)` footer; `Ctrl+O` / `/expand` toggle the full preview.
|
|
7
|
+
*
|
|
8
|
+
* Two v0.4.0 changes (§4.1 / §4.3):
|
|
9
|
+
* - capabilities arrive as a prop. This used to infer Unicode support by
|
|
10
|
+
* comparing `theme.symbols.gaugeFull` against a literal block character, so
|
|
11
|
+
* editing an unrelated gauge glyph would have silently sent every tool icon
|
|
12
|
+
* back to ASCII;
|
|
13
|
+
* - the preview's round border is gone. It cost 2 rows and 4 columns per card,
|
|
14
|
+
* and nested visibly inside markdown code blocks. A left rail carries the
|
|
15
|
+
* same grouping in 1 column and 0 rows.
|
|
16
|
+
*/
|
|
17
|
+
import React from 'react';
|
|
2
18
|
import { Box, Text } from 'ink';
|
|
3
19
|
import Spinner from 'ink-spinner';
|
|
4
20
|
import { pickGlyphs, toolGlyph } from '../glyphs.js';
|
|
5
21
|
import { railBorderProps } from '../layout/Gutter.js';
|
|
22
|
+
import { splitLinesCached } from '../render-cache.js';
|
|
6
23
|
import { formatDuration } from '../../agent/usage.js';
|
|
7
24
|
import { ToolPreview, bashBadge } from './ToolPreview.js';
|
|
25
|
+
import { DiffView } from './DiffView.js';
|
|
26
|
+
import { STALL_AFTER_MS } from '../../tools/tool-output-store.js';
|
|
8
27
|
/** Number of preview lines shown before collapse. */
|
|
9
28
|
const COLLAPSED_LINES = 8;
|
|
29
|
+
/** Shared empty slice, so an argument-less card allocates nothing per frame. */
|
|
30
|
+
const EMPTY_LINES = [];
|
|
10
31
|
export function statusColor(status, theme) {
|
|
11
32
|
switch (status) {
|
|
12
33
|
case 'pending':
|
|
@@ -58,15 +79,91 @@ function badgeLabel(name, status, preview) {
|
|
|
58
79
|
export function toolEntryGlyph(name, caps) {
|
|
59
80
|
return toolGlyph(pickGlyphs(caps), name);
|
|
60
81
|
}
|
|
61
|
-
|
|
62
|
-
const { name, argsRaw, args, status, preview, durationMs, isError, expanded, reducedMotion, theme, caps, } = props;
|
|
82
|
+
function ToolCardImpl(props) {
|
|
83
|
+
const { name, argsRaw, args, status, preview, patch, durationMs, isError, expanded, reducedMotion, liveClampRows, live, lastOutputAt, nowSec, theme, caps, } = props;
|
|
63
84
|
const glyphs = pickGlyphs(caps);
|
|
64
85
|
const color = statusColor(status, theme);
|
|
65
86
|
const previewText = preview ?? '';
|
|
66
|
-
|
|
67
|
-
|
|
87
|
+
// `splitLinesCached` rather than `previewText.split('\n')`: a stored preview
|
|
88
|
+
// is immutable once written, and this used to reallocate up to 200 strings per
|
|
89
|
+
// card per frame (R3).
|
|
90
|
+
const previewLines = previewText.length > 0 ? splitLinesCached(previewText) : EMPTY_LINES;
|
|
91
|
+
// Head-first, exactly as before: the collapsed card shows the FIRST 8 lines.
|
|
92
|
+
// The inline clamp only lowers the ceiling — it never changes which end is
|
|
93
|
+
// kept, because `Ctrl+O` on a 5 000-line bash result is precisely how an
|
|
94
|
+
// inline session reaches `ink.js:121` (I-L5-1) and the fix must not also
|
|
95
|
+
// reshuffle what a full-screen user has been reading for four releases.
|
|
96
|
+
const ceiling = Math.min(expanded ? previewLines.length : COLLAPSED_LINES, liveClampRows ?? Number.POSITIVE_INFINITY);
|
|
97
|
+
const visibleLines = ceiling >= previewLines.length ? previewLines : previewLines.slice(0, ceiling);
|
|
68
98
|
const hidden = previewLines.length - visibleLines.length;
|
|
69
|
-
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)';
|
|
70
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)] }));
|
|
71
|
-
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)" }))] })) }))] }));
|
|
72
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
160
|
+
* Every prop is a scalar except `args` (a stable reference on a settled entry),
|
|
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).
|
|
168
|
+
*/
|
|
169
|
+
export const ToolCard = React.memo(ToolCardImpl);
|
|
@@ -35,7 +35,7 @@ export declare function parseWriteFile(text: string): {
|
|
|
35
35
|
path: string;
|
|
36
36
|
} | null;
|
|
37
37
|
/** `3 questions · Postgres · REST` — or `dismissed`. */
|
|
38
|
-
export declare function summarizeAskUser(lines: string[]): string | null;
|
|
38
|
+
export declare function summarizeAskUser(lines: readonly string[]): string | null;
|
|
39
39
|
export interface ListRow {
|
|
40
40
|
kind: 'dir' | 'file';
|
|
41
41
|
name: string;
|
|
@@ -51,7 +51,7 @@ export declare function parseListRow(line: string): ListRow | null;
|
|
|
51
51
|
*/
|
|
52
52
|
interface ToolPreviewProps {
|
|
53
53
|
name: string;
|
|
54
|
-
lines: string[];
|
|
54
|
+
lines: readonly string[];
|
|
55
55
|
theme: Theme;
|
|
56
56
|
isError?: boolean;
|
|
57
57
|
}
|
|
@@ -8,7 +8,15 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import type { Theme } from '../theme.js';
|
|
11
|
-
|
|
11
|
+
declare function UserEntryImpl({ text, theme }: {
|
|
12
12
|
text: string;
|
|
13
13
|
theme: Theme;
|
|
14
14
|
}): React.ReactElement;
|
|
15
|
+
/**
|
|
16
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
17
|
+
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
18
|
+
* array props below are stable references on a settled card, and `theme` /
|
|
19
|
+
* `caps` are `useMemo`d in `App.tsx` (I-L2-1).
|
|
20
|
+
*/
|
|
21
|
+
export declare const UserEntry: React.MemoExoticComponent<typeof UserEntryImpl>;
|
|
22
|
+
export {};
|