@aragon-agent/cli 0.6.35 → 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 +3173 -3071
- package/dist/agent/agent-mode.d.ts +20 -4
- package/dist/agent/agent-mode.js +20 -4
- package/dist/agent/controller.d.ts +38 -3
- package/dist/agent/controller.js +64 -4
- package/dist/agent/headless.js +31 -0
- 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 +131 -4
- 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/load.d.ts +10 -0
- package/dist/config/load.js +8 -1
- package/dist/config/schema.d.ts +68 -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/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 +310 -46
- package/dist/ui/Composer.d.ts +6 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +9 -1
- package/dist/ui/Header.js +2 -2
- package/dist/ui/PromptInput.d.ts +21 -1
- package/dist/ui/PromptInput.js +21 -1
- package/dist/ui/ScrollbackShell.d.ts +19 -19
- package/dist/ui/ScrollbackShell.js +8 -1
- package/dist/ui/StatusBar.d.ts +5 -0
- package/dist/ui/StatusBar.js +1 -0
- package/dist/ui/TeamPanel.d.ts +26 -44
- package/dist/ui/TeamPanel.js +46 -135
- package/dist/ui/TodoPanel.d.ts +17 -14
- package/dist/ui/TodoPanel.js +146 -38
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/composer-input.d.ts +10 -0
- package/dist/ui/composer-input.js +57 -24
- 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 +13 -3
- package/dist/ui/layout/header-hints.js +48 -14
- 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 +23 -3
- package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
- package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
- package/dist/ui/overlays/HelpOverlay.js +17 -2
- 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/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/band.js +14 -1
- package/dist/ui/team-status.d.ts +25 -0
- package/dist/ui/team-status.js +31 -3
- 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 +9 -1
- package/package.json +2 -2
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
|
@@ -119,7 +119,7 @@ function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reducedMotion,
|
|
|
119
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
120
|
}
|
|
121
121
|
case 'todo':
|
|
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, 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 }));
|
|
123
123
|
case 'retry':
|
|
124
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,
|
|
125
125
|
// READ HERE, not threaded from `App`, and that is what keeps the memo
|
|
@@ -3,10 +3,20 @@ export interface ComposerSubmitResult {
|
|
|
3
3
|
accepted: boolean;
|
|
4
4
|
reason?: string;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* A fill carries the prompt body; `submit` folds "insert and send" into the SAME
|
|
8
|
+
* transaction, so `Ctrl+Enter` in the picker is literally the Enter key one step
|
|
9
|
+
* later rather than a second submission path (quick-prompt-library P0-1 / I-9).
|
|
10
|
+
*/
|
|
6
11
|
export type ComposerInputIntent = InputSegment | {
|
|
7
12
|
kind: 'newline';
|
|
8
13
|
} | {
|
|
9
14
|
kind: 'submit';
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'fill';
|
|
17
|
+
text: string;
|
|
18
|
+
mode: 'insert' | 'replace';
|
|
19
|
+
submit: boolean;
|
|
10
20
|
};
|
|
11
21
|
export interface ComposerInputOptions {
|
|
12
22
|
editor: EditorState;
|
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
import { editorReducer } from './editor-reducer.js';
|
|
2
|
+
import { planPromptFill } from '../prompts/fill.js';
|
|
2
3
|
const MORE_INPUT_NOTICE = 'More input is kept in the draft; press Enter to send.';
|
|
4
|
+
/**
|
|
5
|
+
* Apply one fill to one draft. TWO CALLS PER INTENT, on purpose: the same plan runs
|
|
6
|
+
* against the accepted and the rejected draft (P0-1), so a submission the host
|
|
7
|
+
* refuses leaves the FILLED text in the composer rather than the text from before
|
|
8
|
+
* the fill. Doing it once and sharing the result would silently drop the prompt the
|
|
9
|
+
* user just chose, which is the data loss this design exists to prevent.
|
|
10
|
+
*
|
|
11
|
+
* THE REDUCER OWNS THE RESULT. `planPromptFill` says what to insert; `insert` and
|
|
12
|
+
* `replace` decide the caret, the paste-token snapping and the draft-flag reset,
|
|
13
|
+
* exactly as they do for typing.
|
|
14
|
+
*/
|
|
15
|
+
function applyFill(editor, intent) {
|
|
16
|
+
const plan = planPromptFill({
|
|
17
|
+
draft: editor.buffer,
|
|
18
|
+
cursor: editor.cursor,
|
|
19
|
+
content: intent.text,
|
|
20
|
+
mode: intent.mode,
|
|
21
|
+
});
|
|
22
|
+
return plan.mode === 'replace'
|
|
23
|
+
? editorReducer(editor, { type: 'replace', buffer: plan.text, cursor: plan.cursor })
|
|
24
|
+
: editorReducer(editor, { type: 'insert', text: plan.text });
|
|
25
|
+
}
|
|
3
26
|
/**
|
|
4
27
|
* Plan both accepted and rejected drafts before any submission side effect.
|
|
5
28
|
* Uses caller-assigned paste IDs and pure callbacks; never mutates the source editor.
|
|
@@ -13,34 +36,44 @@ export function planComposerInput(options) {
|
|
|
13
36
|
let submission;
|
|
14
37
|
let notice;
|
|
15
38
|
for (const intent of options.intents) {
|
|
16
|
-
if (intent.kind === '
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
if (intent.kind === 'fill') {
|
|
40
|
+
nextEditor = applyFill(nextEditor, intent);
|
|
41
|
+
rejectedEditor = applyFill(rejectedEditor, intent);
|
|
42
|
+
// A plain fill stops here: the draft is filled, nothing is sent (AC-16).
|
|
43
|
+
if (!intent.submit)
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
else if (intent.kind !== 'submit') {
|
|
47
|
+
const segment = intent.kind === 'newline'
|
|
48
|
+
? { kind: 'text', text: '\n' } : intent;
|
|
49
|
+
const segments = [segment];
|
|
50
|
+
if (segment.kind === 'paste') {
|
|
51
|
+
// Inline pastes have no map entry; count every paste in this callback as before.
|
|
52
|
+
acceptedPastes.push(segment);
|
|
53
|
+
rejectedPastes.push(segment);
|
|
54
|
+
const refusal = options.checkPasteLimit(acceptedPastes, acceptedBase)
|
|
55
|
+
?? options.checkPasteLimit(rejectedPastes, options.editor);
|
|
56
|
+
if (refusal) {
|
|
57
|
+
return { nextEditor: options.editor, rejectedEditor: options.editor, refusal };
|
|
58
|
+
}
|
|
26
59
|
}
|
|
60
|
+
nextEditor = editorReducer(nextEditor, { type: 'input', segments });
|
|
61
|
+
rejectedEditor = editorReducer(rejectedEditor, { type: 'input', segments });
|
|
27
62
|
continue;
|
|
28
63
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
// THE ONE SUBMIT PATH. A `fill` with `submit` and a bare `submit` reach this
|
|
65
|
+
// block and nothing else, so queueing, paste expansion and the
|
|
66
|
+
// accepted/rejected contract are inherited rather than re-implemented.
|
|
67
|
+
const text = options.resolveSubmit(nextEditor);
|
|
68
|
+
if (submission !== undefined) {
|
|
69
|
+
notice = MORE_INPUT_NOTICE;
|
|
70
|
+
}
|
|
71
|
+
else if (text !== null && text.trim().length > 0) {
|
|
72
|
+
submission = text;
|
|
73
|
+
nextEditor = editorReducer(nextEditor, { type: 'clear' });
|
|
74
|
+
acceptedBase = nextEditor;
|
|
75
|
+
acceptedPastes = [];
|
|
41
76
|
}
|
|
42
|
-
nextEditor = editorReducer(nextEditor, { type: 'input', segments });
|
|
43
|
-
rejectedEditor = editorReducer(rejectedEditor, { type: 'input', segments });
|
|
44
77
|
}
|
|
45
78
|
return { nextEditor, rejectedEditor, submission, notice };
|
|
46
79
|
}
|
|
@@ -1,25 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TodoCard — the transcript checklist entry (todo-plan-execution §6.2
|
|
2
|
+
* TodoCard — the transcript checklist entry (todo-plan-execution §6.2;
|
|
3
|
+
* todo-acceptance-criteria §6.10).
|
|
3
4
|
*
|
|
4
5
|
* ASCII ONLY: every glyph comes from `pickGlyphs`.
|
|
5
6
|
*
|
|
6
|
-
* ONE CARD PER USER TURN, REWRITTEN IN PLACE. The rail is EPHEMERAL and this is
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* ONE CARD PER USER TURN, REWRITTEN IN PLACE. The rail is EPHEMERAL and this is the
|
|
8
|
+
* HISTORY, exactly as `TeamPanel` and `TeamCard` divide the same work. It renders
|
|
9
|
+
* through the shared `EntryFrame` supplied by `EntryView`, so a plan reads as one
|
|
10
|
+
* more step in the transcript rather than a second kind of object.
|
|
10
11
|
*
|
|
11
12
|
* THE FULL LIST, NO WINDOWING (the transcript scrolls; the rail does not), and
|
|
12
|
-
* NEVER ANIMATED even while `live` — a resumed session would otherwise spin
|
|
13
|
-
*
|
|
13
|
+
* NEVER ANIMATED even while `live` — a resumed session would otherwise spin forever
|
|
14
|
+
* on a card whose run ended days ago.
|
|
15
|
+
*
|
|
16
|
+
* THE ACCEPTANCE BLOCK ADDS ROWS, NOT A CARD (§6.10): same frame, same marker
|
|
17
|
+
* column, one block underneath. Only a FAILED criterion gets an evidence line
|
|
18
|
+
* (D-14), which is what keeps the height exactly `1 + items.length + failedCount`.
|
|
14
19
|
*/
|
|
15
20
|
import React from 'react';
|
|
16
21
|
import type { Theme } from '../theme.js';
|
|
17
22
|
import type { TermCapabilities } from '../capabilities.js';
|
|
18
|
-
import type { TodoItem } from '../../todo/types.js';
|
|
23
|
+
import type { AcceptanceSnapshot, TodoItem } from '../../todo/types.js';
|
|
19
24
|
export interface TodoCardProps {
|
|
20
25
|
items: TodoItem[];
|
|
21
26
|
doneCount: number;
|
|
22
27
|
total: number;
|
|
28
|
+
/**
|
|
29
|
+
* The acceptance projection (todo-acceptance-criteria §6.10).
|
|
30
|
+
*
|
|
31
|
+
* OPTIONAL, unlike `TodoSnapshot.acceptance` (P1-4): a `TodoSnapshot` has exactly
|
|
32
|
+
* one writer and can fail closed, while this entry's sources include transcript
|
|
33
|
+
* entries read back from a session file written before the feature existed. A
|
|
34
|
+
* required field there would be typed "present" and be `undefined` at runtime.
|
|
35
|
+
*/
|
|
36
|
+
acceptance?: AcceptanceSnapshot | null;
|
|
23
37
|
live: boolean;
|
|
24
38
|
/** Saved mid-run and resumed: the run behind this card died with the process. */
|
|
25
39
|
interrupted?: boolean;
|
|
@@ -31,9 +45,10 @@ export declare function todoCardColor(doneCount: number, total: number, live: bo
|
|
|
31
45
|
declare function TodoCardImpl(props: TodoCardProps): React.ReactElement;
|
|
32
46
|
/**
|
|
33
47
|
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
34
|
-
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
35
|
-
*
|
|
36
|
-
* `
|
|
48
|
+
* `mapEntry` already preserves object identity for untouched entries, so the array
|
|
49
|
+
* props below are stable references on a settled card, and `theme` / `caps` are
|
|
50
|
+
* `useMemo`d in `App.tsx` (I-L2-1). `acceptance` is a NEW object whenever a verdict
|
|
51
|
+
* lands and the SAME reference otherwise, which is exactly what the comparator wants.
|
|
37
52
|
*/
|
|
38
53
|
export declare const TodoCard: React.MemoExoticComponent<typeof TodoCardImpl>;
|
|
39
54
|
export {};
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* TodoCard — the transcript checklist entry (todo-plan-execution §6.2
|
|
3
|
+
* TodoCard — the transcript checklist entry (todo-plan-execution §6.2;
|
|
4
|
+
* todo-acceptance-criteria §6.10).
|
|
4
5
|
*
|
|
5
6
|
* ASCII ONLY: every glyph comes from `pickGlyphs`.
|
|
6
7
|
*
|
|
7
|
-
* ONE CARD PER USER TURN, REWRITTEN IN PLACE. The rail is EPHEMERAL and this is
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* ONE CARD PER USER TURN, REWRITTEN IN PLACE. The rail is EPHEMERAL and this is the
|
|
9
|
+
* HISTORY, exactly as `TeamPanel` and `TeamCard` divide the same work. It renders
|
|
10
|
+
* through the shared `EntryFrame` supplied by `EntryView`, so a plan reads as one
|
|
11
|
+
* more step in the transcript rather than a second kind of object.
|
|
11
12
|
*
|
|
12
13
|
* 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
|
-
*
|
|
14
|
+
* NEVER ANIMATED even while `live` — a resumed session would otherwise spin forever
|
|
15
|
+
* on a card whose run ended days ago.
|
|
16
|
+
*
|
|
17
|
+
* THE ACCEPTANCE BLOCK ADDS ROWS, NOT A CARD (§6.10): same frame, same marker
|
|
18
|
+
* column, one block underneath. Only a FAILED criterion gets an evidence line
|
|
19
|
+
* (D-14), which is what keeps the height exactly `1 + items.length + failedCount`.
|
|
15
20
|
*/
|
|
16
21
|
import React from 'react';
|
|
17
22
|
import { Box, Text } from 'ink';
|
|
18
23
|
import { pickGlyphs } from '../glyphs.js';
|
|
19
24
|
import { railBorderProps } from '../layout/Gutter.js';
|
|
25
|
+
import { derivePlanPhase } from '../../todo/acceptance.js';
|
|
20
26
|
/** The card's aggregate colour: complete is done, anything else is in flight. */
|
|
21
27
|
export function todoCardColor(doneCount, total, live, theme) {
|
|
22
28
|
if (live)
|
|
@@ -39,19 +45,54 @@ function rowColor(item, theme) {
|
|
|
39
45
|
return theme.primary;
|
|
40
46
|
return theme.hintFg ?? theme.muted;
|
|
41
47
|
}
|
|
48
|
+
/** `passed` reuses the done glyph; `failed` is the one new meaning (D-9). */
|
|
49
|
+
function acceptanceMarker(item, glyphs) {
|
|
50
|
+
if (item.status === 'passed')
|
|
51
|
+
return glyphs.todoDone;
|
|
52
|
+
if (item.status === 'failed')
|
|
53
|
+
return glyphs.todoFail;
|
|
54
|
+
if (item.status === 'in_progress')
|
|
55
|
+
return glyphs.todoActive;
|
|
56
|
+
return glyphs.todoPending;
|
|
57
|
+
}
|
|
58
|
+
function acceptanceRowColor(item, theme) {
|
|
59
|
+
if (item.status === 'failed')
|
|
60
|
+
return theme.toolError;
|
|
61
|
+
if (item.status === 'passed')
|
|
62
|
+
return theme.muted;
|
|
63
|
+
if (item.status === 'in_progress')
|
|
64
|
+
return theme.primary;
|
|
65
|
+
return theme.hintFg ?? theme.muted;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* All passed reads as done, any failure as an error, otherwise muted; while the run
|
|
69
|
+
* is live and the plan is in VERIFY it reads as running - the same expression family
|
|
70
|
+
* `todoCardColor` uses one level up.
|
|
71
|
+
*/
|
|
72
|
+
function acceptanceTitleColor(acceptance, items, live, theme) {
|
|
73
|
+
if (acceptance.failedCount > 0)
|
|
74
|
+
return theme.toolError;
|
|
75
|
+
if (live && derivePlanPhase(items, acceptance.items) === 'verify')
|
|
76
|
+
return theme.toolRunning;
|
|
77
|
+
if (acceptance.total > 0 && acceptance.checkedCount === acceptance.total)
|
|
78
|
+
return theme.toolDone;
|
|
79
|
+
return theme.muted;
|
|
80
|
+
}
|
|
42
81
|
function TodoCardImpl(props) {
|
|
43
|
-
const { items, doneCount, total, live, interrupted, theme, caps } = props;
|
|
82
|
+
const { items, doneCount, total, acceptance, live, interrupted, theme, caps } = props;
|
|
44
83
|
const glyphs = pickGlyphs(caps);
|
|
84
|
+
const criteria = acceptance && acceptance.total > 0 ? acceptance : null;
|
|
45
85
|
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 && (
|
|
46
|
-
// A resumed `2/7` would otherwise read as a run still in flight
|
|
47
|
-
//
|
|
48
|
-
//
|
|
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}`))) })] }));
|
|
86
|
+
// A resumed `2/7` would otherwise read as a run still in flight (P2-6).
|
|
87
|
+
// Saying so plainly is the same fix `TeamCard` makes for a dispatch that
|
|
88
|
+
// never got a duration.
|
|
89
|
+
_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}`))) }), criteria && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: theme.accent, bold: true, children: "Acceptance" }), _jsxs(Text, { color: acceptanceTitleColor(criteria, items, live, theme), children: [' ', criteria.checkedCount, "/", criteria.total, " checked", criteria.failedCount > 0 ? ` !${criteria.failedCount} failed` : ''] })] }), _jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, ...railBorderProps(glyphs.railVertical, theme.border), children: criteria.items.map((item, index) => (_jsxs(React.Fragment, { children: [_jsxs(Text, { wrap: "truncate", color: acceptanceRowColor(item, theme), children: [acceptanceMarker(item, glyphs).padEnd(3), item.content] }), item.status === 'failed' && item.evidence.length > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', item.evidence] }))] }, `acc-${index}-${item.content}`))) })] }))] }));
|
|
50
90
|
}
|
|
51
91
|
/**
|
|
52
92
|
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
53
|
-
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
54
|
-
*
|
|
55
|
-
* `
|
|
93
|
+
* `mapEntry` already preserves object identity for untouched entries, so the array
|
|
94
|
+
* props below are stable references on a settled card, and `theme` / `caps` are
|
|
95
|
+
* `useMemo`d in `App.tsx` (I-L2-1). `acceptance` is a NEW object whenever a verdict
|
|
96
|
+
* lands and the SAME reference otherwise, which is exactly what the comparator wants.
|
|
56
97
|
*/
|
|
57
98
|
export const TodoCard = React.memo(TodoCardImpl);
|
package/dist/ui/glyphs.d.ts
CHANGED
|
@@ -86,6 +86,15 @@ export interface Glyphs {
|
|
|
86
86
|
scrollThumb: string;
|
|
87
87
|
/** Redaction dot for `maskSecret` in the settings screen (§4.1 tier B). */
|
|
88
88
|
maskDot: string;
|
|
89
|
+
/**
|
|
90
|
+
* The Agents board's selection cursor (tui-subagent-panel D-14).
|
|
91
|
+
*
|
|
92
|
+
* BYTE-IDENTICAL to `todoActive`, and still a field of its own: the two carry
|
|
93
|
+
* different meanings - "the item the run is on" versus "the child this panel is
|
|
94
|
+
* showing" - and reusing the todo glyph would restyle the board the day someone
|
|
95
|
+
* redraws the TODO rail. `scrollTrack` / `scrollThumb` above are the same precedent.
|
|
96
|
+
*/
|
|
97
|
+
cursor: string;
|
|
89
98
|
/**
|
|
90
99
|
* Team mode (team-subagents §6.1). The rail marker for a dispatch card and
|
|
91
100
|
* the prefix on the panel's incoming-message preview.
|
|
@@ -108,6 +117,16 @@ export interface Glyphs {
|
|
|
108
117
|
todoPending: string;
|
|
109
118
|
todoActive: string;
|
|
110
119
|
todoDone: string;
|
|
120
|
+
/**
|
|
121
|
+
* The ONLY glyph this feature adds (todo-acceptance-criteria D-9): `passed` is a
|
|
122
|
+
* settled positive and reuses `todoDone`, while `failed` is a genuinely new
|
|
123
|
+
* meaning.
|
|
124
|
+
*
|
|
125
|
+
* The ASCII tier must still tell "done" from "failed", so `[!]` joins `[x]` - the
|
|
126
|
+
* same three columns, which is what keeps the mixed-width list aligned with no
|
|
127
|
+
* per-tier branch.
|
|
128
|
+
*/
|
|
129
|
+
todoFail: string;
|
|
111
130
|
/**
|
|
112
131
|
* API retry (llm-api-retry-backoff §6.5). The rail marker on the retry card.
|
|
113
132
|
*
|
package/dist/ui/glyphs.js
CHANGED
|
@@ -60,12 +60,14 @@ const UNICODE_GLYPHS = {
|
|
|
60
60
|
scrollTrack: '│',
|
|
61
61
|
scrollThumb: '█',
|
|
62
62
|
maskDot: '•',
|
|
63
|
+
cursor: '▸',
|
|
63
64
|
boxStyle: 'round',
|
|
64
65
|
teamAgent: '◆',
|
|
65
66
|
teamMail: '⇄',
|
|
66
67
|
todoPending: '○',
|
|
67
68
|
todoActive: '▸',
|
|
68
69
|
todoDone: '✓',
|
|
70
|
+
todoFail: '✗',
|
|
69
71
|
retry: '↻',
|
|
70
72
|
compaction: '⤓',
|
|
71
73
|
queued: '◷',
|
|
@@ -124,12 +126,14 @@ const ASCII_GLYPHS = {
|
|
|
124
126
|
scrollTrack: '|',
|
|
125
127
|
scrollThumb: '#',
|
|
126
128
|
maskDot: '*',
|
|
129
|
+
cursor: '>',
|
|
127
130
|
boxStyle: 'classic',
|
|
128
131
|
teamAgent: '*',
|
|
129
132
|
teamMail: '@',
|
|
130
133
|
todoPending: '[ ]',
|
|
131
134
|
todoActive: '>',
|
|
132
135
|
todoDone: '[x]',
|
|
136
|
+
todoFail: '[!]',
|
|
133
137
|
retry: '[r]',
|
|
134
138
|
compaction: '[c]',
|
|
135
139
|
queued: '*',
|