@aragon-agent/cli 0.6.35 → 0.6.37

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.
Files changed (146) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +3173 -3071
  3. package/dist/agent/agent-mode.d.ts +20 -4
  4. package/dist/agent/agent-mode.js +20 -4
  5. package/dist/agent/controller.d.ts +38 -3
  6. package/dist/agent/controller.js +64 -4
  7. package/dist/agent/headless.js +31 -0
  8. package/dist/agent/reducer.d.ts +13 -2
  9. package/dist/agent/reducer.js +1 -0
  10. package/dist/cli.js +12 -0
  11. package/dist/commands/agents-command.d.ts +15 -0
  12. package/dist/commands/agents-command.js +27 -0
  13. package/dist/commands/builtins.js +131 -4
  14. package/dist/commands/perf.d.ts +11 -0
  15. package/dist/commands/perf.js +2 -1
  16. package/dist/commands/registry.d.ts +14 -0
  17. package/dist/config/app-paths.d.ts +15 -0
  18. package/dist/config/app-paths.js +33 -2
  19. package/dist/config/env.d.ts +4 -1
  20. package/dist/config/env.js +28 -2
  21. package/dist/config/load.d.ts +10 -0
  22. package/dist/config/load.js +8 -1
  23. package/dist/config/schema.d.ts +68 -0
  24. package/dist/config/schema.js +39 -0
  25. package/dist/config/store.js +12 -1
  26. package/dist/config/ui-state.d.ts +18 -0
  27. package/dist/config/ui-state.js +9 -0
  28. package/dist/exec/events.d.ts +25 -0
  29. package/dist/exec/events.js +4 -0
  30. package/dist/exec/index.js +16 -1
  31. package/dist/exec/runner.js +10 -0
  32. package/dist/proc/limits.d.ts +16 -0
  33. package/dist/proc/limits.js +16 -0
  34. package/dist/proc/supervisor.d.ts +20 -1
  35. package/dist/proc/supervisor.js +42 -14
  36. package/dist/prompts/command.d.ts +34 -0
  37. package/dist/prompts/command.js +237 -0
  38. package/dist/prompts/copy.d.ts +89 -0
  39. package/dist/prompts/copy.js +131 -0
  40. package/dist/prompts/fill.d.ts +51 -0
  41. package/dist/prompts/fill.js +46 -0
  42. package/dist/prompts/limits.d.ts +53 -0
  43. package/dist/prompts/limits.js +53 -0
  44. package/dist/prompts/normalize.d.ts +53 -0
  45. package/dist/prompts/normalize.js +190 -0
  46. package/dist/prompts/resolve.d.ts +31 -0
  47. package/dist/prompts/resolve.js +60 -0
  48. package/dist/prompts/search.d.ts +58 -0
  49. package/dist/prompts/search.js +146 -0
  50. package/dist/prompts/store.d.ts +86 -0
  51. package/dist/prompts/store.js +453 -0
  52. package/dist/prompts/types.d.ts +204 -0
  53. package/dist/prompts/types.js +13 -0
  54. package/dist/session/checkpoint.d.ts +8 -1
  55. package/dist/session/checkpoint.js +6 -0
  56. package/dist/session/persist.d.ts +19 -1
  57. package/dist/session/persist.js +13 -1
  58. package/dist/session/startup-session.d.ts +7 -2
  59. package/dist/session/startup-session.js +5 -2
  60. package/dist/session/store.js +3 -0
  61. package/dist/session/validate-session.js +64 -1
  62. package/dist/team/limits.d.ts +25 -0
  63. package/dist/team/limits.js +26 -0
  64. package/dist/team/runtime.js +27 -2
  65. package/dist/team/settled.d.ts +57 -0
  66. package/dist/team/settled.js +87 -0
  67. package/dist/team/subagent.js +48 -2
  68. package/dist/team/timeline.d.ts +74 -0
  69. package/dist/team/timeline.js +101 -0
  70. package/dist/team/types.d.ts +24 -1
  71. package/dist/todo/acceptance.d.ts +120 -0
  72. package/dist/todo/acceptance.js +399 -0
  73. package/dist/todo/follow-through.d.ts +48 -23
  74. package/dist/todo/follow-through.js +110 -44
  75. package/dist/todo/limits.d.ts +62 -1
  76. package/dist/todo/limits.js +62 -1
  77. package/dist/todo/panel-rows.d.ts +29 -39
  78. package/dist/todo/panel-rows.js +37 -83
  79. package/dist/todo/plan-sections.d.ts +60 -0
  80. package/dist/todo/plan-sections.js +108 -0
  81. package/dist/todo/prompt.d.ts +17 -12
  82. package/dist/todo/prompt.js +59 -40
  83. package/dist/todo/row-window.d.ts +51 -0
  84. package/dist/todo/row-window.js +107 -0
  85. package/dist/todo/store.d.ts +139 -31
  86. package/dist/todo/store.js +271 -73
  87. package/dist/todo/todo-tool.d.ts +33 -8
  88. package/dist/todo/todo-tool.js +186 -30
  89. package/dist/todo/types.d.ts +58 -0
  90. package/dist/ui/App.js +310 -46
  91. package/dist/ui/Composer.d.ts +6 -2
  92. package/dist/ui/Composer.js +2 -2
  93. package/dist/ui/Header.d.ts +9 -1
  94. package/dist/ui/Header.js +2 -2
  95. package/dist/ui/PromptInput.d.ts +21 -1
  96. package/dist/ui/PromptInput.js +21 -1
  97. package/dist/ui/ScrollbackShell.d.ts +19 -19
  98. package/dist/ui/ScrollbackShell.js +8 -1
  99. package/dist/ui/StatusBar.d.ts +5 -0
  100. package/dist/ui/StatusBar.js +1 -0
  101. package/dist/ui/TeamPanel.d.ts +26 -44
  102. package/dist/ui/TeamPanel.js +46 -135
  103. package/dist/ui/TodoPanel.d.ts +17 -14
  104. package/dist/ui/TodoPanel.js +146 -38
  105. package/dist/ui/Transcript.js +1 -1
  106. package/dist/ui/composer-input.d.ts +10 -0
  107. package/dist/ui/composer-input.js +57 -24
  108. package/dist/ui/entries/TodoCard.d.ts +26 -11
  109. package/dist/ui/entries/TodoCard.js +56 -15
  110. package/dist/ui/glyphs.d.ts +19 -0
  111. package/dist/ui/glyphs.js +4 -0
  112. package/dist/ui/layout/agents-board.d.ts +102 -0
  113. package/dist/ui/layout/agents-board.js +389 -0
  114. package/dist/ui/layout/cells.d.ts +61 -0
  115. package/dist/ui/layout/cells.js +156 -0
  116. package/dist/ui/layout/header-hints.d.ts +13 -3
  117. package/dist/ui/layout/header-hints.js +48 -14
  118. package/dist/ui/layout/status-detail-layout.js +9 -0
  119. package/dist/ui/layout/status-layout.d.ts +13 -0
  120. package/dist/ui/layout/team-row.d.ts +139 -0
  121. package/dist/ui/layout/team-row.js +246 -0
  122. package/dist/ui/layout/todo-layout.d.ts +38 -0
  123. package/dist/ui/layout/todo-layout.js +75 -0
  124. package/dist/ui/layout/virtual-window.js +23 -3
  125. package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
  126. package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
  127. package/dist/ui/overlays/HelpOverlay.js +17 -2
  128. package/dist/ui/overlays/PromptEditorPage.d.ts +48 -0
  129. package/dist/ui/overlays/PromptEditorPage.js +227 -0
  130. package/dist/ui/overlays/PromptLibraryOverlay.d.ts +37 -0
  131. package/dist/ui/overlays/PromptLibraryOverlay.js +191 -0
  132. package/dist/ui/overlays/PromptPicker.d.ts +36 -0
  133. package/dist/ui/overlays/PromptPicker.js +129 -0
  134. package/dist/ui/overlays/PromptTextArea.d.ts +86 -0
  135. package/dist/ui/overlays/PromptTextArea.js +141 -0
  136. package/dist/ui/overlays/prompt-library-state.d.ts +162 -0
  137. package/dist/ui/overlays/prompt-library-state.js +335 -0
  138. package/dist/ui/overlays/prompt-picker-state.d.ts +71 -0
  139. package/dist/ui/overlays/prompt-picker-state.js +98 -0
  140. package/dist/ui/scrollback/band.js +14 -1
  141. package/dist/ui/team-status.d.ts +25 -0
  142. package/dist/ui/team-status.js +31 -3
  143. package/dist/ui/use-startup-notices.d.ts +35 -1
  144. package/dist/ui/use-startup-notices.js +29 -2
  145. package/dist/ui/use-wheel-routing.js +9 -1
  146. package/package.json +2 -2
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Display-width cell arithmetic (tui-subagent-panel §5 #1).
3
+ *
4
+ * EVERY FUNCTION HERE IS PURE AND TOTAL: no Ink, no clock, no terminal, and no
5
+ * input that throws. The panel row and the Agents board both need to place text
6
+ * in a FIXED number of terminal columns, and `padEnd` cannot do that: it counts
7
+ * code units while a terminal counts cells, so a CJK description padded with
8
+ * `padEnd` overflows its box by one column per wide character and pushes the
9
+ * next cell off the row. That is exactly the defect this round removes from the
10
+ * roster, one layer down.
11
+ *
12
+ * ASCII ONLY: `src/ui/**` is inside `glyphs.test.ts`'s static scan, so the
13
+ * ellipsis and any other symbol a caller wants must be passed IN (the board
14
+ * hands over `glyphs.ellipsis`), never spelled here.
15
+ */
16
+ import stringWidth from 'string-width';
17
+ /** Invalid geometry becomes zero, exactly like the layout modules' `nonNegativeInt`. */
18
+ function nonNegativeInt(value) {
19
+ return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
20
+ }
21
+ /** Display columns a string occupies, or 0 for a non-string (defensive at the edge only). */
22
+ export function cellWidth(text) {
23
+ if (typeof text !== 'string' || text.length === 0)
24
+ return 0;
25
+ return stringWidth(text);
26
+ }
27
+ /**
28
+ * Cut `text` to at most `cells` display columns, marking the cut with `ellipsis`.
29
+ *
30
+ * THE ELLIPSIS IS PART OF THE BUDGET, not an extra column: a caller that asks for
31
+ * 8 cells gets a string that MEASURES 8, which is what makes a fixed-width cell
32
+ * a cell rather than a hope. When the budget cannot hold the ellipsis plus one
33
+ * character, the string is hard-cut instead - a marker wider than the cell it
34
+ * marks is the one outcome worse than no marker.
35
+ *
36
+ * Wide characters are never split: a surrogate pair or a double-width glyph that
37
+ * does not fit is dropped whole, so the result can measure one column less than
38
+ * asked but never one more.
39
+ */
40
+ export function fitCells(text, cells, ellipsis = '...') {
41
+ const budget = nonNegativeInt(cells);
42
+ if (budget === 0)
43
+ return '';
44
+ if (cellWidth(text) <= budget)
45
+ return text;
46
+ const mark = cellWidth(ellipsis) < budget ? ellipsis : '';
47
+ const room = budget - cellWidth(mark);
48
+ let out = '';
49
+ let used = 0;
50
+ // `Array.from` iterates by CODE POINT, so a surrogate pair is one step and can
51
+ // never be cut in half by this loop.
52
+ for (const char of Array.from(text)) {
53
+ const width = cellWidth(char);
54
+ if (used + width > room)
55
+ break;
56
+ out += char;
57
+ used += width;
58
+ }
59
+ return `${out}${mark}`;
60
+ }
61
+ /**
62
+ * Pad `text` to exactly `cells` columns (or leave it alone when it already
63
+ * measures at least that).
64
+ *
65
+ * NOT `padEnd`: see this file's header. A padded cell is what keeps the next
66
+ * column at a stable offset, which is the whole point of the panel's table.
67
+ */
68
+ export function padCells(text, cells) {
69
+ const budget = nonNegativeInt(cells);
70
+ const width = cellWidth(text);
71
+ if (width >= budget)
72
+ return text;
73
+ return `${text}${' '.repeat(budget - width)}`;
74
+ }
75
+ /** `fitCells` then `padCells`: the result ALWAYS measures exactly `cells`. */
76
+ export function alignRightCells(text, cells, ellipsis = '...') {
77
+ const budget = nonNegativeInt(cells);
78
+ return padCells(fitCells(text, budget, ellipsis), budget);
79
+ }
80
+ /**
81
+ * Fold `text` into at most `maxRows` lines of at most `cells` columns each.
82
+ *
83
+ * BREAKS AT SPACES, HARD-CUTS ONLY WHEN A SINGLE TOKEN CANNOT FIT: a word that is
84
+ * wider than the column is the one case where an honest over-long token beats a
85
+ * line of nothing, so it is cut rather than pushed to a line that still cannot
86
+ * hold it. The last allowed line, when text remains, carries the `(+N lines)`
87
+ * tail so the reader learns that the section was truncated instead of believing
88
+ * the prose ended mid-sentence.
89
+ *
90
+ * Pure and total: `cells <= 0` or `maxRows <= 0` yields no lines, and the caller
91
+ * decides what an empty section means (the board omits it).
92
+ */
93
+ export function wrapCells(text, cells, maxRows, ellipsis = '...') {
94
+ const width = nonNegativeInt(cells);
95
+ const limit = nonNegativeInt(maxRows);
96
+ const flat = (text ?? '').replace(/\s+/g, ' ').trim();
97
+ if (width === 0 || limit === 0 || flat.length === 0) {
98
+ return { lines: [], dropped: flat.length === 0 ? 0 : limit === 0 ? 1 : 0 };
99
+ }
100
+ const lines = [];
101
+ let rest = flat;
102
+ while (rest.length > 0) {
103
+ if (cellWidth(rest) <= width) {
104
+ lines.push(rest);
105
+ rest = '';
106
+ break;
107
+ }
108
+ const cut = breakCells(rest, width);
109
+ lines.push(cut.line);
110
+ rest = cut.rest;
111
+ }
112
+ if (lines.length <= limit)
113
+ return { lines, dropped: 0 };
114
+ const kept = lines.slice(0, limit);
115
+ const dropped = lines.length - limit;
116
+ const tail = `(+${dropped} lines)`;
117
+ // THE ANNOUNCEMENT IS RESERVED, not appended and then truncated away: a reader who
118
+ // sees prose stop mid-sentence with no marker reads it as the end of the fact, which
119
+ // is exactly the wrong conclusion. When the column is too narrow to hold the marker
120
+ // at all, the line is simply cut - the caller's section is unusable at that width.
121
+ const tailCells = cellWidth(tail) + 1;
122
+ const last = kept[limit - 1] ?? '';
123
+ kept[limit - 1] = tailCells < width
124
+ ? `${fitCells(last, width - tailCells, ellipsis)} ${tail}`
125
+ : fitCells(last, width, ellipsis);
126
+ return { lines: kept, dropped };
127
+ }
128
+ /**
129
+ * The longest prefix of `text` that fits `width` columns, preferring a space.
130
+ *
131
+ * Returns the consumed line and what is left, so the caller's loop is one
132
+ * assignment per iteration and the "one function does one thing" rule holds.
133
+ */
134
+ function breakCells(text, width) {
135
+ let used = 0;
136
+ let taken = 0;
137
+ let lastSpace = -1;
138
+ for (const char of Array.from(text)) {
139
+ const charWidth = cellWidth(char);
140
+ if (used + charWidth > width)
141
+ break;
142
+ used += charWidth;
143
+ taken += char.length;
144
+ if (char === ' ')
145
+ lastSpace = taken;
146
+ }
147
+ // A single token wider than the column: hard-cut it rather than emit nothing.
148
+ if (taken === 0) {
149
+ const first = Array.from(text)[0] ?? '';
150
+ return { line: fitCells(first, width), rest: text.slice(first.length) };
151
+ }
152
+ if (lastSpace > 0) {
153
+ return { line: text.slice(0, lastSpace).trimEnd(), rest: text.slice(lastSpace).trimStart() };
154
+ }
155
+ return { line: text.slice(0, taken), rest: text.slice(taken).trimStart() };
156
+ }
@@ -46,7 +46,7 @@ export declare const HEADER_HINT_LIMITS: {
46
46
  */
47
47
  readonly separatorCells: 2;
48
48
  };
49
- export type HeaderHintId = 'branch' | 'index' | 'kit' | 'detail';
49
+ export type HeaderHintId = 'branch' | 'prompts' | 'index' | 'kit' | 'detail';
50
50
  export interface HeaderHintClause {
51
51
  id: HeaderHintId;
52
52
  text: string;
@@ -66,8 +66,9 @@ export interface HeaderHintPlan {
66
66
  /**
67
67
  * Plan the right-hand hints.
68
68
  *
69
- * The four inputs are exactly the props `Header` already receives - the call site
70
- * does not change, only the arithmetic behind it (spec 3.11 step 5).
69
+ * The inputs are the props `Header` receives plus the two switches that decide which
70
+ * clauses exist at all - the call site does not change, only the arithmetic behind it
71
+ * (spec 3.11 step 5).
71
72
  */
72
73
  export declare function planHeaderHints(input: {
73
74
  columns: number;
@@ -84,4 +85,13 @@ export declare function planHeaderHints(input: {
84
85
  * switch, and it degrades the six expanded rungs back into today's four.
85
86
  */
86
87
  kitHint?: boolean;
88
+ /**
89
+ * Whether the `^P prompts` clause exists at all (default `true`).
90
+ *
91
+ * `false` is the `prompts.hint` config key and the only reason the ladder is
92
+ * parameterised: a user who turned the header hint off must get exactly the row
93
+ * they had before this feature, which is what the unfiltered ladder below
94
+ * degenerates into.
95
+ */
96
+ promptsHint?: boolean;
87
97
  }): HeaderHintPlan;
@@ -58,6 +58,25 @@ const INDEX_CLAUSE = { collapsed: '^I Index', expanded: '^I build index' };
58
58
  * leftward on every launch for a key pressed twice a session.
59
59
  */
60
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';
61
80
  const DETAIL_CLAUSE = { collapsed: '^G more', expanded: '^G less' };
62
81
  /** L3 gives the key name and nothing else: discoverability at the last width. */
63
82
  const BRANCH_KEY_ONLY = '^L';
@@ -69,8 +88,9 @@ const SCROLL_CHIP_MAX = 9999;
69
88
  /**
70
89
  * Plan the right-hand hints.
71
90
  *
72
- * The four inputs are exactly the props `Header` already receives - the call site
73
- * does not change, only the arithmetic behind it (spec 3.11 step 5).
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).
74
94
  */
75
95
  export function planHeaderHints(input) {
76
96
  // NORMALISED HERE, NOT TRUSTED (R-14). `Header` defaults to 80, but a test or a
@@ -86,7 +106,7 @@ export function planHeaderHints(input) {
86
106
  const detail = input.statusExpanded ? DETAILS_ON : DETAILS_OFF;
87
107
  return buildPlan([{ id: 'detail', text: detail }], scrollCells, true);
88
108
  }
89
- const ladder = ladderFor(input.statusExpanded, input.kitHint !== false);
109
+ const ladder = ladderFor(input.statusExpanded, input.kitHint !== false, input.promptsHint !== false);
90
110
  // THE SEPARATOR IS SPENT OUT OF THE SAME ROW (defect 12): left half + gap + this
91
111
  // column have to add up to `columns`, so the gap comes off the budget instead of
92
112
  // being assumed free.
@@ -114,7 +134,7 @@ export function planHeaderHints(input) {
114
134
  * `^I` everything from 64 to 78 columns - a permanent loss, paid for a
115
135
  * low-frequency key.
116
136
  */
117
- function ladderFor(statusExpanded, kitHint) {
137
+ function ladderFor(statusExpanded, kitHint, promptsHint) {
118
138
  const branch = {
119
139
  id: 'branch',
120
140
  text: statusExpanded ? BRANCH_CLAUSE.expanded : BRANCH_CLAUSE.collapsed,
@@ -127,25 +147,39 @@ function ladderFor(statusExpanded, kitHint) {
127
147
  id: 'detail',
128
148
  text: statusExpanded ? DETAIL_CLAUSE.expanded : DETAIL_CLAUSE.collapsed,
129
149
  };
130
- const base = [
131
- [branch, index, detail],
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],
132
166
  [branch, detail],
133
- [{ id: 'branch', text: BRANCH_KEY_ONLY }, detail],
167
+ [branchKey, detail],
134
168
  [detail],
135
169
  ];
136
- // THE COLLAPSED LADDER IS UNTOUCHED (D-11): `^K` has no collapsed form, so a
137
- // user who never presses `ctrl+G` sees the row they always saw (I-9).
138
- if (!statusExpanded || !kitHint)
139
- return base;
140
- const kit = { id: 'kit', text: KIT_CLAUSE.expanded };
141
- return [
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],
142
175
  [branch, index, kit, detail],
143
176
  [branch, index, detail],
144
177
  [branch, kit, detail],
145
178
  [branch, detail],
146
- [{ id: 'branch', text: BRANCH_KEY_ONLY }, detail],
179
+ [branchKey, detail],
147
180
  [detail],
148
181
  ];
182
+ return (statusExpanded ? expanded : collapsed).map((rung) => rung.filter(keep));
149
183
  }
150
184
  /**
151
185
  * Compose the final string. The chip carries its OWN trailing spacing and is the
@@ -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;