@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
|
@@ -49,6 +49,34 @@ export const HEADER_HINT_LIMITS = {
|
|
|
49
49
|
/** The verbatim clause text (spec 3.8, appendix A). */
|
|
50
50
|
const BRANCH_CLAUSE = { collapsed: '^L branch', expanded: '^L new branch' };
|
|
51
51
|
const INDEX_CLAUSE = { collapsed: '^I Index', expanded: '^I build index' };
|
|
52
|
+
/**
|
|
53
|
+
* THE KIT CLAUSE HAS NO COLLAPSED FORM (agent-model-kits D-11).
|
|
54
|
+
*
|
|
55
|
+
* The requirement says the `ctrl+k` hint appears AFTER `ctrl+G`; the collapsed
|
|
56
|
+
* row is the narrowest budget in the header, and `^K` is a low-frequency action.
|
|
57
|
+
* Spending 13 columns of the everyday row on it would push `provider:model`
|
|
58
|
+
* leftward on every launch for a key pressed twice a session.
|
|
59
|
+
*/
|
|
60
|
+
const KIT_CLAUSE = { expanded: '^K switch kit' };
|
|
61
|
+
/**
|
|
62
|
+
* THE PROMPT LIBRARY KEY HAS EXACTLY ONE FORM, AND IT IS THE SHORT ONE
|
|
63
|
+
* (quick-prompt-library D-15).
|
|
64
|
+
*
|
|
65
|
+
* `^P` is a REQUIREMENT (the user must be able to see that `ctrl+P` exists), and
|
|
66
|
+
* the collapsed row is where that requirement is met. The expanded row's job is to
|
|
67
|
+
* reveal MORE keys than the collapsed one, so inserting a 17-cell
|
|
68
|
+
* `^P prompt library` before `^I` there would trade `^I` and `^K` away on an
|
|
69
|
+
* 80-column terminal - a user who pressed `^G` asking for more would be shown less.
|
|
70
|
+
*
|
|
71
|
+
* The second thing this constant's position encodes is an explicit REVISION of the
|
|
72
|
+
* `RV-P1-2` ordering constraint for the COLLAPSED ladder: `^P` sits before `^I`
|
|
73
|
+
* there, so `^I Index` now appears from 66 columns instead of 54. That is a
|
|
74
|
+
* deliberate, documented loss: `^I` is a once-per-project action and `^P` is the
|
|
75
|
+
* entry point this feature is about. The expanded ladder keeps its full existing
|
|
76
|
+
* order, so `RV-P1-2` still holds there and `^I` never disappears from a row where
|
|
77
|
+
* it used to fit.
|
|
78
|
+
*/
|
|
79
|
+
const PROMPTS_CLAUSE = '^P prompts';
|
|
52
80
|
const DETAIL_CLAUSE = { collapsed: '^G more', expanded: '^G less' };
|
|
53
81
|
/** L3 gives the key name and nothing else: discoverability at the last width. */
|
|
54
82
|
const BRANCH_KEY_ONLY = '^L';
|
|
@@ -60,8 +88,9 @@ const SCROLL_CHIP_MAX = 9999;
|
|
|
60
88
|
/**
|
|
61
89
|
* Plan the right-hand hints.
|
|
62
90
|
*
|
|
63
|
-
* The
|
|
64
|
-
* does not change, only the arithmetic behind it
|
|
91
|
+
* The inputs are the props `Header` receives plus the two switches that decide which
|
|
92
|
+
* clauses exist at all - the call site does not change, only the arithmetic behind it
|
|
93
|
+
* (spec 3.11 step 5).
|
|
65
94
|
*/
|
|
66
95
|
export function planHeaderHints(input) {
|
|
67
96
|
// NORMALISED HERE, NOT TRUSTED (R-14). `Header` defaults to 80, but a test or a
|
|
@@ -77,7 +106,7 @@ export function planHeaderHints(input) {
|
|
|
77
106
|
const detail = input.statusExpanded ? DETAILS_ON : DETAILS_OFF;
|
|
78
107
|
return buildPlan([{ id: 'detail', text: detail }], scrollCells, true);
|
|
79
108
|
}
|
|
80
|
-
const ladder = ladderFor(input.statusExpanded);
|
|
109
|
+
const ladder = ladderFor(input.statusExpanded, input.kitHint !== false, input.promptsHint !== false);
|
|
81
110
|
// THE SEPARATOR IS SPENT OUT OF THE SAME ROW (defect 12): left half + gap + this
|
|
82
111
|
// column have to add up to `columns`, so the gap comes off the budget instead of
|
|
83
112
|
// being assumed free.
|
|
@@ -94,8 +123,18 @@ export function planHeaderHints(input) {
|
|
|
94
123
|
// shrinks (to zero), and `overflow="hidden"` keeps that from overflowing.
|
|
95
124
|
return buildPlan(ladder[ladder.length - 1] ?? [], scrollCells, false);
|
|
96
125
|
}
|
|
97
|
-
/**
|
|
98
|
-
|
|
126
|
+
/**
|
|
127
|
+
* The rungs, widest first. Built per call because the widths are text-derived.
|
|
128
|
+
*
|
|
129
|
+
* THE EXPANDED ORDER `branch -> index -> kit -> detail` IS A REGRESSION
|
|
130
|
+
* CONSTRAINT, NOT A PREFERENCE (RV-P1-2). With `kit` AFTER `index`, rungs E2,
|
|
131
|
+
* E4, E5 and E6 are exactly today's four expanded rungs, so every existing
|
|
132
|
+
* width assertion for 48-78 columns keeps its old answer and `^I` never
|
|
133
|
+
* disappears from a row where it used to fit. Putting `^K` first would have cost
|
|
134
|
+
* `^I` everything from 64 to 78 columns - a permanent loss, paid for a
|
|
135
|
+
* low-frequency key.
|
|
136
|
+
*/
|
|
137
|
+
function ladderFor(statusExpanded, kitHint, promptsHint) {
|
|
99
138
|
const branch = {
|
|
100
139
|
id: 'branch',
|
|
101
140
|
text: statusExpanded ? BRANCH_CLAUSE.expanded : BRANCH_CLAUSE.collapsed,
|
|
@@ -108,12 +147,39 @@ function ladderFor(statusExpanded) {
|
|
|
108
147
|
id: 'detail',
|
|
109
148
|
text: statusExpanded ? DETAIL_CLAUSE.expanded : DETAIL_CLAUSE.collapsed,
|
|
110
149
|
};
|
|
111
|
-
|
|
150
|
+
const branchKey = { id: 'branch', text: BRANCH_KEY_ONLY };
|
|
151
|
+
const prompts = { id: 'prompts', text: PROMPTS_CLAUSE };
|
|
152
|
+
const kit = { id: 'kit', text: KIT_CLAUSE.expanded };
|
|
153
|
+
// THE LADDERS ARE WRITTEN WITH EVERY CLAUSE IN PLACE AND THEN FILTERED, rather
|
|
154
|
+
// than branched per width. Two independently written tables drift: the day one of
|
|
155
|
+
// them gains a rung, the `prompts.hint=false` / `kitHint=false` degradations stop
|
|
156
|
+
// being "the same row minus that clause" and nothing would notice. Filtering a
|
|
157
|
+
// single table makes that property structural.
|
|
158
|
+
const keep = (clause) => (promptsHint || clause.id !== 'prompts') && (kitHint || clause.id !== 'kit');
|
|
159
|
+
// COLLAPSED (six rungs). `^P prompts` sits between the branch and the index, and
|
|
160
|
+
// the third rung is where `^L` loses its label in favour of `^P` - see the note
|
|
161
|
+
// on `PROMPTS_CLAUSE` above for the revision of `RV-P1-2` this encodes.
|
|
162
|
+
const collapsed = [
|
|
163
|
+
[branch, prompts, index, detail],
|
|
164
|
+
[branch, prompts, detail],
|
|
165
|
+
[branchKey, prompts, detail],
|
|
166
|
+
[branch, detail],
|
|
167
|
+
[branchKey, detail],
|
|
168
|
+
[detail],
|
|
169
|
+
];
|
|
170
|
+
// EXPANDED (seven rungs). `^P` appears in the WIDEST rung only, and AFTER `^K`,
|
|
171
|
+
// so E2..E7 - the rungs that decide what a 34..90 column terminal shows - are
|
|
172
|
+
// byte-identical to the pre-feature answers.
|
|
173
|
+
const expanded = [
|
|
174
|
+
[branch, index, kit, prompts, detail],
|
|
175
|
+
[branch, index, kit, detail],
|
|
112
176
|
[branch, index, detail],
|
|
177
|
+
[branch, kit, detail],
|
|
113
178
|
[branch, detail],
|
|
114
|
-
[
|
|
179
|
+
[branchKey, detail],
|
|
115
180
|
[detail],
|
|
116
181
|
];
|
|
182
|
+
return (statusExpanded ? expanded : collapsed).map((rung) => rung.filter(keep));
|
|
117
183
|
}
|
|
118
184
|
/**
|
|
119
185
|
* Compose the final string. The chip carries its OWN trailing spacing and is the
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The status bar's kit chip: pure width planning, no JSX
|
|
3
|
+
* (agent-model-kits §4.8 / D-12).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
6
|
+
*
|
|
7
|
+
* WHY A PLANNER, AND WHY IT IS THE SAME SHAPE AS `session-chip.ts`: the status
|
|
8
|
+
* row is measured cell by cell, and anything appended "just to the right" eats
|
|
9
|
+
* `phase` - the one field a user needs while a run is in flight. The chip
|
|
10
|
+
* therefore competes for the SAME budget as every other field, and the rung that
|
|
11
|
+
* survives is computed rather than hand-tuned.
|
|
12
|
+
*
|
|
13
|
+
* ONE COMPARISON, NOT A THRESHOLD TABLE: `columns - (cells + gap) >=
|
|
14
|
+
* minStatusCells`. The width table in the spec is what that comparison WORKS
|
|
15
|
+
* OUT TO. `minStatusCells` is deliberately the same 34 as the session chip's -
|
|
16
|
+
* both answer "how much room must the left half keep", and two numbers for one
|
|
17
|
+
* question is how a "fit" and a "don't fit" end up disagreeing.
|
|
18
|
+
*/
|
|
19
|
+
export declare const KIT_CHIP_LIMITS: {
|
|
20
|
+
/** Blank cells between the chip and the field run. */
|
|
21
|
+
readonly gapCells: 2;
|
|
22
|
+
/** The floor the left half must keep; asserted equal to the session chip's. */
|
|
23
|
+
readonly minStatusCells: 34;
|
|
24
|
+
/** Below this the `kit` label is dropped before the name is shortened. */
|
|
25
|
+
readonly labelMinCols: 72;
|
|
26
|
+
/** The shortest unique prefix worth showing; below it the chip hides. */
|
|
27
|
+
readonly minNameChars: 4;
|
|
28
|
+
};
|
|
29
|
+
export interface KitChipPlan {
|
|
30
|
+
visible: boolean;
|
|
31
|
+
text: string;
|
|
32
|
+
cells: number;
|
|
33
|
+
gap: number;
|
|
34
|
+
}
|
|
35
|
+
export interface KitChipInput {
|
|
36
|
+
columns: number;
|
|
37
|
+
/**
|
|
38
|
+
* The kit in effect, or absent/null when there is none.
|
|
39
|
+
*
|
|
40
|
+
* ABSENT IS NOT EMPTY: with no kit the plan is hidden and the row's output is
|
|
41
|
+
* byte-identical to the pre-feature build (I-9).
|
|
42
|
+
*/
|
|
43
|
+
kit?: {
|
|
44
|
+
name: string;
|
|
45
|
+
modified: boolean;
|
|
46
|
+
otherNames: readonly string[];
|
|
47
|
+
} | null | undefined;
|
|
48
|
+
}
|
|
49
|
+
/** Plan the chip for `columns` and this session's kit. */
|
|
50
|
+
export declare function planKitChip(input: KitChipInput): KitChipPlan;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The status bar's kit chip: pure width planning, no JSX
|
|
3
|
+
* (agent-model-kits §4.8 / D-12).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
6
|
+
*
|
|
7
|
+
* WHY A PLANNER, AND WHY IT IS THE SAME SHAPE AS `session-chip.ts`: the status
|
|
8
|
+
* row is measured cell by cell, and anything appended "just to the right" eats
|
|
9
|
+
* `phase` - the one field a user needs while a run is in flight. The chip
|
|
10
|
+
* therefore competes for the SAME budget as every other field, and the rung that
|
|
11
|
+
* survives is computed rather than hand-tuned.
|
|
12
|
+
*
|
|
13
|
+
* ONE COMPARISON, NOT A THRESHOLD TABLE: `columns - (cells + gap) >=
|
|
14
|
+
* minStatusCells`. The width table in the spec is what that comparison WORKS
|
|
15
|
+
* OUT TO. `minStatusCells` is deliberately the same 34 as the session chip's -
|
|
16
|
+
* both answer "how much room must the left half keep", and two numbers for one
|
|
17
|
+
* question is how a "fit" and a "don't fit" end up disagreeing.
|
|
18
|
+
*/
|
|
19
|
+
import stringWidth from 'string-width';
|
|
20
|
+
export const KIT_CHIP_LIMITS = {
|
|
21
|
+
/** Blank cells between the chip and the field run. */
|
|
22
|
+
gapCells: 2,
|
|
23
|
+
/** The floor the left half must keep; asserted equal to the session chip's. */
|
|
24
|
+
minStatusCells: 34,
|
|
25
|
+
/** Below this the `kit` label is dropped before the name is shortened. */
|
|
26
|
+
labelMinCols: 72,
|
|
27
|
+
/** The shortest unique prefix worth showing; below it the chip hides. */
|
|
28
|
+
minNameChars: 4,
|
|
29
|
+
};
|
|
30
|
+
const HIDDEN = { visible: false, text: '', cells: 0, gap: 0 };
|
|
31
|
+
/** The shortest prefix of `name` that no sibling name also starts with. */
|
|
32
|
+
function uniqueLength(name, others) {
|
|
33
|
+
const points = [...name];
|
|
34
|
+
const start = Math.min(KIT_CHIP_LIMITS.minNameChars, points.length);
|
|
35
|
+
const shared = (length) => {
|
|
36
|
+
const prefix = points.slice(0, length).join('');
|
|
37
|
+
return others.some((other) => [...other].slice(0, length).join('') === prefix);
|
|
38
|
+
};
|
|
39
|
+
let length = start;
|
|
40
|
+
while (length < points.length && shared(length))
|
|
41
|
+
length += 1;
|
|
42
|
+
return Math.max(length, start);
|
|
43
|
+
}
|
|
44
|
+
/** Plan the chip for `columns` and this session's kit. */
|
|
45
|
+
export function planKitChip(input) {
|
|
46
|
+
const kit = input.kit;
|
|
47
|
+
if (!kit)
|
|
48
|
+
return HIDDEN;
|
|
49
|
+
const name = kit.name.trim();
|
|
50
|
+
if (!name)
|
|
51
|
+
return HIDDEN;
|
|
52
|
+
const columns = Number.isFinite(input.columns) ? Math.floor(input.columns) : 80;
|
|
53
|
+
const gap = KIT_CHIP_LIMITS.gapCells;
|
|
54
|
+
const fits = (text) => columns - (stringWidth(text) + gap) >= KIT_CHIP_LIMITS.minStatusCells;
|
|
55
|
+
const mark = kit.modified ? '~' : '';
|
|
56
|
+
const plan = (text) => ({ visible: true, text, cells: stringWidth(text), gap });
|
|
57
|
+
// Widest rung first, then drop the label, then shorten the name, then hide.
|
|
58
|
+
// THE ORDER IS THE DECISION: a reader cares WHICH kit is in effect before they
|
|
59
|
+
// care that it is a kit, so the name is the last thing to go.
|
|
60
|
+
//
|
|
61
|
+
// `KIT_CHIP_LIMITS.labelMinCols` NEEDS NO BRANCH: a labelled rung is 45 cells
|
|
62
|
+
// for a 40-character name, so `fits` already rejects it below ~81 columns. The
|
|
63
|
+
// constant is the documented intent (labels belong to wide rows), measured
|
|
64
|
+
// rather than hard-coded into the ladder.
|
|
65
|
+
const points = [...name];
|
|
66
|
+
const shortest = uniqueLength(name, kit.otherNames);
|
|
67
|
+
const rungs = [];
|
|
68
|
+
if (kit.modified)
|
|
69
|
+
rungs.push(`kit ${name}~`);
|
|
70
|
+
rungs.push(`kit ${name}`);
|
|
71
|
+
if (kit.modified)
|
|
72
|
+
rungs.push(`${name}~`);
|
|
73
|
+
rungs.push(name);
|
|
74
|
+
for (let length = points.length - 1; length >= shortest; length -= 1) {
|
|
75
|
+
rungs.push(`${points.slice(0, length).join('')}${mark}`);
|
|
76
|
+
}
|
|
77
|
+
for (const text of rungs)
|
|
78
|
+
if (fits(text))
|
|
79
|
+
return plan(text);
|
|
80
|
+
return HIDDEN;
|
|
81
|
+
}
|
|
@@ -55,6 +55,15 @@ export function planStatusDetail(input) {
|
|
|
55
55
|
add(statusField('team', 'Team ' + status.teamActive.running + '/' + status.teamActive.total));
|
|
56
56
|
if (status.todoActive)
|
|
57
57
|
add(statusField('todo', 'Todo ' + status.todoActive.done + '/' + status.todoActive.total));
|
|
58
|
+
// AFTER `todo`, AND THE ORDER IS THE PRIORITY (D-12): `add()` only appends while
|
|
59
|
+
// the whole row still fits, so a later field is dropped FIRST. `Todo 3/7` is the
|
|
60
|
+
// more important of the two numbers, so it goes first and survives the narrower
|
|
61
|
+
// terminal; `Accept 2/5` degrades before it.
|
|
62
|
+
if (status.acceptanceActive) {
|
|
63
|
+
const acceptance = status.acceptanceActive;
|
|
64
|
+
add(statusField('acceptance', 'Accept ' + acceptance.checked + '/' + acceptance.total
|
|
65
|
+
+ (acceptance.failed > 0 ? ' !' + acceptance.failed : ''), acceptance.failed > 0 ? 'error' : 'normal'));
|
|
66
|
+
}
|
|
58
67
|
add(statusField('model', input.provider + ':' + input.model));
|
|
59
68
|
if (status.fastActive)
|
|
60
69
|
add(statusField('fast', 'Fast' + (status.fastActive.inFlight ? ' active' : '')));
|
|
@@ -51,6 +51,19 @@ export interface StatusLayoutInput {
|
|
|
51
51
|
done: number;
|
|
52
52
|
total: number;
|
|
53
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* The acceptance counter (todo-acceptance-criteria §6.11).
|
|
56
|
+
*
|
|
57
|
+
* A FALLBACK, exactly like `todoActive`: present only when the rail is NOT already
|
|
58
|
+
* reporting the number, because printing `2/5` twice in one row is the defect
|
|
59
|
+
* RV-P2-4 records. Whether it is present is decided by `acceptanceChip()` in
|
|
60
|
+
* `ui/layout/todo-layout.ts`, which owns "what did the rail actually draw".
|
|
61
|
+
*/
|
|
62
|
+
acceptanceActive?: {
|
|
63
|
+
checked: number;
|
|
64
|
+
total: number;
|
|
65
|
+
failed: number;
|
|
66
|
+
};
|
|
54
67
|
ecoRung?: number;
|
|
55
68
|
fastActive?: {
|
|
56
69
|
inFlight: boolean;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel row geometry — the ONE authority for how a subagent row is measured and
|
|
3
|
+
* what it says (tui-subagent-panel §4.2 / §6.1).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS FILE EXISTS. The roster used to place its right-hand columns by
|
|
6
|
+
* `flexGrow`, and a fixed-height parent box makes Yoga stretch the column to its
|
|
7
|
+
* widest CHILD instead of to the available width: on a 100-column terminal inside
|
|
8
|
+
* the band slot the row measured 48 (spec §0.1, cases 3/4), so `grep 22.1s` sat
|
|
9
|
+
* hard against the description and the right half of the row was blank. Flex
|
|
10
|
+
* cannot fix that, because the number it needs - "how wide is this terminal" - is
|
|
11
|
+
* not part of the layout it is given. So the widths are COMPUTED here, by a total
|
|
12
|
+
* pure function, and the component only places fixed-width boxes at the offsets
|
|
13
|
+
* this module names.
|
|
14
|
+
*
|
|
15
|
+
* THE FIVE CELLS, in drawing order:
|
|
16
|
+
*
|
|
17
|
+
* marker(1+status+1) label(8) description(flex) gap(2) activity(n) clock(8)
|
|
18
|
+
*
|
|
19
|
+
* `description` is the ONLY flexible cell, and it is the one that absorbs every
|
|
20
|
+
* width pressure - the same degradation discipline `StatusBar` documents for its
|
|
21
|
+
* left cluster. The two right-hand cells are fixed and `flexShrink={0}`, so their
|
|
22
|
+
* absolute offsets follow from `rowWidth = parentWidth` and the sum of the fixed
|
|
23
|
+
* cells in front of them, and never from how a particular frame allocates slack.
|
|
24
|
+
*
|
|
25
|
+
* ASCII ONLY: `src/ui/**` is inside `glyphs.test.ts`'s static scan.
|
|
26
|
+
*
|
|
27
|
+
* THE ACTIVITY HELPERS LIVE HERE TOO (v2 implementation note, recorded in the
|
|
28
|
+
* spec's "implementation findings"). `teamRowCells` has to compose the activity
|
|
29
|
+
* text with the supervisor badge, which is what `activityWithBadge` already does;
|
|
30
|
+
* leaving those in `TeamPanel.tsx` would make this module and the component
|
|
31
|
+
* import each other. `TeamPanel` therefore RE-EXPORTS every symbol below, so the
|
|
32
|
+
* existing import sites (three test files) keep their import path unchanged.
|
|
33
|
+
*/
|
|
34
|
+
import type { OverseerAction, SubagentRun } from '../../team/types.js';
|
|
35
|
+
import type { Glyphs } from '../glyphs.js';
|
|
36
|
+
/** The clock cell: wide enough for `125m07s` (7) with one column of headroom. */
|
|
37
|
+
export declare const TEAM_CLOCK_CELLS = 8;
|
|
38
|
+
/** `run.label.padEnd(8).slice(0,8)` — unchanged from the row this replaces. */
|
|
39
|
+
export declare const TEAM_LABEL_CELLS = 8;
|
|
40
|
+
/** The two spaces in front of the activity cell. */
|
|
41
|
+
export declare const TEAM_GAP_CELLS = 2;
|
|
42
|
+
/**
|
|
43
|
+
* The description column's floor: below this the row is unreadable, so the
|
|
44
|
+
* activity cell gives way instead (`activityDropCols`).
|
|
45
|
+
*/
|
|
46
|
+
export declare const TEAM_MIN_DESCRIPTION_CELLS = 12;
|
|
47
|
+
export interface TeamRowGeometry {
|
|
48
|
+
marker: number;
|
|
49
|
+
label: number;
|
|
50
|
+
gap: number;
|
|
51
|
+
activity: number;
|
|
52
|
+
clock: number;
|
|
53
|
+
/** The description column's reserved MINIMUM; the real width comes from flex. */
|
|
54
|
+
minDescription: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* How many characters the activity cell may spend at this width.
|
|
58
|
+
*
|
|
59
|
+
* `TEAM_LIMITS.activityChars` is a STORAGE ceiling; a terminal has a COLUMN
|
|
60
|
+
* budget, and the two are not the same number. Below
|
|
61
|
+
* `TEAM_LIMITS.activityDropCols` the cell gives way ENTIRELY (returns 0) rather
|
|
62
|
+
* than shrinking to its 12-character floor: at 40 columns those twelve characters
|
|
63
|
+
* would push the description below its own floor, and "who is doing what" outranks
|
|
64
|
+
* "what they are writing right now" exactly when there is room for one of them.
|
|
65
|
+
* That is a semantic change to a shipped assertion (`activityBudget(40)` used to
|
|
66
|
+
* return 12) and is recorded in the spec's §10 collateral table.
|
|
67
|
+
*
|
|
68
|
+
* `markerCells` defaults to the Unicode tier so the three existing single-argument
|
|
69
|
+
* call sites keep compiling; the panel passes the real tier's width.
|
|
70
|
+
*/
|
|
71
|
+
export declare function activityBudget(cols: number, markerCells?: number): number;
|
|
72
|
+
/**
|
|
73
|
+
* The row's cell widths. A TOTAL function: every cell is a non-negative integer
|
|
74
|
+
* and the five of them never sum past `cols`, for any `cols >= 0` and either
|
|
75
|
+
* glyph tier (I-3, `team-row-geometry.test.ts`'s property test).
|
|
76
|
+
*
|
|
77
|
+
* THE LADDER IS THE SPECIFICATION (§4.2.2), in the order the cells give way:
|
|
78
|
+
* 1. a wide enough terminal gets the business budget;
|
|
79
|
+
* 2. below `activityDropCols` the activity cell is 0;
|
|
80
|
+
* 3. description still negative -> the gap goes;
|
|
81
|
+
* 4. still negative -> the label shrinks to `max(3, cols - marker - clock)`;
|
|
82
|
+
* 5. still negative -> the clock shrinks (`cols < marker + 3 + clock`);
|
|
83
|
+
* 6. the residue (a terminal narrower than the marker itself) is clamped so the
|
|
84
|
+
* sum stays inside `cols` — unreachable on a real device, where native mode
|
|
85
|
+
* degrades to the placeholder below 40x12, but the function is total because
|
|
86
|
+
* the property test runs `cols = 1..400`.
|
|
87
|
+
*/
|
|
88
|
+
export declare function teamRowGeometry(cols: number, glyphs: Glyphs): TeamRowGeometry;
|
|
89
|
+
/**
|
|
90
|
+
* The clock cell's text: this child's elapsed time, or empty when it never
|
|
91
|
+
* started (a `queued` row has no clock to show, and `0ms` would be a lie).
|
|
92
|
+
*/
|
|
93
|
+
export declare function teamClockText(run: SubagentRun, at: number): string;
|
|
94
|
+
/** The past-tense word the supervisor badge and status line render. */
|
|
95
|
+
export declare function interventionWord(action: OverseerAction): string;
|
|
96
|
+
/**
|
|
97
|
+
* The row's supervisor badge (subagent-overseer-v2 AC-5): what the supervisor
|
|
98
|
+
* last did to THIS child and how long ago, e.g. `nudged 2m03s`.
|
|
99
|
+
*
|
|
100
|
+
* `lastIntervention.at` is ABSOLUTE epoch ms (R-P2-3), the same units as
|
|
101
|
+
* `startedAt`, so this is one subtraction - no second clock convention.
|
|
102
|
+
*/
|
|
103
|
+
export declare function overseerBadge(run: SubagentRun, at: number): string;
|
|
104
|
+
/**
|
|
105
|
+
* The middle cell: what this child is doing right now, in a few words.
|
|
106
|
+
*
|
|
107
|
+
* Two width policies, answering different questions. `wide` decides WHICH FACT to
|
|
108
|
+
* show - a whole path or its basename, a whole command line or its program name -
|
|
109
|
+
* mirroring `StatusBar`'s `statusCompactCols` degradation rather than inventing a
|
|
110
|
+
* second threshold. `activityBudget(cols)` decides HOW MUCH OF IT FITS, and it is
|
|
111
|
+
* a continuous function of width rather than a threshold, because the failure it
|
|
112
|
+
* prevents - a row wider than the terminal - is continuous.
|
|
113
|
+
*/
|
|
114
|
+
export declare function activityLine(run: SubagentRun, cols: number, markerCells?: number): string;
|
|
115
|
+
/**
|
|
116
|
+
* The activity cell WITH the badge composed in (AC-5 / risk table): the badge
|
|
117
|
+
* rides the SAME allocation the prose uses, so a row can never grow past its
|
|
118
|
+
* terminal; on a narrow terminal the badge degrades to the action word alone -
|
|
119
|
+
* the who-acted fact outranks the what-they-are-doing tail exactly when there is
|
|
120
|
+
* room for only one.
|
|
121
|
+
*/
|
|
122
|
+
export declare function activityWithBadge(run: SubagentRun, cols: number, at: number, markerCells?: number): string;
|
|
123
|
+
/** The five text fragments of one row, each already clamped to its cell's policy. */
|
|
124
|
+
export interface TeamRowCells {
|
|
125
|
+
marker: string;
|
|
126
|
+
label: string;
|
|
127
|
+
description: string;
|
|
128
|
+
activity: string;
|
|
129
|
+
clock: string;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The five text fragments of one row.
|
|
133
|
+
*
|
|
134
|
+
* The description is normalized here rather than in the component because
|
|
135
|
+
* truncation preserves explicit newlines and the band reserves exactly ONE row
|
|
136
|
+
* for a child: a description carrying a newline would make the row two rows tall
|
|
137
|
+
* and every row below it would land one row lower than `band.teamRows` allows.
|
|
138
|
+
*/
|
|
139
|
+
export declare function teamRowCells(run: SubagentRun, cols: number, at: number, glyphs: Glyphs): TeamRowCells;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel row geometry — the ONE authority for how a subagent row is measured and
|
|
3
|
+
* what it says (tui-subagent-panel §4.2 / §6.1).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS FILE EXISTS. The roster used to place its right-hand columns by
|
|
6
|
+
* `flexGrow`, and a fixed-height parent box makes Yoga stretch the column to its
|
|
7
|
+
* widest CHILD instead of to the available width: on a 100-column terminal inside
|
|
8
|
+
* the band slot the row measured 48 (spec §0.1, cases 3/4), so `grep 22.1s` sat
|
|
9
|
+
* hard against the description and the right half of the row was blank. Flex
|
|
10
|
+
* cannot fix that, because the number it needs - "how wide is this terminal" - is
|
|
11
|
+
* not part of the layout it is given. So the widths are COMPUTED here, by a total
|
|
12
|
+
* pure function, and the component only places fixed-width boxes at the offsets
|
|
13
|
+
* this module names.
|
|
14
|
+
*
|
|
15
|
+
* THE FIVE CELLS, in drawing order:
|
|
16
|
+
*
|
|
17
|
+
* marker(1+status+1) label(8) description(flex) gap(2) activity(n) clock(8)
|
|
18
|
+
*
|
|
19
|
+
* `description` is the ONLY flexible cell, and it is the one that absorbs every
|
|
20
|
+
* width pressure - the same degradation discipline `StatusBar` documents for its
|
|
21
|
+
* left cluster. The two right-hand cells are fixed and `flexShrink={0}`, so their
|
|
22
|
+
* absolute offsets follow from `rowWidth = parentWidth` and the sum of the fixed
|
|
23
|
+
* cells in front of them, and never from how a particular frame allocates slack.
|
|
24
|
+
*
|
|
25
|
+
* ASCII ONLY: `src/ui/**` is inside `glyphs.test.ts`'s static scan.
|
|
26
|
+
*
|
|
27
|
+
* THE ACTIVITY HELPERS LIVE HERE TOO (v2 implementation note, recorded in the
|
|
28
|
+
* spec's "implementation findings"). `teamRowCells` has to compose the activity
|
|
29
|
+
* text with the supervisor badge, which is what `activityWithBadge` already does;
|
|
30
|
+
* leaving those in `TeamPanel.tsx` would make this module and the component
|
|
31
|
+
* import each other. `TeamPanel` therefore RE-EXPORTS every symbol below, so the
|
|
32
|
+
* existing import sites (three test files) keep their import path unchanged.
|
|
33
|
+
*/
|
|
34
|
+
import { describeToolActivity, sanitizeActivity } from '../../team/activity.js';
|
|
35
|
+
import { TEAM_LIMITS } from '../../team/limits.js';
|
|
36
|
+
import { formatDuration } from '../../agent/usage.js';
|
|
37
|
+
import { fitCells } from './cells.js';
|
|
38
|
+
import { childElapsedMs, childStatus, statusMarkerCell, statusMarkerWidth } from '../team-status.js';
|
|
39
|
+
/** The clock cell: wide enough for `125m07s` (7) with one column of headroom. */
|
|
40
|
+
export const TEAM_CLOCK_CELLS = 8;
|
|
41
|
+
/** `run.label.padEnd(8).slice(0,8)` — unchanged from the row this replaces. */
|
|
42
|
+
export const TEAM_LABEL_CELLS = 8;
|
|
43
|
+
/** The two spaces in front of the activity cell. */
|
|
44
|
+
export const TEAM_GAP_CELLS = 2;
|
|
45
|
+
/**
|
|
46
|
+
* The description column's floor: below this the row is unreadable, so the
|
|
47
|
+
* activity cell gives way instead (`activityDropCols`).
|
|
48
|
+
*/
|
|
49
|
+
export const TEAM_MIN_DESCRIPTION_CELLS = 12;
|
|
50
|
+
/**
|
|
51
|
+
* The concession to code units vs terminal columns, inherited verbatim from the
|
|
52
|
+
* `WIDE_CHAR_ALLOWANCE` comment on the row this replaces: this budget is
|
|
53
|
+
* denominated in code units while the terminal is denominated in columns, and CJK
|
|
54
|
+
* and emoji occupy two columns per unit, so the budget is halved against the
|
|
55
|
+
* reserve rather than spent to the last cell. Erring small costs a few characters
|
|
56
|
+
* of prose; erring large costs the clock column.
|
|
57
|
+
*/
|
|
58
|
+
const WIDE_CHAR_ALLOWANCE = 2;
|
|
59
|
+
/** The marker cell of the Unicode tier, used as the default by callers that pass no tier. */
|
|
60
|
+
const DEFAULT_MARKER_CELLS = 3;
|
|
61
|
+
function nonNegativeInt(value) {
|
|
62
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* How many characters the activity cell may spend at this width.
|
|
66
|
+
*
|
|
67
|
+
* `TEAM_LIMITS.activityChars` is a STORAGE ceiling; a terminal has a COLUMN
|
|
68
|
+
* budget, and the two are not the same number. Below
|
|
69
|
+
* `TEAM_LIMITS.activityDropCols` the cell gives way ENTIRELY (returns 0) rather
|
|
70
|
+
* than shrinking to its 12-character floor: at 40 columns those twelve characters
|
|
71
|
+
* would push the description below its own floor, and "who is doing what" outranks
|
|
72
|
+
* "what they are writing right now" exactly when there is room for one of them.
|
|
73
|
+
* That is a semantic change to a shipped assertion (`activityBudget(40)` used to
|
|
74
|
+
* return 12) and is recorded in the spec's §10 collateral table.
|
|
75
|
+
*
|
|
76
|
+
* `markerCells` defaults to the Unicode tier so the three existing single-argument
|
|
77
|
+
* call sites keep compiling; the panel passes the real tier's width.
|
|
78
|
+
*/
|
|
79
|
+
export function activityBudget(cols, markerCells = DEFAULT_MARKER_CELLS) {
|
|
80
|
+
if (!Number.isFinite(cols) || cols < TEAM_LIMITS.activityDropCols)
|
|
81
|
+
return 0;
|
|
82
|
+
const fixed = markerCells + TEAM_LABEL_CELLS + TEAM_GAP_CELLS + TEAM_CLOCK_CELLS
|
|
83
|
+
+ TEAM_MIN_DESCRIPTION_CELLS;
|
|
84
|
+
const spare = Math.floor((cols - fixed) / WIDE_CHAR_ALLOWANCE);
|
|
85
|
+
return Math.max(TEAM_LIMITS.activityMinChars, Math.min(TEAM_LIMITS.activityChars, spare));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The row's cell widths. A TOTAL function: every cell is a non-negative integer
|
|
89
|
+
* and the five of them never sum past `cols`, for any `cols >= 0` and either
|
|
90
|
+
* glyph tier (I-3, `team-row-geometry.test.ts`'s property test).
|
|
91
|
+
*
|
|
92
|
+
* THE LADDER IS THE SPECIFICATION (§4.2.2), in the order the cells give way:
|
|
93
|
+
* 1. a wide enough terminal gets the business budget;
|
|
94
|
+
* 2. below `activityDropCols` the activity cell is 0;
|
|
95
|
+
* 3. description still negative -> the gap goes;
|
|
96
|
+
* 4. still negative -> the label shrinks to `max(3, cols - marker - clock)`;
|
|
97
|
+
* 5. still negative -> the clock shrinks (`cols < marker + 3 + clock`);
|
|
98
|
+
* 6. the residue (a terminal narrower than the marker itself) is clamped so the
|
|
99
|
+
* sum stays inside `cols` — unreachable on a real device, where native mode
|
|
100
|
+
* degrades to the placeholder below 40x12, but the function is total because
|
|
101
|
+
* the property test runs `cols = 1..400`.
|
|
102
|
+
*/
|
|
103
|
+
export function teamRowGeometry(cols, glyphs) {
|
|
104
|
+
const width = nonNegativeInt(cols);
|
|
105
|
+
const markerCells = 1 + statusMarkerWidth(glyphs) + 1;
|
|
106
|
+
const cells = {
|
|
107
|
+
marker: Math.min(markerCells, width),
|
|
108
|
+
label: TEAM_LABEL_CELLS,
|
|
109
|
+
gap: TEAM_GAP_CELLS,
|
|
110
|
+
activity: activityBudget(width, markerCells),
|
|
111
|
+
clock: TEAM_CLOCK_CELLS,
|
|
112
|
+
minDescription: TEAM_MIN_DESCRIPTION_CELLS,
|
|
113
|
+
};
|
|
114
|
+
const sum = () => cells.marker + cells.label + cells.gap + cells.activity + cells.clock;
|
|
115
|
+
if (sum() > width)
|
|
116
|
+
cells.gap = 0;
|
|
117
|
+
if (sum() > width)
|
|
118
|
+
cells.label = Math.max(3, width - cells.marker - cells.clock);
|
|
119
|
+
if (sum() > width)
|
|
120
|
+
cells.clock = Math.max(0, Math.min(TEAM_CLOCK_CELLS, width - cells.marker - cells.label));
|
|
121
|
+
if (sum() > width)
|
|
122
|
+
cells.label = Math.max(0, Math.min(cells.label, width - cells.marker));
|
|
123
|
+
cells.minDescription = sum() >= width ? 0 : TEAM_MIN_DESCRIPTION_CELLS;
|
|
124
|
+
return cells;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The clock cell's text: this child's elapsed time, or empty when it never
|
|
128
|
+
* started (a `queued` row has no clock to show, and `0ms` would be a lie).
|
|
129
|
+
*/
|
|
130
|
+
export function teamClockText(run, at) {
|
|
131
|
+
return run.startedAt === undefined ? '' : formatDuration(childElapsedMs(run, at));
|
|
132
|
+
}
|
|
133
|
+
/** The past-tense word the supervisor badge and status line render. */
|
|
134
|
+
export function interventionWord(action) {
|
|
135
|
+
switch (action) {
|
|
136
|
+
case 'nudge':
|
|
137
|
+
return 'nudged';
|
|
138
|
+
case 'replace':
|
|
139
|
+
return 'replaced';
|
|
140
|
+
case 'abandon':
|
|
141
|
+
return 'abandoned';
|
|
142
|
+
default:
|
|
143
|
+
return 'waited';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The row's supervisor badge (subagent-overseer-v2 AC-5): what the supervisor
|
|
148
|
+
* last did to THIS child and how long ago, e.g. `nudged 2m03s`.
|
|
149
|
+
*
|
|
150
|
+
* `lastIntervention.at` is ABSOLUTE epoch ms (R-P2-3), the same units as
|
|
151
|
+
* `startedAt`, so this is one subtraction - no second clock convention.
|
|
152
|
+
*/
|
|
153
|
+
export function overseerBadge(run, at) {
|
|
154
|
+
const last = run.lastIntervention;
|
|
155
|
+
if (last === undefined)
|
|
156
|
+
return '';
|
|
157
|
+
return `${interventionWord(last.action)} ${formatDuration(Math.max(0, at - last.at))}`;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The middle cell: what this child is doing right now, in a few words.
|
|
161
|
+
*
|
|
162
|
+
* Two width policies, answering different questions. `wide` decides WHICH FACT to
|
|
163
|
+
* show - a whole path or its basename, a whole command line or its program name -
|
|
164
|
+
* mirroring `StatusBar`'s `statusCompactCols` degradation rather than inventing a
|
|
165
|
+
* second threshold. `activityBudget(cols)` decides HOW MUCH OF IT FITS, and it is
|
|
166
|
+
* a continuous function of width rather than a threshold, because the failure it
|
|
167
|
+
* prevents - a row wider than the terminal - is continuous.
|
|
168
|
+
*/
|
|
169
|
+
export function activityLine(run, cols, markerCells) {
|
|
170
|
+
const wide = cols >= TEAM_LIMITS.activityWideCols;
|
|
171
|
+
const budget = activityBudget(cols, markerCells);
|
|
172
|
+
switch (run.phase) {
|
|
173
|
+
case 'queued':
|
|
174
|
+
return 'queued';
|
|
175
|
+
case 'starting':
|
|
176
|
+
// The one row that legitimately restarts its clock, saying why (P2-4):
|
|
177
|
+
// F-2 ranks the running group by `startedAt` and a retry re-stamps it.
|
|
178
|
+
return run.retries ? 'starting (retry)' : 'starting';
|
|
179
|
+
case 'thinking':
|
|
180
|
+
// Clamped AT RENDER, against this terminal's budget. `run.activity` was
|
|
181
|
+
// clamped at STORE against `activityChars`; that is a ceiling, not a fit.
|
|
182
|
+
return run.activity ? `writing: ${sanitizeActivity(run.activity, budget)}` : 'thinking';
|
|
183
|
+
case 'tool':
|
|
184
|
+
case 'waiting':
|
|
185
|
+
return run.lastTool
|
|
186
|
+
? sanitizeActivity(describeToolActivity(run.lastTool, run.activityArgs, wide), budget)
|
|
187
|
+
: run.phase === 'waiting'
|
|
188
|
+
? 'waiting for mail'
|
|
189
|
+
: 'tool';
|
|
190
|
+
case 'done':
|
|
191
|
+
// `compacted N` ON THE SETTLED ROW ONLY (hardening §3.4.4 / W3). A running
|
|
192
|
+
// row is already competing for `activityBudget(cols)` with the child's own
|
|
193
|
+
// prose, and a marker that displaces what the child is DOING right now
|
|
194
|
+
// buys nothing - the dispatch report carries the same fact for good.
|
|
195
|
+
return run.compactions
|
|
196
|
+
? `done ${run.turns} turns compacted ${run.compactions}`
|
|
197
|
+
: `done ${run.turns} turns`;
|
|
198
|
+
case 'failed':
|
|
199
|
+
return 'failed';
|
|
200
|
+
case 'aborted':
|
|
201
|
+
return 'aborted';
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The activity cell WITH the badge composed in (AC-5 / risk table): the badge
|
|
206
|
+
* rides the SAME allocation the prose uses, so a row can never grow past its
|
|
207
|
+
* terminal; on a narrow terminal the badge degrades to the action word alone -
|
|
208
|
+
* the who-acted fact outranks the what-they-are-doing tail exactly when there is
|
|
209
|
+
* room for only one.
|
|
210
|
+
*/
|
|
211
|
+
export function activityWithBadge(run, cols, at, markerCells) {
|
|
212
|
+
const last = run.lastIntervention;
|
|
213
|
+
if (last === undefined)
|
|
214
|
+
return activityLine(run, cols, markerCells);
|
|
215
|
+
const badge = overseerBadge(run, at);
|
|
216
|
+
if (cols < TEAM_LIMITS.activityWideCols)
|
|
217
|
+
return interventionWord(last.action);
|
|
218
|
+
return sanitizeActivity(`${badge}; ${activityLine(run, cols, markerCells)}`, activityBudget(cols, markerCells));
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* The five text fragments of one row.
|
|
222
|
+
*
|
|
223
|
+
* The description is normalized here rather than in the component because
|
|
224
|
+
* truncation preserves explicit newlines and the band reserves exactly ONE row
|
|
225
|
+
* for a child: a description carrying a newline would make the row two rows tall
|
|
226
|
+
* and every row below it would land one row lower than `band.teamRows` allows.
|
|
227
|
+
*/
|
|
228
|
+
export function teamRowCells(run, cols, at, glyphs) {
|
|
229
|
+
const geometry = teamRowGeometry(cols, glyphs);
|
|
230
|
+
// THE TEXT IS CLAMPED TO THE SAME WIDTH THE GEOMETRY ALLOCATED, so a cell's content
|
|
231
|
+
// measures no more than its box. `activityWithBadge` already clamps to the business
|
|
232
|
+
// budget; this is the belt-and-braces that also covers the two cases the budget alone
|
|
233
|
+
// cannot express - a ZERO-width cell (below `activityDropCols`, where the answer is
|
|
234
|
+
// "no activity column at all", not "a nine-character word in a zero-column box") and
|
|
235
|
+
// a wide character straddling the edge.
|
|
236
|
+
const activity = geometry.activity === 0
|
|
237
|
+
? ''
|
|
238
|
+
: fitCells(activityWithBadge(run, cols, at, geometry.marker), geometry.activity, glyphs.ellipsis);
|
|
239
|
+
return {
|
|
240
|
+
marker: statusMarkerCell(childStatus(run), glyphs),
|
|
241
|
+
label: run.label.padEnd(TEAM_LABEL_CELLS).slice(0, TEAM_LABEL_CELLS),
|
|
242
|
+
description: run.description.replace(/\s+/g, ' '),
|
|
243
|
+
activity,
|
|
244
|
+
clock: teamClockText(run, at),
|
|
245
|
+
};
|
|
246
|
+
}
|