@aragon-agent/cli 0.6.34 → 0.6.36
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 +21 -0
- package/README.md +173 -7
- package/dist/agent/agent-mode.d.ts +20 -4
- package/dist/agent/agent-mode.js +20 -4
- package/dist/agent/controller.d.ts +67 -3
- package/dist/agent/controller.js +106 -5
- package/dist/agent/headless.js +31 -0
- package/dist/agent/kit-apply.d.ts +56 -0
- package/dist/agent/kit-apply.js +209 -0
- package/dist/agent/model-profile-settings.d.ts +28 -0
- package/dist/agent/model-profile-settings.js +142 -29
- package/dist/agent/reducer.d.ts +13 -2
- package/dist/agent/reducer.js +1 -0
- package/dist/cli.js +12 -0
- package/dist/commands/agents-command.d.ts +15 -0
- package/dist/commands/agents-command.js +27 -0
- package/dist/commands/builtins.js +135 -4
- package/dist/commands/kit-command.d.ts +20 -0
- package/dist/commands/kit-command.js +131 -0
- package/dist/commands/perf.d.ts +11 -0
- package/dist/commands/perf.js +2 -1
- package/dist/commands/registry.d.ts +14 -0
- package/dist/config/app-paths.d.ts +15 -0
- package/dist/config/app-paths.js +33 -2
- package/dist/config/env.d.ts +4 -1
- package/dist/config/env.js +28 -2
- package/dist/config/kit-resolution.d.ts +93 -0
- package/dist/config/kit-resolution.js +226 -0
- package/dist/config/load.d.ts +10 -0
- package/dist/config/load.js +8 -1
- package/dist/config/model-kits.d.ts +224 -0
- package/dist/config/model-kits.js +302 -0
- package/dist/config/model-profile-store.d.ts +25 -0
- package/dist/config/model-profiles.d.ts +41 -3
- package/dist/config/model-profiles.js +46 -5
- package/dist/config/schema.d.ts +84 -0
- package/dist/config/schema.js +39 -0
- package/dist/config/store.js +12 -1
- package/dist/config/ui-state.d.ts +18 -0
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +25 -0
- package/dist/exec/events.js +4 -0
- package/dist/exec/index.js +16 -1
- package/dist/exec/runner.js +10 -0
- package/dist/input/keymap.js +18 -4
- package/dist/proc/limits.d.ts +16 -0
- package/dist/proc/limits.js +16 -0
- package/dist/proc/supervisor.d.ts +20 -1
- package/dist/proc/supervisor.js +42 -14
- package/dist/prompts/command.d.ts +34 -0
- package/dist/prompts/command.js +237 -0
- package/dist/prompts/copy.d.ts +89 -0
- package/dist/prompts/copy.js +131 -0
- package/dist/prompts/fill.d.ts +51 -0
- package/dist/prompts/fill.js +46 -0
- package/dist/prompts/limits.d.ts +53 -0
- package/dist/prompts/limits.js +53 -0
- package/dist/prompts/normalize.d.ts +53 -0
- package/dist/prompts/normalize.js +190 -0
- package/dist/prompts/resolve.d.ts +31 -0
- package/dist/prompts/resolve.js +60 -0
- package/dist/prompts/search.d.ts +58 -0
- package/dist/prompts/search.js +146 -0
- package/dist/prompts/store.d.ts +86 -0
- package/dist/prompts/store.js +453 -0
- package/dist/prompts/types.d.ts +204 -0
- package/dist/prompts/types.js +13 -0
- package/dist/session/checkpoint.d.ts +8 -1
- package/dist/session/checkpoint.js +6 -0
- package/dist/session/persist.d.ts +19 -1
- package/dist/session/persist.js +13 -1
- package/dist/session/startup-session.d.ts +7 -2
- package/dist/session/startup-session.js +5 -2
- package/dist/session/store.js +3 -0
- package/dist/session/validate-session.js +64 -1
- package/dist/team/limits.d.ts +25 -0
- package/dist/team/limits.js +26 -0
- package/dist/team/runtime.js +27 -2
- package/dist/team/settled.d.ts +57 -0
- package/dist/team/settled.js +87 -0
- package/dist/team/subagent.js +48 -2
- package/dist/team/timeline.d.ts +74 -0
- package/dist/team/timeline.js +101 -0
- package/dist/team/types.d.ts +24 -1
- package/dist/todo/acceptance.d.ts +120 -0
- package/dist/todo/acceptance.js +399 -0
- package/dist/todo/follow-through.d.ts +48 -23
- package/dist/todo/follow-through.js +110 -44
- package/dist/todo/limits.d.ts +62 -1
- package/dist/todo/limits.js +62 -1
- package/dist/todo/panel-rows.d.ts +29 -39
- package/dist/todo/panel-rows.js +37 -83
- package/dist/todo/plan-sections.d.ts +60 -0
- package/dist/todo/plan-sections.js +108 -0
- package/dist/todo/prompt.d.ts +17 -12
- package/dist/todo/prompt.js +59 -40
- package/dist/todo/row-window.d.ts +51 -0
- package/dist/todo/row-window.js +107 -0
- package/dist/todo/store.d.ts +139 -31
- package/dist/todo/store.js +271 -73
- package/dist/todo/todo-tool.d.ts +33 -8
- package/dist/todo/todo-tool.js +186 -30
- package/dist/todo/types.d.ts +58 -0
- package/dist/ui/App.js +414 -54
- package/dist/ui/Composer.d.ts +6 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +17 -1
- package/dist/ui/Header.js +8 -2
- package/dist/ui/PromptInput.d.ts +21 -1
- package/dist/ui/PromptInput.js +22 -2
- package/dist/ui/ScrollbackShell.d.ts +22 -7
- package/dist/ui/ScrollbackShell.js +8 -1
- package/dist/ui/StatusBar.d.ts +18 -0
- package/dist/ui/StatusBar.js +11 -3
- package/dist/ui/TeamPanel.d.ts +35 -41
- package/dist/ui/TeamPanel.js +53 -163
- package/dist/ui/TodoPanel.d.ts +17 -14
- package/dist/ui/TodoPanel.js +146 -38
- package/dist/ui/Transcript.js +23 -3
- package/dist/ui/composer-input.d.ts +10 -0
- package/dist/ui/composer-input.js +57 -24
- package/dist/ui/entries/TeamCard.d.ts +18 -2
- package/dist/ui/entries/TeamCard.js +47 -13
- package/dist/ui/entries/TodoCard.d.ts +26 -11
- package/dist/ui/entries/TodoCard.js +56 -15
- package/dist/ui/glyphs.d.ts +19 -0
- package/dist/ui/glyphs.js +4 -0
- package/dist/ui/layout/agents-board.d.ts +102 -0
- package/dist/ui/layout/agents-board.js +389 -0
- package/dist/ui/layout/cells.d.ts +61 -0
- package/dist/ui/layout/cells.js +156 -0
- package/dist/ui/layout/header-hints.d.ts +23 -3
- package/dist/ui/layout/header-hints.js +73 -7
- package/dist/ui/layout/kit-chip.d.ts +50 -0
- package/dist/ui/layout/kit-chip.js +81 -0
- package/dist/ui/layout/status-detail-layout.js +9 -0
- package/dist/ui/layout/status-layout.d.ts +13 -0
- package/dist/ui/layout/team-row.d.ts +139 -0
- package/dist/ui/layout/team-row.js +246 -0
- package/dist/ui/layout/todo-layout.d.ts +38 -0
- package/dist/ui/layout/todo-layout.js +75 -0
- package/dist/ui/layout/virtual-window.js +32 -3
- package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
- package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
- package/dist/ui/overlays/HelpOverlay.js +24 -2
- package/dist/ui/overlays/KitEditor.d.ts +23 -0
- package/dist/ui/overlays/KitEditor.js +101 -0
- package/dist/ui/overlays/KitManager.d.ts +23 -0
- package/dist/ui/overlays/KitManager.js +104 -0
- package/dist/ui/overlays/KitPicker.d.ts +49 -0
- package/dist/ui/overlays/KitPicker.js +58 -0
- package/dist/ui/overlays/ModelProfilePicker.js +16 -6
- package/dist/ui/overlays/PromptEditorPage.d.ts +48 -0
- package/dist/ui/overlays/PromptEditorPage.js +227 -0
- package/dist/ui/overlays/PromptLibraryOverlay.d.ts +37 -0
- package/dist/ui/overlays/PromptLibraryOverlay.js +191 -0
- package/dist/ui/overlays/PromptPicker.d.ts +36 -0
- package/dist/ui/overlays/PromptPicker.js +129 -0
- package/dist/ui/overlays/PromptTextArea.d.ts +86 -0
- package/dist/ui/overlays/PromptTextArea.js +141 -0
- package/dist/ui/overlays/SettingsScreen.js +139 -16
- package/dist/ui/overlays/kit-picker-state.d.ts +87 -0
- package/dist/ui/overlays/kit-picker-state.js +136 -0
- package/dist/ui/overlays/kit-settings-state.d.ts +129 -0
- package/dist/ui/overlays/kit-settings-state.js +267 -0
- package/dist/ui/overlays/model-profile-state.d.ts +26 -3
- package/dist/ui/overlays/model-profile-state.js +59 -6
- package/dist/ui/overlays/prompt-library-state.d.ts +162 -0
- package/dist/ui/overlays/prompt-library-state.js +335 -0
- package/dist/ui/overlays/prompt-picker-state.d.ts +71 -0
- package/dist/ui/overlays/prompt-picker-state.js +98 -0
- package/dist/ui/scrollback/LiveTail.d.ts +2 -2
- package/dist/ui/scrollback/LiveTail.js +7 -2
- package/dist/ui/scrollback/band.js +14 -1
- package/dist/ui/scrollback/live-preview.js +10 -3
- package/dist/ui/scrollback/tail-window.d.ts +12 -0
- package/dist/ui/scrollback/tail-window.js +17 -2
- package/dist/ui/scrollback/use-scrollback-view.js +7 -0
- package/dist/ui/team-status.d.ts +166 -0
- package/dist/ui/team-status.js +252 -0
- package/dist/ui/transcript-text.js +13 -4
- package/dist/ui/use-startup-notices.d.ts +35 -1
- package/dist/ui/use-startup-notices.js +29 -2
- package/dist/ui/use-wheel-routing.js +10 -2
- package/package.json +2 -2
package/dist/ui/TeamPanel.js
CHANGED
|
@@ -1,148 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import Spinner from 'ink-spinner';
|
|
4
4
|
import { pickGlyphs } from './glyphs.js';
|
|
5
|
+
import { childStatus, isRunningStatus, rosterCounts, rosterHeadline, statusColor, } from './team-status.js';
|
|
5
6
|
import { formatDuration } from '../agent/usage.js';
|
|
6
|
-
import { describeToolActivity, sanitizeActivity } from '../team/activity.js';
|
|
7
|
-
import { TEAM_LIMITS } from '../team/limits.js';
|
|
8
7
|
import { buildTeamPanelLayout } from './layout/team-panel.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
run.phase === 'waiting';
|
|
12
|
-
}
|
|
13
|
-
function isSettled(run) {
|
|
14
|
-
return run.phase === 'done' || run.phase === 'failed' || run.phase === 'aborted';
|
|
15
|
-
}
|
|
16
|
-
function statusColor(run, theme) {
|
|
17
|
-
if (run.phase === 'failed' || run.phase === 'aborted')
|
|
18
|
-
return theme.toolError;
|
|
19
|
-
if (run.phase === 'done')
|
|
20
|
-
return theme.toolDone;
|
|
21
|
-
if (isRunning(run))
|
|
22
|
-
return theme.toolRunning;
|
|
23
|
-
return theme.muted;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* How many characters the activity column may spend at this width (P0-1).
|
|
27
|
-
*
|
|
28
|
-
* `TEAM_LIMITS.activityChars` is a STORAGE ceiling; a terminal has a COLUMN
|
|
29
|
-
* budget, and the two are not the same number. The row is
|
|
30
|
-
*
|
|
31
|
-
* marker(3) + label(8) + description(flex) + 2 + activity + 2 + elapsed(~6)
|
|
32
|
-
*
|
|
33
|
-
* and the comment on the description cell below promises that activity and
|
|
34
|
-
* elapsed never lose characters, i.e. that `description` absorbs ALL width
|
|
35
|
-
* pressure. A 64-character prose tail plus `writing: ` is 73, which on an
|
|
36
|
-
* 80-column terminal needs 94 columns before `description` gets one. The promise
|
|
37
|
-
* cannot be kept by wishing; the column has to be sized from `cols`.
|
|
38
|
-
*
|
|
39
|
-
* `WIDE_CHAR_ALLOWANCE` is the concession to there being no display-width helper
|
|
40
|
-
* in production code: this budget is denominated in code units while the
|
|
41
|
-
* terminal is denominated in columns, and CJK and emoji occupy two columns per
|
|
42
|
-
* unit, so the budget is halved against the reserve rather than spent to the
|
|
43
|
-
* last cell. Erring small costs a few characters of prose; erring large costs
|
|
44
|
-
* the elapsed column.
|
|
45
|
-
*/
|
|
46
|
-
const ROW_RESERVED_COLS = 3 + 8 + 2 + 2 + 8 + 12; // marker, label, gaps, elapsed, min description
|
|
47
|
-
const WIDE_CHAR_ALLOWANCE = 2;
|
|
48
|
-
export function activityBudget(cols) {
|
|
49
|
-
const spare = Math.floor((cols - ROW_RESERVED_COLS) / WIDE_CHAR_ALLOWANCE);
|
|
50
|
-
return Math.max(TEAM_LIMITS.activityMinChars, Math.min(TEAM_LIMITS.activityChars, spare));
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* The middle column: what this child is doing right now, in a few words.
|
|
54
|
-
*
|
|
55
|
-
* Two width policies, answering different questions. `wide` decides WHICH FACT
|
|
56
|
-
* to show - a whole path or its basename, a whole command line or its program
|
|
57
|
-
* name - mirroring `StatusBar`'s `statusCompactCols` degradation rather than
|
|
58
|
-
* inventing a second threshold. `activityBudget(cols)` decides HOW MUCH OF IT
|
|
59
|
-
* FITS, and it is a continuous function of width rather than a threshold,
|
|
60
|
-
* because the failure it prevents - a row wider than the terminal - is
|
|
61
|
-
* continuous.
|
|
62
|
-
*
|
|
63
|
-
* Exported for the same reason `buildSubagentTools` is: the width bound is the
|
|
64
|
-
* property this round has to keep, and asserting it through a rendered frame
|
|
65
|
-
* cannot distinguish "the arithmetic clamped it" from "Ink truncated it" - which
|
|
66
|
-
* is precisely the confusion that let v1's fixed-width column look fine at 100
|
|
67
|
-
* columns and break at 80.
|
|
68
|
-
*/
|
|
69
|
-
export function activityLine(run, cols) {
|
|
70
|
-
const wide = cols >= TEAM_LIMITS.activityWideCols;
|
|
71
|
-
const budget = activityBudget(cols);
|
|
72
|
-
switch (run.phase) {
|
|
73
|
-
case 'queued':
|
|
74
|
-
return 'queued';
|
|
75
|
-
case 'starting':
|
|
76
|
-
// The one row that legitimately restarts its clock, saying why (P2-4):
|
|
77
|
-
// F-2 ranks the running group by `startedAt` and a retry re-stamps it.
|
|
78
|
-
return run.retries ? 'starting (retry)' : 'starting';
|
|
79
|
-
case 'thinking':
|
|
80
|
-
// Clamped AT RENDER, against this terminal's budget. `run.activity` was
|
|
81
|
-
// clamped at STORE against `activityChars`; that is a ceiling, not a fit.
|
|
82
|
-
return run.activity ? `writing: ${sanitizeActivity(run.activity, budget)}` : 'thinking';
|
|
83
|
-
case 'tool':
|
|
84
|
-
case 'waiting':
|
|
85
|
-
return run.lastTool
|
|
86
|
-
? sanitizeActivity(describeToolActivity(run.lastTool, run.activityArgs, wide), budget)
|
|
87
|
-
: run.phase === 'waiting'
|
|
88
|
-
? 'waiting for mail'
|
|
89
|
-
: 'tool';
|
|
90
|
-
case 'done':
|
|
91
|
-
// `compacted N` ON THE SETTLED ROW ONLY (hardening §3.4.4 / W3). A running
|
|
92
|
-
// row is already competing for `activityBudget(cols)` with the child's own
|
|
93
|
-
// prose, and a marker that displaces what the child is DOING right now
|
|
94
|
-
// buys nothing - the dispatch report carries the same fact for good.
|
|
95
|
-
return run.compactions
|
|
96
|
-
? `done ${run.turns} turns compacted ${run.compactions}`
|
|
97
|
-
: `done ${run.turns} turns`;
|
|
98
|
-
case 'failed':
|
|
99
|
-
return 'failed';
|
|
100
|
-
case 'aborted':
|
|
101
|
-
return 'aborted';
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/** The past-tense word the supervisor badge and status line render. */
|
|
105
|
-
function interventionWord(action) {
|
|
106
|
-
switch (action) {
|
|
107
|
-
case 'nudge':
|
|
108
|
-
return 'nudged';
|
|
109
|
-
case 'replace':
|
|
110
|
-
return 'replaced';
|
|
111
|
-
case 'abandon':
|
|
112
|
-
return 'abandoned';
|
|
113
|
-
default:
|
|
114
|
-
return 'waited';
|
|
115
|
-
}
|
|
116
|
-
}
|
|
8
|
+
import { interventionWord, teamRowCells, teamRowGeometry, TEAM_CLOCK_CELLS, } from './layout/team-row.js';
|
|
9
|
+
import { fitCells } from './layout/cells.js';
|
|
117
10
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
11
|
+
* THE ACTIVITY / BADGE HELPERS LIVE IN `layout/team-row.ts` NOW (v2 implementation
|
|
12
|
+
* note, recorded in the spec's "implementation findings"). `teamRowCells` has to
|
|
13
|
+
* compose the activity text with the supervisor badge, so keeping those functions
|
|
14
|
+
* in this component would make the two modules import each other. They are
|
|
15
|
+
* re-exported here so every existing import site keeps its path.
|
|
123
16
|
*/
|
|
124
|
-
export
|
|
125
|
-
const last = run.lastIntervention;
|
|
126
|
-
if (last === undefined)
|
|
127
|
-
return '';
|
|
128
|
-
return `${interventionWord(last.action)} ${formatDuration(Math.max(0, at - last.at))}`;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* The activity column WITH the badge composed in (AC-5 / risk table): the
|
|
132
|
-
* badge rides the SAME `activityBudget(cols)` allocation the prose uses, so
|
|
133
|
-
* a row can never grow past its terminal; on a narrow terminal the badge
|
|
134
|
-
* degrades to the action word alone - the who-acted fact outranks the
|
|
135
|
-
* what-they-are-doing tail exactly when there is room for only one.
|
|
136
|
-
*/
|
|
137
|
-
export function activityWithBadge(run, cols, at) {
|
|
138
|
-
const last = run.lastIntervention;
|
|
139
|
-
if (last === undefined)
|
|
140
|
-
return activityLine(run, cols);
|
|
141
|
-
const badge = overseerBadge(run, at);
|
|
142
|
-
if (cols < TEAM_LIMITS.activityWideCols)
|
|
143
|
-
return interventionWord(last.action);
|
|
144
|
-
return sanitizeActivity(`${badge}; ${activityLine(run, cols)}`, activityBudget(cols));
|
|
145
|
-
}
|
|
17
|
+
export { activityBudget, activityLine, activityWithBadge, overseerBadge, teamClockText, teamRowCells, teamRowGeometry, } from './layout/team-row.js';
|
|
146
18
|
/**
|
|
147
19
|
* The panel's one supervisor status line (AC-5): the most recent intervention
|
|
148
20
|
* across the roster, human-phrased. Empty when the supervisor has not acted
|
|
@@ -164,6 +36,9 @@ export function supervisorStatusLine(runs, at) {
|
|
|
164
36
|
const head = latest.reasonHead.length > 0 ? ` (${latest.reasonHead})` : '';
|
|
165
37
|
return `supervisor: ${interventionWord(latest.action)} ${label} ${ago} ago${head}`;
|
|
166
38
|
}
|
|
39
|
+
/** Columns the `ctrl+b` hint cell spends, and the width below which it is dropped. */
|
|
40
|
+
const HINT_CELLS = 'ctrl+b'.length;
|
|
41
|
+
const HINT_MIN_COLS = 100;
|
|
167
42
|
export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, now, layout, }) {
|
|
168
43
|
const projection = layout ?? buildTeamPanelLayout({
|
|
169
44
|
snapshot, terminalRows: rows, availableRows: Infinity,
|
|
@@ -172,42 +47,57 @@ export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, no
|
|
|
172
47
|
return null;
|
|
173
48
|
const glyphs = pickGlyphs(caps);
|
|
174
49
|
const at = now ?? Date.now();
|
|
175
|
-
|
|
176
|
-
|
|
50
|
+
// ONE COUNTING AUTHORITY (A-1): `queued` used to fall between `isRunning` and
|
|
51
|
+
// `isSettled`, so a fresh dispatch read `0 running 0 done` above rows that each
|
|
52
|
+
// said `queued`.
|
|
53
|
+
const counts = rosterCounts(snapshot.runs);
|
|
177
54
|
const elapsed = formatDuration(Math.max(0, at - snapshot.startedAt));
|
|
178
|
-
const
|
|
55
|
+
const g = teamRowGeometry(cols, glyphs);
|
|
56
|
+
// WIDTH IS BUDGETED ONCE (I-13). `rosterHeadline` subtracts its own
|
|
57
|
+
// HEADER_RESERVED_COLS(16) - it takes the ROW's column count, not a budget - so
|
|
58
|
+
// the caller may only take away the cells this feature ADDS. Subtracting 16 here
|
|
59
|
+
// too would spend the reserve twice and drop `done` / `queued` from a 100-column
|
|
60
|
+
// header that has room for them.
|
|
61
|
+
const showHint = cols >= HINT_MIN_COLS && !projection.collapsed;
|
|
62
|
+
const headlineCols = Math.max(0, cols - TEAM_CLOCK_CELLS - (showHint ? HINT_CELLS : 0));
|
|
63
|
+
const mailText = `${glyphs.teamMail} ${snapshot.messageCount}`;
|
|
64
|
+
// The badge takes its NATURAL width and the flexible left cell gives way: it is
|
|
65
|
+
// the only header cell whose content length varies, and pinning it to a computed
|
|
66
|
+
// width would clip `@ 12` on a row that has room for it.
|
|
67
|
+
const mailCells = Math.min(mailText.length, Math.max(0, cols - TEAM_CLOCK_CELLS));
|
|
68
|
+
/**
|
|
69
|
+
* The header is the row's own geometry one level up: a flexible left cell and
|
|
70
|
+
* fixed right-hand cells, so the dispatch clock and every child clock stand in
|
|
71
|
+
* the SAME column (I-5 / AC-2).
|
|
72
|
+
*
|
|
73
|
+
* THE ELAPSED TIME APPEARS ONLY IN THE CLOCK CELL (§4.3 / RV-P2-9). The old
|
|
74
|
+
* header also carried it inline among the count clauses; with a right-edge clock
|
|
75
|
+
* that would be the same number twice, and the pair could disagree for one frame
|
|
76
|
+
* every 200 ms.
|
|
77
|
+
*/
|
|
78
|
+
const header = (_jsxs(Box, { flexDirection: "row", width: "100%", height: 1, flexShrink: 0, overflow: "hidden", children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: theme.accent, bold: true, children: [glyphs.teamAgent, ' team ', _jsx(Text, { color: theme.muted, children: rosterHeadline(counts, glyphs, headlineCols) })] }) }), showHint && (_jsx(Box, { width: HINT_CELLS, flexShrink: 0, justifyContent: "flex-end", children: _jsx(Text, { color: theme.hintFg, children: "ctrl+b" }) })), snapshot.messageCount > 0 && (_jsx(Box, { width: mailCells, flexShrink: 0, justifyContent: "flex-end", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: mailText }) })), _jsx(Box, { width: TEAM_CLOCK_CELLS, flexShrink: 0, justifyContent: "flex-end", children: _jsx(Text, { wrap: "truncate", color: theme.accent, bold: true, children: elapsed }) })] }));
|
|
179
79
|
// Short terminals or a long draft leave only the shared header projection,
|
|
180
|
-
// which still answers whether anything is running and for how long.
|
|
80
|
+
// which still answers whether anything is running and for how long. The header
|
|
81
|
+
// keeps the count clauses AND the right-edge clock, so the collapsed form loses
|
|
82
|
+
// no information - only the per-child rows.
|
|
181
83
|
if (projection.collapsed) {
|
|
182
|
-
return _jsx(Box, { flexDirection: "column", flexShrink: 0, children: header });
|
|
84
|
+
return _jsx(Box, { flexDirection: "column", width: "100%", flexShrink: 0, children: header });
|
|
183
85
|
}
|
|
184
86
|
// RANKED, not sliced by array index (F-2). The slot pool hands out indices in
|
|
185
87
|
// order, so `slice(0, 5)` showed the five children that finished FIRST and hid
|
|
186
88
|
// everything still working behind `+N more` - at exactly the fan-out widths
|
|
187
89
|
// the requirement permits.
|
|
188
90
|
const { visible, hiddenTotal, hiddenRunning } = projection;
|
|
189
|
-
|
|
190
|
-
|
|
91
|
+
const supervisorLine = supervisorStatusLine(snapshot.runs, at);
|
|
92
|
+
return (_jsxs(Box, { flexDirection: "column", width: "100%", flexShrink: 0, children: [header, visible.map((run) => {
|
|
93
|
+
const status = childStatus(run);
|
|
94
|
+
const color = statusColor(status, theme);
|
|
191
95
|
// One spinner per RUNNING row, and the row cap is 5 precisely because
|
|
192
96
|
// five is the documented maximum number of simultaneous spinners. Both
|
|
193
97
|
// `reducedMotion` and a non-Unicode terminal fall back to the same
|
|
194
98
|
// static marker, exactly as `AssistantEntry` and `ToolCard` already do.
|
|
195
|
-
const animate =
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// supervisor has actually acted - the per-row badges above already
|
|
200
|
-
// carry the per-child facts.
|
|
201
|
-
const line = supervisorStatusLine(snapshot.runs, at);
|
|
202
|
-
return line ? (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', line] })) : null;
|
|
203
|
-
})(), snapshot.lastMessage && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', glyphs.teamMail, " ", snapshot.lastMessage.from, " ", glyphs.arrowRight, " ", snapshot.lastMessage.to, ": ", snapshot.lastMessage.subject.replace(/\s+/g, ' ')] }))] }));
|
|
204
|
-
}
|
|
205
|
-
function marker(run, glyphs) {
|
|
206
|
-
if (run.phase === 'done')
|
|
207
|
-
return glyphs.toolDone;
|
|
208
|
-
if (run.phase === 'failed' || run.phase === 'aborted')
|
|
209
|
-
return glyphs.toolError;
|
|
210
|
-
if (run.phase === 'queued')
|
|
211
|
-
return glyphs.toolPending;
|
|
212
|
-
return glyphs.spinnerStill;
|
|
99
|
+
const animate = isRunningStatus(status) && !reducedMotion && caps.unicode;
|
|
100
|
+
const cells = teamRowCells(run, cols, at, glyphs);
|
|
101
|
+
return (_jsxs(Box, { flexDirection: "row", width: "100%", height: 1, flexShrink: 0, overflow: "hidden", children: [_jsx(Box, { width: g.marker, flexShrink: 0, children: _jsxs(Text, { wrap: "truncate", color: color, children: [' ', animate ? _jsx(Spinner, { type: "dots" }) : cells.marker, ' '] }) }), _jsx(Box, { width: g.label, flexShrink: 0, children: _jsx(Text, { wrap: "truncate", color: theme.primary, children: cells.label }) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: cells.description }) }), _jsx(Box, { width: g.gap, flexShrink: 0, children: _jsx(Text, { children: " " }) }), _jsx(Box, { width: g.activity, flexShrink: 0, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: color, children: cells.activity }) }), _jsx(Box, { width: g.clock, flexShrink: 0, justifyContent: "flex-end", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: cells.clock }) })] }, run.label));
|
|
102
|
+
}), hiddenTotal > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenTotal, " more", hiddenRunning > 0 ? ` (${hiddenRunning} running)` : ''] })), supervisorLine.length > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', fitCells(supervisorLine, Math.max(0, cols - 2), glyphs.ellipsis)] })), snapshot.lastMessage && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', glyphs.teamMail, " ", snapshot.lastMessage.from, " ", glyphs.arrowRight, " ", snapshot.lastMessage.to, ": ", snapshot.lastMessage.subject.replace(/\s+/g, ' ')] }))] }));
|
|
213
103
|
}
|
package/dist/ui/TodoPanel.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TodoPanel — the right rail (todo-plan-execution §6.1
|
|
2
|
+
* TodoPanel — the right rail (todo-plan-execution §6.1; two sections per
|
|
3
|
+
* todo-acceptance-criteria §6.9).
|
|
3
4
|
*
|
|
4
5
|
* ASCII ONLY: every glyph comes from `pickGlyphs`; a literal here bypasses the
|
|
5
6
|
* capability probe and shows mojibake on a legacy console (`glyphs.ts` header).
|
|
@@ -8,22 +9,24 @@
|
|
|
8
9
|
* this component is not rendered at all, so a session that never plans has zero
|
|
9
10
|
* columns and zero layout shift (R-g's "no furniture when idle" / AC-24).
|
|
10
11
|
*
|
|
12
|
+
* NO NEW PROP CARRIES THE ACCEPTANCE TABLE (§6.9.1): `snapshot.acceptance` is
|
|
13
|
+
* already inside `snapshot`, and `null` IS the projection of "the capability is
|
|
14
|
+
* off", because a disabled store commits an empty table. Keeping the props surface
|
|
15
|
+
* narrow is what keeps `App`'s two mount points untouched.
|
|
16
|
+
*
|
|
11
17
|
* IT MUST BE MOUNTED IN `AppShell`'s MIDDLE BAND, NEVER IN THE BOTTOM CHROME
|
|
12
18
|
* (C-6 / I-4). This is a COLUMN BESIDE the viewport, and the middle band is the
|
|
13
|
-
* only row-wrapped region there is; moved down into the bottom chrome it would
|
|
14
|
-
*
|
|
19
|
+
* only row-wrapped region there is; moved down into the bottom chrome it would stop
|
|
20
|
+
* being a rail and start eating viewport height, growing and shrinking the
|
|
15
21
|
* transcript every time the list changed length.
|
|
16
22
|
*
|
|
17
23
|
* WIDTH IS MEASURED IN COLUMNS, NEVER IN CODE UNITS (P2-3). This rail is 14-36
|
|
18
24
|
* columns wide and the system prompt's last line is "Respond in the language the
|
|
19
|
-
* user writes in", so CJK
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* safe; a "characters that fit" calculation over `content` would not be. That is
|
|
25
|
-
* the whole of the lesson `TeamPanel`'s `WIDE_CHAR_ALLOWANCE` records at length,
|
|
26
|
-
* and the mitigation here is to have no such budget at all.
|
|
25
|
+
* user writes in", so CJK text in an 18-column rail is the NORMAL case rather than
|
|
26
|
+
* the edge case. Every text cell is `wrap="truncate"` and Ink's truncation is
|
|
27
|
+
* display-width aware, so nothing can overflow the box - and NO ARITHMETIC HERE MAY
|
|
28
|
+
* BUDGET USER TEXT BY `.length`. The count strings and `padStart(2)` cells below are
|
|
29
|
+
* ASCII counts and indices, which is why they are safe.
|
|
27
30
|
*/
|
|
28
31
|
import React from 'react';
|
|
29
32
|
import type { Theme } from './theme.js';
|
|
@@ -35,9 +38,9 @@ export interface TodoPanelProps {
|
|
|
35
38
|
width: number;
|
|
36
39
|
/**
|
|
37
40
|
* From `buildTodoRailLayout` after actual team/menu rows are subtracted.
|
|
38
|
-
* (I-10 / P1-3). The gate that mounts this component and the number it lays
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
+
* (I-10 / P1-3). The gate that mounts this component and the number it lays out
|
|
42
|
+
* against are ONE value computed once in `App`: a panel that mounts on one number
|
|
43
|
+
* and lays out against another renders nothing and reports nothing.
|
|
41
44
|
*/
|
|
42
45
|
rows: number;
|
|
43
46
|
/** Whether the agent is running; gates the spinner (never animate when idle). */
|
package/dist/ui/TodoPanel.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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
|
-
* TodoPanel — the right rail (todo-plan-execution §6.1
|
|
3
|
+
* TodoPanel — the right rail (todo-plan-execution §6.1; two sections per
|
|
4
|
+
* todo-acceptance-criteria §6.9).
|
|
4
5
|
*
|
|
5
6
|
* ASCII ONLY: every glyph comes from `pickGlyphs`; a literal here bypasses the
|
|
6
7
|
* capability probe and shows mojibake on a legacy console (`glyphs.ts` header).
|
|
@@ -9,22 +10,24 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
10
|
* this component is not rendered at all, so a session that never plans has zero
|
|
10
11
|
* columns and zero layout shift (R-g's "no furniture when idle" / AC-24).
|
|
11
12
|
*
|
|
13
|
+
* NO NEW PROP CARRIES THE ACCEPTANCE TABLE (§6.9.1): `snapshot.acceptance` is
|
|
14
|
+
* already inside `snapshot`, and `null` IS the projection of "the capability is
|
|
15
|
+
* off", because a disabled store commits an empty table. Keeping the props surface
|
|
16
|
+
* narrow is what keeps `App`'s two mount points untouched.
|
|
17
|
+
*
|
|
12
18
|
* IT MUST BE MOUNTED IN `AppShell`'s MIDDLE BAND, NEVER IN THE BOTTOM CHROME
|
|
13
19
|
* (C-6 / I-4). This is a COLUMN BESIDE the viewport, and the middle band is the
|
|
14
|
-
* only row-wrapped region there is; moved down into the bottom chrome it would
|
|
15
|
-
*
|
|
20
|
+
* only row-wrapped region there is; moved down into the bottom chrome it would stop
|
|
21
|
+
* being a rail and start eating viewport height, growing and shrinking the
|
|
16
22
|
* transcript every time the list changed length.
|
|
17
23
|
*
|
|
18
24
|
* WIDTH IS MEASURED IN COLUMNS, NEVER IN CODE UNITS (P2-3). This rail is 14-36
|
|
19
25
|
* columns wide and the system prompt's last line is "Respond in the language the
|
|
20
|
-
* user writes in", so CJK
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* safe; a "characters that fit" calculation over `content` would not be. That is
|
|
26
|
-
* the whole of the lesson `TeamPanel`'s `WIDE_CHAR_ALLOWANCE` records at length,
|
|
27
|
-
* and the mitigation here is to have no such budget at all.
|
|
26
|
+
* user writes in", so CJK text in an 18-column rail is the NORMAL case rather than
|
|
27
|
+
* the edge case. Every text cell is `wrap="truncate"` and Ink's truncation is
|
|
28
|
+
* display-width aware, so nothing can overflow the box - and NO ARITHMETIC HERE MAY
|
|
29
|
+
* BUDGET USER TEXT BY `.length`. The count strings and `padStart(2)` cells below are
|
|
30
|
+
* ASCII counts and indices, which is why they are safe.
|
|
28
31
|
*/
|
|
29
32
|
import { useMemo } from 'react';
|
|
30
33
|
import { Box, Text } from 'ink';
|
|
@@ -34,18 +37,21 @@ import { buildGauge } from './gauge.js';
|
|
|
34
37
|
import { railBorderProps } from './layout/Gutter.js';
|
|
35
38
|
import { TODO_RAIL_INDEX_MIN_COLS } from './layout/rail.js';
|
|
36
39
|
import { TODO_LIMITS } from '../todo/limits.js';
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
import { derivePlanPhase } from '../todo/acceptance.js';
|
|
41
|
+
import { buildPlanSections } from '../todo/plan-sections.js';
|
|
42
|
+
import { selectAcceptanceRows, selectCompactAcceptanceRows, selectCompactTodoRows, selectTodoRows, } from '../todo/panel-rows.js';
|
|
40
43
|
/** The gauge is the one purely decorative element, so it is the first to go. */
|
|
41
44
|
const GAUGE_MIN_ROWS = 8;
|
|
42
45
|
const GAUGE_MIN_COLS = 20;
|
|
43
46
|
/** Separator column + one space of padding, supplied by `railBorderProps`. */
|
|
44
47
|
const CHROME_COLS = 2;
|
|
45
|
-
/** Marker cell. Three, because the ASCII tier's `[x]` / `[ ]` are three wide. */
|
|
48
|
+
/** Marker cell. Three, because the ASCII tier's `[x]` / `[ ]` / `[!]` are three wide. */
|
|
46
49
|
const MARKER_COLS = 3;
|
|
47
50
|
/** Index cell: two digits plus the space that separates it from the text. */
|
|
48
51
|
const INDEX_COLS = 3;
|
|
52
|
+
/** `ACCEPT` / `VERIFY` are both six columns, so switching phase shifts nothing (D-8). */
|
|
53
|
+
const ACCEPT_LABEL = 'ACCEPT';
|
|
54
|
+
const VERIFY_LABEL = 'VERIFY';
|
|
49
55
|
function marker(item, glyphs) {
|
|
50
56
|
// Ink's tokenizer otherwise treats the bare emoji-capable check as one cell,
|
|
51
57
|
// while string-width counts two, adding an extra visible space to each row.
|
|
@@ -56,6 +62,21 @@ function marker(item, glyphs) {
|
|
|
56
62
|
return glyphs.todoActive;
|
|
57
63
|
return glyphs.todoPending;
|
|
58
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* `passed` REUSES THE DONE GLYPH ON PURPOSE (D-9): it is the same settled positive,
|
|
67
|
+
* so two colour ramps and two shapes for one meaning would be learned twice for
|
|
68
|
+
* nothing. `failed` is the only new meaning, and it gets the only new glyph.
|
|
69
|
+
*/
|
|
70
|
+
function acceptanceMarker(item, glyphs) {
|
|
71
|
+
if (item.status === 'passed') {
|
|
72
|
+
return glyphs.todoDone.length === 1 ? `${glyphs.todoDone}\uFE0F` : glyphs.todoDone;
|
|
73
|
+
}
|
|
74
|
+
if (item.status === 'failed')
|
|
75
|
+
return glyphs.todoFail;
|
|
76
|
+
if (item.status === 'in_progress')
|
|
77
|
+
return glyphs.todoActive;
|
|
78
|
+
return glyphs.todoPending;
|
|
79
|
+
}
|
|
59
80
|
function rowColor(item, theme) {
|
|
60
81
|
if (item.status === 'completed')
|
|
61
82
|
return theme.muted;
|
|
@@ -63,44 +84,131 @@ function rowColor(item, theme) {
|
|
|
63
84
|
return theme.primary;
|
|
64
85
|
return theme.hintFg ?? theme.muted;
|
|
65
86
|
}
|
|
87
|
+
function acceptanceRowColor(item, theme) {
|
|
88
|
+
if (item.status === 'failed')
|
|
89
|
+
return theme.toolError;
|
|
90
|
+
if (item.status === 'passed')
|
|
91
|
+
return theme.muted;
|
|
92
|
+
if (item.status === 'in_progress')
|
|
93
|
+
return theme.primary;
|
|
94
|
+
return theme.hintFg ?? theme.muted;
|
|
95
|
+
}
|
|
66
96
|
export function TodoPanel({ snapshot, width, rows, running, reducedMotion, theme, caps, }) {
|
|
67
97
|
const glyphs = pickGlyphs(caps);
|
|
68
|
-
// Ink reapplies all four Yoga borders when the borderStyle object changes,
|
|
69
|
-
//
|
|
98
|
+
// Ink reapplies all four Yoga borders when the borderStyle object changes, but
|
|
99
|
+
// unchanged borderTop/Bottom=false props are not reapplied on that update.
|
|
70
100
|
const border = useMemo(() => railBorderProps(glyphs.railVertical), [glyphs.railVertical]);
|
|
71
101
|
if (snapshot.items.length === 0 || rows < TODO_LIMITS.panelMinRows)
|
|
72
102
|
return null;
|
|
73
103
|
const compact = rows < TODO_LIMITS.panelFullRows;
|
|
74
104
|
const showGauge = width >= GAUGE_MIN_COLS && rows >= GAUGE_MIN_ROWS;
|
|
75
105
|
const showIndex = !compact && width >= TODO_RAIL_INDEX_MIN_COLS;
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
106
|
+
const acceptance = snapshot.acceptance;
|
|
107
|
+
const acceptanceItems = acceptance?.items ?? [];
|
|
108
|
+
const phase = derivePlanPhase(snapshot.items, acceptanceItems);
|
|
109
|
+
// EVERY ROW FIGURE COMES FROM ONE FUNCTION (§6.9.2). Not because arithmetic in a
|
|
110
|
+
// component is hard, but because `/perf` and the status-bar chip have to report
|
|
111
|
+
// the same answer, and a second copy is a second answer.
|
|
112
|
+
const sections = buildPlanSections({
|
|
113
|
+
rows,
|
|
114
|
+
width,
|
|
115
|
+
phase,
|
|
116
|
+
todoCount: snapshot.items.length,
|
|
117
|
+
acceptanceCount: acceptanceItems.length,
|
|
118
|
+
showGauge,
|
|
119
|
+
});
|
|
120
|
+
const compactFooter = sections.todoFooter;
|
|
121
|
+
const todoWindow = compact
|
|
122
|
+
? selectCompactTodoRows(snapshot.items, sections.todoItemRows)
|
|
123
|
+
: selectTodoRows(snapshot.items, sections.todoItemRows);
|
|
124
|
+
const acceptanceSection = sections.acceptance;
|
|
125
|
+
const acceptanceWindow = acceptanceSection && acceptanceSection.itemRows > 0
|
|
126
|
+
? compact
|
|
127
|
+
? selectCompactAcceptanceRows(acceptanceItems, acceptanceSection.itemRows)
|
|
128
|
+
: selectAcceptanceRows(acceptanceItems, acceptanceSection.itemRows)
|
|
129
|
+
: { visible: [], hiddenAbove: 0, hiddenBelow: 0 };
|
|
82
130
|
const pct = snapshot.total > 0 ? Math.round((snapshot.doneCount / snapshot.total) * 100) : 0;
|
|
83
131
|
const gauge = buildGauge(pct, Math.max(4, width - CHROME_COLS - 2), theme, caps);
|
|
84
132
|
const complete = snapshot.doneCount === snapshot.total;
|
|
85
133
|
const countText = `${snapshot.doneCount}/${snapshot.total}`;
|
|
86
|
-
const showDone = complete
|
|
87
|
-
&& width - CHROME_COLS >= 'TODO '.length + countText.length + ' done'.length;
|
|
134
|
+
const showDone = complete && fitsDone(width, countText, 'TODO');
|
|
88
135
|
const count = `${countText}${showDone ? ' done' : ''}`;
|
|
136
|
+
// ONE CALL EACH: the text is both the header's right-hand cell and the width that
|
|
137
|
+
// cell is measured with, so computing it twice invites the two to drift.
|
|
138
|
+
const acceptanceCountLabel = acceptanceCountText(acceptance, width);
|
|
139
|
+
const acceptanceTone = acceptanceColor(acceptance, theme);
|
|
140
|
+
// The `summary` rung has NO `done` rung (§6.9.3): one muted line, or the error
|
|
141
|
+
// colour when something failed. Green is a verdict about a table the narrow rail is
|
|
142
|
+
// not showing, and claiming it in one line would be the only place in this feature
|
|
143
|
+
// where a colour says more than the text does.
|
|
144
|
+
const hasFailed = (acceptance?.failedCount ?? 0) > 0;
|
|
145
|
+
const acceptanceSummaryTone = hasFailed ? theme.toolError : theme.muted;
|
|
146
|
+
// `verify` collapses the step list to ONE row: every step is done, so seven rows
|
|
147
|
+
// of "completed" buy no information and cost the rows the eye needs (D-7). The
|
|
148
|
+
// marker cell is still reserved so the two sections stay column-aligned.
|
|
149
|
+
const showTodoItems = phase !== 'verify';
|
|
150
|
+
// THE `-N +M` FOOTER DESCRIBES A WINDOW THAT IS ON SCREEN. In `verify` the list is
|
|
151
|
+
// collapsed on purpose, so the marker would advertise rows the user cannot reach
|
|
152
|
+
// (`-0 +5` under "all steps complete" is the exact opposite of the sentence above
|
|
153
|
+
// it). The rows it would have occupied stay blank inside the fixed-height box.
|
|
154
|
+
const showCompactFooter = compactFooter && showTodoItems;
|
|
89
155
|
return (
|
|
90
156
|
// `flexShrink={0}` IS LOAD-BEARING (P1-4). Ink's `Box.defaultProps` is
|
|
91
|
-
// `{ flexWrap:'nowrap', flexDirection:'row', flexGrow:0, flexShrink:1 }`, so
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: width, height: rows, overflow: "hidden", paddingLeft: 1, ...border, borderColor: theme.border, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.accent, bold: true, children: "TODO" }) }), _jsx(Box, { width: count.length, flexShrink: 0, children: _jsx(Text, { wrap: "truncate", color: complete ? theme.toolDone : theme.muted, children: count }) })] }), showGauge && (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: gauge.fillColor, children: gauge.filled }), _jsx(Text, { color: gauge.trackColor, children: gauge.empty })] })), !compact && _jsx(Text, { children: " " }), !compact && hiddenAbove > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenAbove, " above"] })), visible.map(({ item, index }) => {
|
|
157
|
+
// `{ flexWrap:'nowrap', flexDirection:'row', flexGrow:0, flexShrink:1 }`, so a
|
|
158
|
+
// `width={n}` box inside a shrinking row is SQUEEZED under pressure - and every
|
|
159
|
+
// piece of arithmetic in `rail.ts` silently becomes a suggestion at exactly the
|
|
160
|
+
// narrow widths where the guarantee matters. `ScrollIndicator` spells the same
|
|
161
|
+
// pair one level down. Height uses the same row budget as selection, so content
|
|
162
|
+
// cannot silently enlarge the middle band.
|
|
163
|
+
_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: width, height: rows, overflow: "hidden", paddingLeft: 1, ...border, borderColor: theme.border, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.accent, bold: true, children: "TODO" }) }), _jsx(Box, { width: count.length, flexShrink: 0, children: _jsx(Text, { wrap: "truncate", color: complete ? theme.toolDone : theme.muted, children: count }) })] }), showGauge && (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: gauge.fillColor, children: gauge.filled }), _jsx(Text, { color: gauge.trackColor, children: gauge.empty })] })), !compact && _jsx(Text, { children: " " }), !compact && showTodoItems && todoWindow.hiddenAbove > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", todoWindow.hiddenAbove, " above"] })), showTodoItems && todoWindow.visible.map(({ item, index }) => {
|
|
98
164
|
const active = item.status === 'in_progress';
|
|
99
|
-
// The spinner is gated on `running`: a spinner while the agent is idle
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
165
|
+
// The spinner is gated on `running`: a spinner while the agent is idle is a
|
|
166
|
+
// lie about the state of the world, and this panel outlives the run that
|
|
167
|
+
// filled it. Reduced motion and a non-Unicode terminal fall back to the same
|
|
168
|
+
// static marker, exactly as `AssistantEntry` and `TeamPanel` do.
|
|
103
169
|
const animate = active && running && !reducedMotion && caps.unicode;
|
|
104
170
|
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexShrink: 0, width: MARKER_COLS, children: _jsx(Text, { color: rowColor(item, theme), bold: active, children: animate ? _jsx(Spinner, { type: "dots" }) : marker(item, glyphs) }) }), showIndex && (_jsx(Box, { flexShrink: 0, width: INDEX_COLS, children: _jsx(Text, { color: theme.muted, children: String(index + 1).padStart(2) }) })), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: rowColor(item, theme), bold: active, children: active ? item.activeForm : item.content }) })] }, `${index}-${item.content}`));
|
|
105
|
-
}),
|
|
171
|
+
}), phase === 'verify' && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "all steps complete"] })), showCompactFooter && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: ["-", todoWindow.hiddenAbove, " +", todoWindow.hiddenBelow] })), !compact && showTodoItems && todoWindow.hiddenBelow > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", todoWindow.hiddenBelow, " more"] })), acceptanceSection && acceptanceSection.reason === 'summary' && (_jsxs(Text, { wrap: "truncate", color: acceptanceSummaryTone, children: [' ', "AC ", summaryCount(acceptance)] })), acceptanceSection && acceptanceSection.reason === 'visible' && (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.accent, bold: true, children: phase === 'verify' ? VERIFY_LABEL : ACCEPT_LABEL }) }), _jsx(Box, { width: acceptanceCountLabel.length, flexShrink: 0, children: _jsx(Text, { wrap: "truncate", color: acceptanceTone, children: acceptanceCountLabel }) })] }), acceptanceWindow.visible.map(({ item, index }) => {
|
|
172
|
+
const active = item.status === 'in_progress';
|
|
173
|
+
const animate = active && running && !reducedMotion && caps.unicode;
|
|
174
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexShrink: 0, width: MARKER_COLS + 1, children: _jsx(Text, { color: acceptanceRowColor(item, theme), bold: active, children: animate ? _jsx(Spinner, { type: "dots" }) : acceptanceMarker(item, glyphs) }) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: acceptanceRowColor(item, theme), bold: active, children: item.content }) })] }, `acc-${index}-${item.content}`));
|
|
175
|
+
})] }))] }, glyphs.railVertical));
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Does the section's count fit beside its label? The same width guard the TODO
|
|
179
|
+
* header uses for ` done`, so a narrow rail drops the suffix rather than the number.
|
|
180
|
+
*/
|
|
181
|
+
function fitsDone(width, countText, label) {
|
|
182
|
+
return width - CHROME_COLS >= label.length + 1 + countText.length + ' done'.length;
|
|
183
|
+
}
|
|
184
|
+
/** `<checked>/<total>`, ` !<failed>`, and ` done` when every criterion passed. */
|
|
185
|
+
function acceptanceCountText(acceptance, width) {
|
|
186
|
+
if (!acceptance)
|
|
187
|
+
return '';
|
|
188
|
+
const base = `${acceptance.checkedCount}/${acceptance.total}`;
|
|
189
|
+
const failed = acceptance.failedCount > 0 ? ` !${acceptance.failedCount}` : '';
|
|
190
|
+
const allPassed = acceptance.checkedCount === acceptance.total && acceptance.failedCount === 0;
|
|
191
|
+
const done = allPassed && fitsDone(width, base, VERIFY_LABEL) ? ' done' : '';
|
|
192
|
+
return `${base}${failed}${done}`;
|
|
193
|
+
}
|
|
194
|
+
/** The `summary` rung's single line: `2/5`, plus ` !1` when something failed. */
|
|
195
|
+
function summaryCount(acceptance) {
|
|
196
|
+
if (!acceptance)
|
|
197
|
+
return '';
|
|
198
|
+
return `${acceptance.checkedCount}/${acceptance.total}` +
|
|
199
|
+
(acceptance.failedCount > 0 ? ` !${acceptance.failedCount}` : '');
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* All passed reads as done, any failure reads as an error, everything else is
|
|
203
|
+
* muted - the same three-rung colour ladder the TODO header uses.
|
|
204
|
+
*/
|
|
205
|
+
function acceptanceColor(acceptance, theme) {
|
|
206
|
+
if (!acceptance)
|
|
207
|
+
return theme.muted;
|
|
208
|
+
if (acceptance.failedCount > 0)
|
|
209
|
+
return theme.toolError;
|
|
210
|
+
if (acceptance.total > 0 && acceptance.checkedCount === acceptance.total) {
|
|
211
|
+
return theme.toolDone;
|
|
212
|
+
}
|
|
213
|
+
return theme.muted;
|
|
106
214
|
}
|
package/dist/ui/Transcript.js
CHANGED
|
@@ -12,6 +12,7 @@ import { QueuedEntry } from './entries/QueuedEntry.js';
|
|
|
12
12
|
import { AssistantEntry } from './entries/AssistantEntry.js';
|
|
13
13
|
import { ToolCard, statusColor, toolEntryGlyph } from './entries/ToolCard.js';
|
|
14
14
|
import { TeamCard, teamCardColor } from './entries/TeamCard.js';
|
|
15
|
+
import { isLiveTeamCard, rosterCounts } from './team-status.js';
|
|
15
16
|
import { TodoCard, todoCardColor } from './entries/TodoCard.js';
|
|
16
17
|
import { RetryCard, retryCardColor } from './entries/RetryCard.js';
|
|
17
18
|
import { FastCard, fastCardColor } from './entries/FastCard.js';
|
|
@@ -42,6 +43,12 @@ export function nowSecFor(entry, nowSec) {
|
|
|
42
43
|
// function protects is unchanged for every other card, forever.
|
|
43
44
|
if (entry.kind === 'compaction' && entry.live)
|
|
44
45
|
return nowSec;
|
|
46
|
+
// THE FOURTH CONSUMER IS A LIVE DISPATCH CARD (tui-subagent-double-roster A-1):
|
|
47
|
+
// a child's elapsed time must advance while it works, and `TeamCard` reads this
|
|
48
|
+
// same 1 Hz clock instead of a second `Date.now()` per render. A suppressed card
|
|
49
|
+
// never mounts, so this only keeps the reading honest for a directly mounted one.
|
|
50
|
+
if (entry.kind === 'team' && entry.active)
|
|
51
|
+
return nowSec;
|
|
45
52
|
// THE THIRD CONSUMER IS A LIVE JOB (long-task-jobs D-8), and it is the reason
|
|
46
53
|
// this stays a single 1 Hz clock: a task's elapsed time and its `no output`
|
|
47
54
|
// badge both have to move, and a per-card `setInterval` would be one timer per
|
|
@@ -96,10 +103,23 @@ function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reducedMotion,
|
|
|
96
103
|
// comparator sees a fresh `[]` as a changed prop, which is why that
|
|
97
104
|
// file already keeps a shared `EMPTY_LINES`.
|
|
98
105
|
live: entry.live, lastOutputAt: entry.lastOutputAt, nowSec: nowSec, theme: theme, caps: caps }));
|
|
99
|
-
case 'team':
|
|
100
|
-
|
|
106
|
+
case 'team': {
|
|
107
|
+
// THE LIVE DISPATCH HAS EXACTLY ONE SURFACE (tui-subagent-double-roster A-2):
|
|
108
|
+
// while `active`, the roster (`TeamPanel`) is the only live subagent block, so
|
|
109
|
+
// the transcript draws nothing for this entry and the card appears - once - as
|
|
110
|
+
// history the moment the dispatch settles. Returning BEFORE `frame(...)` draws
|
|
111
|
+
// neither the rail marker nor the separation row.
|
|
112
|
+
//
|
|
113
|
+
// `renderer.tsx`'s offscreen measurement goes through THIS function too, which is
|
|
114
|
+
// what makes the native print pump's `measureWindow` agree with the frame for
|
|
115
|
+
// free. `estimateEntryRows` carries the same predicate (0 rows), so no other
|
|
116
|
+
// arithmetic has to know about the suppression.
|
|
117
|
+
if (isLiveTeamCard(entry))
|
|
118
|
+
return null;
|
|
119
|
+
return frame(glyphs.teamAgent, teamCardColor(rosterCounts(entry.runs), entry.active, entry.aborted, theme), _jsx(TeamCard, { requested: entry.requested, runs: entry.runs, aborted: entry.aborted, durationMs: entry.durationMs, active: entry.active, expanded: expanded, reducedMotion: reducedMotion, theme: theme, caps: caps, nowSec: nowSecFor(entry, nowSec) }));
|
|
120
|
+
}
|
|
101
121
|
case 'todo':
|
|
102
|
-
return frame(toolGlyph(glyphs, 'todo_write'), todoCardColor(entry.doneCount, entry.total, entry.live, theme), _jsx(TodoCard, { items: entry.items, doneCount: entry.doneCount, total: entry.total, live: entry.live, interrupted: entry.interrupted, theme: theme, caps: caps }));
|
|
122
|
+
return frame(toolGlyph(glyphs, 'todo_write'), todoCardColor(entry.doneCount, entry.total, entry.live, theme), _jsx(TodoCard, { items: entry.items, doneCount: entry.doneCount, total: entry.total, acceptance: entry.acceptance ?? null, live: entry.live, interrupted: entry.interrupted, theme: theme, caps: caps }));
|
|
103
123
|
case 'retry':
|
|
104
124
|
return frame(entry.phase === 'exhausted' ? glyphs.error : glyphs.retry, retryCardColor(entry.phase, theme), _jsx(RetryCard, { attempt: entry.attempt, maxRetries: entry.maxRetries, errorType: entry.errorType, phase: entry.phase, resumeAt: entry.resumeAt, totalRetries: entry.totalRetries, elapsedMs: entry.elapsedMs,
|
|
105
125
|
// READ HERE, not threaded from `App`, and that is what keeps the memo
|