@aragon-agent/cli 0.6.44 → 0.6.46

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 (255) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +120 -2
  3. package/dist/agent/controller.d.ts +69 -0
  4. package/dist/agent/controller.js +224 -11
  5. package/dist/agent/governance-view.d.ts +40 -0
  6. package/dist/agent/governance-view.js +40 -0
  7. package/dist/agent/governance-wiring.d.ts +162 -0
  8. package/dist/agent/governance-wiring.js +470 -0
  9. package/dist/agent/invocation-card.d.ts +77 -0
  10. package/dist/agent/invocation-card.js +169 -0
  11. package/dist/agent/plan-prompt.d.ts +7 -0
  12. package/dist/agent/plan-prompt.js +2 -1
  13. package/dist/agent/prompt-options.d.ts +6 -0
  14. package/dist/agent/queued-messages.d.ts +3 -0
  15. package/dist/agent/queued-messages.js +4 -1
  16. package/dist/agent/reducer.d.ts +38 -1
  17. package/dist/agent/reducer.js +57 -12
  18. package/dist/agent/system-prompt.d.ts +19 -0
  19. package/dist/agent/system-prompt.js +9 -1
  20. package/dist/cli.js +45 -1
  21. package/dist/commands/builtins.js +22 -4
  22. package/dist/commands/governance-command.d.ts +35 -0
  23. package/dist/commands/governance-command.js +304 -0
  24. package/dist/commands/governance-runs-table.d.ts +40 -0
  25. package/dist/commands/governance-runs-table.js +130 -0
  26. package/dist/commands/index-build.d.ts +8 -0
  27. package/dist/commands/index-build.js +11 -0
  28. package/dist/commands/registry.d.ts +35 -0
  29. package/dist/commands/registry.js +20 -7
  30. package/dist/commands/skills.js +36 -2
  31. package/dist/commands/subagent-command.js +3 -1
  32. package/dist/commands/team-command.d.ts +6 -0
  33. package/dist/commands/team-command.js +40 -3
  34. package/dist/config/app-paths.d.ts +10 -0
  35. package/dist/config/app-paths.js +12 -0
  36. package/dist/config/cli-commands.d.ts +18 -0
  37. package/dist/config/cli-commands.js +47 -1
  38. package/dist/config/env.d.ts +2 -1
  39. package/dist/config/env.js +10 -1
  40. package/dist/config/governance-config.d.ts +125 -0
  41. package/dist/config/governance-config.js +164 -0
  42. package/dist/config/governance-launch.d.ts +71 -0
  43. package/dist/config/governance-launch.js +58 -0
  44. package/dist/config/load.d.ts +15 -0
  45. package/dist/config/load.js +26 -1
  46. package/dist/config/model-profile-store.d.ts +4 -2
  47. package/dist/config/schema.d.ts +22 -0
  48. package/dist/config/schema.js +6 -0
  49. package/dist/config/store.js +14 -1
  50. package/dist/config/ui-state.d.ts +11 -0
  51. package/dist/config/ui-state.js +11 -0
  52. package/dist/diagnostics/doctor.js +20 -0
  53. package/dist/governance/card-normalize.d.ts +24 -0
  54. package/dist/governance/card-normalize.js +76 -0
  55. package/dist/governance/copy.d.ts +109 -0
  56. package/dist/governance/copy.js +223 -0
  57. package/dist/governance/engine/aftermath.d.ts +49 -0
  58. package/dist/governance/engine/aftermath.js +134 -0
  59. package/dist/governance/engine/artifact-persist.d.ts +35 -0
  60. package/dist/governance/engine/artifact-persist.js +86 -0
  61. package/dist/governance/engine/budget.d.ts +52 -0
  62. package/dist/governance/engine/budget.js +112 -0
  63. package/dist/governance/engine/cards.d.ts +18 -0
  64. package/dist/governance/engine/cards.js +186 -0
  65. package/dist/governance/engine/digest.d.ts +32 -0
  66. package/dist/governance/engine/digest.js +63 -0
  67. package/dist/governance/engine/engine-core.d.ts +219 -0
  68. package/dist/governance/engine/engine-core.js +562 -0
  69. package/dist/governance/engine/engine.d.ts +68 -0
  70. package/dist/governance/engine/engine.js +314 -0
  71. package/dist/governance/engine/estimate.d.ts +38 -0
  72. package/dist/governance/engine/estimate.js +46 -0
  73. package/dist/governance/engine/inspector.d.ts +16 -0
  74. package/dist/governance/engine/inspector.js +396 -0
  75. package/dist/governance/engine/integration.d.ts +35 -0
  76. package/dist/governance/engine/integration.js +220 -0
  77. package/dist/governance/engine/lifecycle.d.ts +29 -0
  78. package/dist/governance/engine/lifecycle.js +656 -0
  79. package/dist/governance/engine/pause.d.ts +56 -0
  80. package/dist/governance/engine/pause.js +107 -0
  81. package/dist/governance/engine/phase-members.d.ts +15 -0
  82. package/dist/governance/engine/phase-members.js +34 -0
  83. package/dist/governance/engine/phase-planner.d.ts +53 -0
  84. package/dist/governance/engine/phase-planner.js +282 -0
  85. package/dist/governance/engine/rebuild.d.ts +27 -0
  86. package/dist/governance/engine/rebuild.js +57 -0
  87. package/dist/governance/engine/report.d.ts +66 -0
  88. package/dist/governance/engine/report.js +220 -0
  89. package/dist/governance/engine/run-digest.d.ts +20 -0
  90. package/dist/governance/engine/run-digest.js +42 -0
  91. package/dist/governance/engine/spec-builder.d.ts +117 -0
  92. package/dist/governance/engine/spec-builder.js +321 -0
  93. package/dist/governance/engine/stage-runner.d.ts +22 -0
  94. package/dist/governance/engine/stage-runner.js +664 -0
  95. package/dist/governance/engine/start-input.d.ts +19 -0
  96. package/dist/governance/engine/start-input.js +111 -0
  97. package/dist/governance/engine/state-machine.d.ts +101 -0
  98. package/dist/governance/engine/state-machine.js +586 -0
  99. package/dist/governance/engine/submission-queries.d.ts +42 -0
  100. package/dist/governance/engine/submission-queries.js +97 -0
  101. package/dist/governance/engine/submission-render.d.ts +12 -0
  102. package/dist/governance/engine/submission-render.js +92 -0
  103. package/dist/governance/engine/submissions.d.ts +27 -0
  104. package/dist/governance/engine/submissions.js +455 -0
  105. package/dist/governance/engine/visibility.d.ts +104 -0
  106. package/dist/governance/engine/visibility.js +223 -0
  107. package/dist/governance/governed-dispatch.d.ts +19 -0
  108. package/dist/governance/governed-dispatch.js +17 -0
  109. package/dist/governance/limits.d.ts +107 -0
  110. package/dist/governance/limits.js +131 -0
  111. package/dist/governance/posture-transition.d.ts +38 -0
  112. package/dist/governance/posture-transition.js +44 -0
  113. package/dist/governance/posture.d.ts +35 -0
  114. package/dist/governance/posture.js +59 -0
  115. package/dist/governance/profiles.d.ts +48 -0
  116. package/dist/governance/profiles.js +140 -0
  117. package/dist/governance/prompts/orchestrator-block.d.ts +40 -0
  118. package/dist/governance/prompts/orchestrator-block.js +128 -0
  119. package/dist/governance/prompts/plan-addendum.d.ts +22 -0
  120. package/dist/governance/prompts/plan-addendum.js +60 -0
  121. package/dist/governance/prompts/refusals.d.ts +48 -0
  122. package/dist/governance/prompts/refusals.js +82 -0
  123. package/dist/governance/prompts/role-blocks.d.ts +40 -0
  124. package/dist/governance/prompts/role-blocks.js +255 -0
  125. package/dist/governance/report-reader.d.ts +31 -0
  126. package/dist/governance/report-reader.js +57 -0
  127. package/dist/governance/roles.d.ts +83 -0
  128. package/dist/governance/roles.js +142 -0
  129. package/dist/governance/rules/claims.d.ts +83 -0
  130. package/dist/governance/rules/claims.js +250 -0
  131. package/dist/governance/rules/elimination.d.ts +66 -0
  132. package/dist/governance/rules/elimination.js +105 -0
  133. package/dist/governance/rules/events-table.d.ts +46 -0
  134. package/dist/governance/rules/events-table.js +94 -0
  135. package/dist/governance/rules/failure-analysis.d.ts +28 -0
  136. package/dist/governance/rules/failure-analysis.js +93 -0
  137. package/dist/governance/rules/ledger-replay.d.ts +53 -0
  138. package/dist/governance/rules/ledger-replay.js +53 -0
  139. package/dist/governance/rules/ledger-state.d.ts +85 -0
  140. package/dist/governance/rules/ledger-state.js +175 -0
  141. package/dist/governance/rules/ledger.d.ts +228 -0
  142. package/dist/governance/rules/ledger.js +560 -0
  143. package/dist/governance/rules/rca.d.ts +68 -0
  144. package/dist/governance/rules/rca.js +220 -0
  145. package/dist/governance/rules/settlement.d.ts +129 -0
  146. package/dist/governance/rules/settlement.js +363 -0
  147. package/dist/governance/rules/suspicion.d.ts +66 -0
  148. package/dist/governance/rules/suspicion.js +169 -0
  149. package/dist/governance/store/artifacts.d.ts +81 -0
  150. package/dist/governance/store/artifacts.js +173 -0
  151. package/dist/governance/store/atomic.d.ts +79 -0
  152. package/dist/governance/store/atomic.js +119 -0
  153. package/dist/governance/store/events-log.d.ts +35 -0
  154. package/dist/governance/store/events-log.js +86 -0
  155. package/dist/governance/store/run-lock.d.ts +51 -0
  156. package/dist/governance/store/run-lock.js +122 -0
  157. package/dist/governance/store/run-store.d.ts +229 -0
  158. package/dist/governance/store/run-store.js +644 -0
  159. package/dist/governance/store/scoreboard-export.d.ts +53 -0
  160. package/dist/governance/store/scoreboard-export.js +65 -0
  161. package/dist/governance/tools/child-tools.d.ts +31 -0
  162. package/dist/governance/tools/child-tools.js +65 -0
  163. package/dist/governance/tools/guard.d.ts +71 -0
  164. package/dist/governance/tools/guard.js +194 -0
  165. package/dist/governance/tools/inspector-tools.d.ts +63 -0
  166. package/dist/governance/tools/inspector-tools.js +424 -0
  167. package/dist/governance/tools/lead-tools.d.ts +42 -0
  168. package/dist/governance/tools/lead-tools.js +226 -0
  169. package/dist/governance/tools/normalize-util.d.ts +101 -0
  170. package/dist/governance/tools/normalize-util.js +230 -0
  171. package/dist/governance/tools/scan-incomplete.d.ts +79 -0
  172. package/dist/governance/tools/scan-incomplete.js +486 -0
  173. package/dist/governance/tools/shared-tools.d.ts +44 -0
  174. package/dist/governance/tools/shared-tools.js +125 -0
  175. package/dist/governance/tools/submit-normalizers.d.ts +28 -0
  176. package/dist/governance/tools/submit-normalizers.js +474 -0
  177. package/dist/governance/tools/submit-tools.d.ts +26 -0
  178. package/dist/governance/tools/submit-tools.js +171 -0
  179. package/dist/governance/types.d.ts +661 -0
  180. package/dist/governance/types.js +27 -0
  181. package/dist/session/persist.js +10 -0
  182. package/dist/session/validate-session.js +36 -3
  183. package/dist/team/forced-mode.d.ts +19 -0
  184. package/dist/team/forced-mode.js +42 -20
  185. package/dist/team/runtime.d.ts +8 -0
  186. package/dist/team/runtime.js +9 -0
  187. package/dist/team/subagent.d.ts +23 -0
  188. package/dist/team/subagent.js +14 -3
  189. package/dist/team/types.d.ts +6 -0
  190. package/dist/tools/index.d.ts +30 -7
  191. package/dist/tools/index.js +14 -0
  192. package/dist/tools/plan-tools.d.ts +14 -2
  193. package/dist/tools/plan-tools.js +5 -2
  194. package/dist/ui/App.js +170 -31
  195. package/dist/ui/BottomStatusRow.js +1 -1
  196. package/dist/ui/Composer.d.ts +7 -1
  197. package/dist/ui/Composer.js +9 -6
  198. package/dist/ui/GovernancePanel.d.ts +50 -0
  199. package/dist/ui/GovernancePanel.js +77 -0
  200. package/dist/ui/GovernanceRun.d.ts +18 -0
  201. package/dist/ui/GovernanceRun.js +11 -0
  202. package/dist/ui/StatusBar.d.ts +15 -2
  203. package/dist/ui/StatusBar.js +10 -4
  204. package/dist/ui/Transcript.js +7 -1
  205. package/dist/ui/entries/GovernanceCard.d.ts +30 -0
  206. package/dist/ui/entries/GovernanceCard.js +32 -0
  207. package/dist/ui/entries/InvocationCard.d.ts +20 -0
  208. package/dist/ui/entries/InvocationCard.js +32 -0
  209. package/dist/ui/governance-copy.d.ts +69 -0
  210. package/dist/ui/governance-copy.js +104 -0
  211. package/dist/ui/governance-status.d.ts +145 -0
  212. package/dist/ui/governance-status.js +326 -0
  213. package/dist/ui/interaction-copy.d.ts +4 -0
  214. package/dist/ui/interaction-copy.js +5 -0
  215. package/dist/ui/interaction-hints.d.ts +17 -0
  216. package/dist/ui/interaction-hints.js +14 -2
  217. package/dist/ui/layout/governance-board.d.ts +121 -0
  218. package/dist/ui/layout/governance-board.js +382 -0
  219. package/dist/ui/layout/governance-card.d.ts +53 -0
  220. package/dist/ui/layout/governance-card.js +134 -0
  221. package/dist/ui/layout/governance-chip.d.ts +38 -0
  222. package/dist/ui/layout/governance-chip.js +64 -0
  223. package/dist/ui/layout/governance-panel.d.ts +85 -0
  224. package/dist/ui/layout/governance-panel.js +358 -0
  225. package/dist/ui/layout/governance-roster.d.ts +65 -0
  226. package/dist/ui/layout/governance-roster.js +275 -0
  227. package/dist/ui/layout/invocation-card.d.ts +41 -0
  228. package/dist/ui/layout/invocation-card.js +91 -0
  229. package/dist/ui/layout/status-detail-layout.js +3 -3
  230. package/dist/ui/layout/status-layout.d.ts +5 -2
  231. package/dist/ui/layout/status-layout.js +10 -6
  232. package/dist/ui/layout/virtual-window.js +22 -1
  233. package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +41 -0
  234. package/dist/ui/overlays/GovernanceBoardOverlay.js +76 -0
  235. package/dist/ui/overlays/GovernanceSettingsPage.d.ts +48 -0
  236. package/dist/ui/overlays/GovernanceSettingsPage.js +93 -0
  237. package/dist/ui/overlays/HelpOverlay.js +4 -3
  238. package/dist/ui/overlays/QuestionOverlay.d.ts +9 -1
  239. package/dist/ui/overlays/QuestionOverlay.js +4 -2
  240. package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
  241. package/dist/ui/overlays/SettingsScreen.js +21 -1
  242. package/dist/ui/overlays/governance-settings.d.ts +79 -0
  243. package/dist/ui/overlays/governance-settings.js +140 -0
  244. package/dist/ui/overlays/model-profile-state.d.ts +6 -1
  245. package/dist/ui/overlays/model-profile-state.js +10 -0
  246. package/dist/ui/palettes.d.ts +17 -0
  247. package/dist/ui/palettes.js +9 -0
  248. package/dist/ui/scrollback/live-preview.js +2 -0
  249. package/dist/ui/scrollback/settled-boundary.js +3 -0
  250. package/dist/ui/shortcuts.js +2 -2
  251. package/dist/ui/theme.d.ts +7 -0
  252. package/dist/ui/theme.js +1 -0
  253. package/dist/ui/transcript-text.js +13 -1
  254. package/dist/ui/use-wheel-routing.js +3 -1
  255. package/package.json +2 -2
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Roster geometry and cell text for the legion panel (governance-mode section 10.3).
3
+ *
4
+ * Pure and total: no Ink, no clock, no terminal. ASCII ONLY.
5
+ *
6
+ * WHY COMPUTED FIXED-WIDTH COLUMNS. Same lesson as `team-row.ts`, whose header records
7
+ * it at length: a fixed-height parent makes Yoga size a flex column to its widest CHILD,
8
+ * so a row placed by `flexGrow` stops short of the terminal and the cells to its right
9
+ * lose their absolute offsets. Every column below therefore has a COMPUTED integer width
10
+ * and exactly ONE column is elastic; it sits between the fixed cluster on its left and
11
+ * the fixed `delta` cell on its right, so the score change always lands in the row's
12
+ * last column whatever the activity text says.
13
+ *
14
+ * `columnPlan` is TOTAL: for any width (including 0 and 400) the widths never sum past
15
+ * the width it was given. `governance-panel-layout.test.ts` runs it for every width.
16
+ */
17
+ import { statusMarkerCell, statusMarkerWidth } from '../team-status.js';
18
+ import { GAUGE_CELLS, deltaCell, gaugeFilledCells, govChildStatus, labelTag, scoreText, scoreToneKey, segment, statusTone, } from '../governance-status.js';
19
+ import { NOT_SCORED, ROLE_LABEL, STATE_WORD, WORKING_VERB, replacedByText, } from '../governance-copy.js';
20
+ import { cellWidth, fitCells } from './cells.js';
21
+ /** The elastic column keeps at least this many columns, else it is dropped (activity) or kept (wp). */
22
+ const ELASTIC_MIN_CELLS = { activity: 12, wp: 6 };
23
+ /** One leading margin column, drawn by the component. */
24
+ export const PANEL_MARGIN_CELLS = 1;
25
+ function columnSpecs(tier, markerCells) {
26
+ const marker = { key: 'marker', width: markerCells };
27
+ const delta = { key: 'delta', width: 7, align: 'right', drop: 2 };
28
+ switch (tier) {
29
+ case 'full':
30
+ return [
31
+ marker,
32
+ { key: 'id', width: 8 },
33
+ { key: 'role', width: 6, drop: 3 },
34
+ { key: 'state', width: 12 },
35
+ { key: 'gauge', width: GAUGE_CELLS + 1, drop: 5 },
36
+ { key: 'score', width: 5, align: 'right' },
37
+ { key: 'prov', width: 7, drop: 4 },
38
+ { key: 'tag', width: 8, drop: 6 },
39
+ { key: 'wp', width: 11 },
40
+ { key: 'activity', width: 0, elastic: true },
41
+ delta,
42
+ ];
43
+ case 'standard':
44
+ return [
45
+ marker,
46
+ { key: 'id', width: 8 },
47
+ { key: 'role', width: 6, drop: 3 },
48
+ { key: 'state', width: 12 },
49
+ { key: 'gauge', width: GAUGE_CELLS + 1, drop: 5 },
50
+ { key: 'score', width: 5, align: 'right' },
51
+ { key: 'prov', width: 7, drop: 4 },
52
+ { key: 'tag', width: 8, drop: 6 },
53
+ { key: 'wp', width: 0, elastic: true },
54
+ delta,
55
+ ];
56
+ default:
57
+ return [
58
+ marker,
59
+ { key: 'id', width: 8 },
60
+ { key: 'state', width: 12, drop: 3 },
61
+ { key: 'score', width: 5, align: 'right' },
62
+ { key: 'wp', width: 0, elastic: true },
63
+ delta,
64
+ ];
65
+ }
66
+ }
67
+ function fixedSum(specs) {
68
+ return specs.reduce((sum, spec) => sum + (spec.elastic ? 0 : spec.width), 0);
69
+ }
70
+ /** The column that gives way first: the highest `drop` rank, or -1 when none can. */
71
+ function dropVictim(specs) {
72
+ let victim = -1;
73
+ let rank = 0;
74
+ specs.forEach((spec, index) => {
75
+ if (spec.drop !== undefined && spec.drop > rank) {
76
+ rank = spec.drop;
77
+ victim = index;
78
+ }
79
+ });
80
+ return victim;
81
+ }
82
+ /** Last-resort squeeze so the sum NEVER exceeds `inner`, from the right edge inward. */
83
+ function squeeze(columns, inner) {
84
+ let over = columns.reduce((sum, column) => sum + column.width, 0) - inner;
85
+ for (let index = columns.length - 1; index >= 0 && over > 0; index -= 1) {
86
+ const column = columns[index];
87
+ const cut = Math.min(column.width, over);
88
+ column.width -= cut;
89
+ over -= cut;
90
+ }
91
+ }
92
+ /**
93
+ * Give the activity column up and make the work-package cell the elastic one, which turns
94
+ * a `full` row into a `standard` one without losing the single-elastic-column invariant.
95
+ */
96
+ function promoteWorkPackage(specs) {
97
+ return specs
98
+ .filter((spec) => spec.key !== 'activity')
99
+ .map((spec) => (spec.key === 'wp' ? { key: 'wp', width: 0, elastic: true } : spec));
100
+ }
101
+ /**
102
+ * The row's columns for a tier at `inner` columns (the terminal width minus the margin).
103
+ *
104
+ * THE DROP LADDER IS THE SPECIFICATION, in the order the cells give way: the activity
105
+ * text first (the only column whose loss costs nothing structural - the work-package cell
106
+ * becomes the elastic one), then the tag, gauge, provisional, role and delta cells. The
107
+ * elastic column keeps at least its floor; when even the fixed cluster does not fit,
108
+ * `squeeze` trims from the right so the sum stays inside `inner` for any input.
109
+ */
110
+ export function columnPlan(tier, inner, markerCells) {
111
+ const width = Number.isFinite(inner) ? Math.max(0, Math.floor(inner)) : 0;
112
+ let specs = columnSpecs(tier, markerCells);
113
+ const floorOf = (spec) => spec?.key === 'activity' ? ELASTIC_MIN_CELLS.activity : ELASTIC_MIN_CELLS.wp;
114
+ for (;;) {
115
+ const elastic = specs.find((spec) => spec.elastic === true);
116
+ if (fixedSum(specs) + floorOf(elastic) <= width)
117
+ break;
118
+ if (elastic?.key === 'activity') {
119
+ specs = promoteWorkPackage(specs);
120
+ continue;
121
+ }
122
+ const victim = dropVictim(specs);
123
+ if (victim < 0)
124
+ break;
125
+ specs = specs.filter((_, index) => index !== victim);
126
+ }
127
+ const remaining = Math.max(0, width - fixedSum(specs));
128
+ const columns = specs.map((spec) => ({
129
+ key: spec.key,
130
+ width: spec.elastic ? remaining : spec.width,
131
+ elastic: spec.elastic === true,
132
+ align: spec.align ?? 'left',
133
+ }));
134
+ squeeze(columns, width);
135
+ return columns;
136
+ }
137
+ export function columnsWidth(columns) {
138
+ return columns.reduce((sum, column) => sum + column.width, 0);
139
+ }
140
+ const BLANK_TONE = 'muted';
141
+ function textCell(column, text, tone, ellipsis, bold = false) {
142
+ const clipped = column.width === 0 ? '' : fitCells(text, column.width, ellipsis);
143
+ return {
144
+ key: column.key,
145
+ width: column.width,
146
+ elastic: column.elastic,
147
+ align: column.align,
148
+ segments: clipped === '' ? [] : [segment(clipped, tone, bold)],
149
+ };
150
+ }
151
+ function leading(text) {
152
+ return text === '' ? '' : ` ${text}`;
153
+ }
154
+ /** `not scored` replaces the gauge for the inspector (spec 5.1: never scored). */
155
+ function gaugeCell(column, row, glyphs) {
156
+ if (row.confirmed === null)
157
+ return textCell(column, NOT_SCORED, 'muted', glyphs.ellipsis);
158
+ const filled = gaugeFilledCells(row.confirmed);
159
+ const bar = [
160
+ segment(glyphs.gaugeFull.repeat(filled), scoreToneKey(row.label)),
161
+ segment(glyphs.gaugeEmpty.repeat(GAUGE_CELLS - filled), 'border'),
162
+ ].filter((part) => part.text !== '');
163
+ return {
164
+ key: column.key,
165
+ width: column.width,
166
+ elastic: false,
167
+ align: 'left',
168
+ segments: bar,
169
+ };
170
+ }
171
+ function stateText(row) {
172
+ return row.state === 'working' ? WORKING_VERB[row.role] : STATE_WORD[row.state];
173
+ }
174
+ function rightAligned(column, text) {
175
+ return ' '.repeat(Math.max(0, column.width - cellWidth(text))) + text;
176
+ }
177
+ function scoreCell(column, row, ctx) {
178
+ const ell = ctx.glyphs.ellipsis;
179
+ if (row.confirmed === null)
180
+ return textCell(column, '', BLANK_TONE, ell);
181
+ const text = rightAligned(column, scoreText(row.confirmed));
182
+ return textCell(column, text, scoreToneKey(row.label), ell);
183
+ }
184
+ function deltaColumnCell(column, row, ctx) {
185
+ const delta = deltaCell(row, ctx.now, ctx.reducedMotion);
186
+ const text = delta.text === '' ? '' : rightAligned(column, delta.text);
187
+ return textCell(column, text, delta.tone, ctx.glyphs.ellipsis, delta.bold);
188
+ }
189
+ /** One builder per column: a table, so adding a column cannot raise a function's complexity. */
190
+ const CELL_BUILDERS = {
191
+ marker: (column, row, ctx) => {
192
+ const status = govChildStatus(row.state);
193
+ const text = ` ${statusMarkerCell(status, ctx.glyphs)} `;
194
+ return textCell(column, text, statusTone(status), ctx.glyphs.ellipsis);
195
+ },
196
+ id: (column, row, ctx) => textCell(column, row.id, 'primary', ctx.glyphs.ellipsis),
197
+ role: (column, row, ctx) => textCell(column, ROLE_LABEL[row.role], 'muted', ctx.glyphs.ellipsis),
198
+ state: (column, row, ctx) => {
199
+ const tone = statusTone(govChildStatus(row.state));
200
+ return textCell(column, stateText(row), tone, ctx.glyphs.ellipsis);
201
+ },
202
+ gauge: (column, row, ctx) => gaugeCell(column, row, ctx.glyphs),
203
+ score: scoreCell,
204
+ prov: (column, row, ctx) => {
205
+ const text = row.provisional > 0 ? leading(`+${row.provisional}P`) : '';
206
+ return textCell(column, text, 'muted', ctx.glyphs.ellipsis);
207
+ },
208
+ tag: (column, row, ctx) => {
209
+ const text = leading(labelTag(row.label));
210
+ return textCell(column, text, scoreToneKey(row.label), ctx.glyphs.ellipsis, true);
211
+ },
212
+ wp: (column, row, ctx) => textCell(column, leading(row.wpId ?? '-'), 'text', ctx.glyphs.ellipsis),
213
+ activity: (column, row, ctx) => {
214
+ const text = leading(row.activity.replace(/\s+/g, ' ').trim());
215
+ return textCell(column, text, 'muted', ctx.glyphs.ellipsis);
216
+ },
217
+ delta: deltaColumnCell,
218
+ note: (column, _row, ctx) => textCell(column, '', BLANK_TONE, ctx.glyphs.ellipsis),
219
+ };
220
+ function cellFor(column, row, ctx) {
221
+ return CELL_BUILDERS[column.key](column, row, ctx);
222
+ }
223
+ /** Columns a replaced member's one-line note spans. */
224
+ const NOTE_SPAN = new Set(['state', 'gauge', 'score', 'prov', 'tag']);
225
+ /**
226
+ * A replaced member: marker, id and role stay (so the row is recognisable), and one
227
+ * muted note spans the state-through-tag cells. The note NAMES the successor, which is the
228
+ * information the member's vanishing from the panel next phase would otherwise erase.
229
+ */
230
+ function eliminatedCells(columns, row, ctx) {
231
+ const cells = [];
232
+ let spanned = 0;
233
+ let spanColumn = null;
234
+ const flush = () => {
235
+ if (spanColumn === null)
236
+ return;
237
+ const merged = { ...spanColumn, key: 'note', width: spanned, align: 'left' };
238
+ const text = ` ${replacedByText(row.replacedBy)}`;
239
+ cells.push(textCell(merged, text, 'muted', ctx.glyphs.ellipsis));
240
+ spanColumn = null;
241
+ spanned = 0;
242
+ };
243
+ for (const column of columns) {
244
+ if (NOTE_SPAN.has(column.key)) {
245
+ spanColumn ??= column;
246
+ spanned += column.width;
247
+ continue;
248
+ }
249
+ flush();
250
+ const cell = cellFor(column, row, ctx);
251
+ const quiet = column.key === 'id' || column.key === 'role' || column.key === 'marker';
252
+ cells.push(quiet ? mute(cell) : blank(cell));
253
+ }
254
+ flush();
255
+ return cells;
256
+ }
257
+ function mute(cell) {
258
+ return { ...cell, segments: cell.segments.map((part) => ({ ...part, tone: 'muted' })) };
259
+ }
260
+ function blank(cell) {
261
+ return { ...cell, segments: [] };
262
+ }
263
+ /** One roster row's cells, text already clamped to its cell. */
264
+ export function buildRosterLine(input) {
265
+ const { columns, row, glyphs, now, reducedMotion } = input;
266
+ const ctx = { glyphs, now, reducedMotion };
267
+ if (row.state === 'eliminated') {
268
+ return { key: row.id, kind: 'eliminated', cells: eliminatedCells(columns, row, ctx) };
269
+ }
270
+ return { key: row.id, kind: 'agent', cells: columns.map((column) => cellFor(column, row, ctx)) };
271
+ }
272
+ /** The marker cell's width in the current glyph tier: 1 + widest marker + 1. */
273
+ export function markerCells(glyphs) {
274
+ return 1 + statusMarkerWidth(glyphs) + 1;
275
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Invocation card layout (tui-invocation-cards section 3.7): what a `user` entry that
3
+ * carries an `invocation` says, as pure data. No Ink, no React. ASCII ONLY.
4
+ *
5
+ * THE CARD'S HEIGHT IS A FUNCTION OF ITS DATA AND THE USABLE WIDTH (I-4).
6
+ * `InvocationCard.tsx` renders exactly the rows `invocationCardRows` counts, and both
7
+ * read `INVOCATION_LIMITS` plus the one `invocationInputText` below - an estimate that
8
+ * disagrees with the painter is permanent, because an over-estimated entry is never
9
+ * mounted and so never measured (see `virtual-window.ts`).
10
+ */
11
+ import type { InvocationCard } from '../../agent/invocation-card.js';
12
+ import type { Glyphs } from '../glyphs.js';
13
+ /** Colour slots the component maps onto the theme; kept local so this file needs no `Theme`. */
14
+ export type InvocationTone = 'label' | 'name' | 'muted';
15
+ export interface InvocationSegment {
16
+ text: string;
17
+ tone: InvocationTone;
18
+ bold: boolean;
19
+ }
20
+ /**
21
+ * The header row, already fitted to `usable` columns (RV-10). Narrow terminals drop
22
+ * facts from the right WHOLE before anything else is cut; only when the identity alone
23
+ * is too wide is the name shortened. Ink's `wrap="truncate"` stays on the row as a
24
+ * backstop, but it is never what produces the visible ellipsis.
25
+ */
26
+ export declare function invocationHeaderSegments(card: InvocationCard, glyphs: Glyphs, usable: number): InvocationSegment[];
27
+ /** The text painted for `input`: tabs expanded per line, the same rule `UserEntry` follows. */
28
+ export declare function invocationInputText(card: InvocationCard): string;
29
+ /** Singular/plural for the hidden-characters tail. */
30
+ export declare function invocationHiddenText(card: InvocationCard, glyphs: Glyphs): string;
31
+ /**
32
+ * Rows the card occupies at `usable` columns. `wrappedRows` is injected by the caller
33
+ * (`virtual-window.ts` owns the one wrapping rule) so this file stays import-cycle free.
34
+ */
35
+ export declare function invocationCardRows(card: InvocationCard, usable: number, wrappedRows: (text: string, usable: number) => number): number;
36
+ /**
37
+ * The lines the exit replay / export prints UNDER the command line (section 2.4):
38
+ * a bracketed fact line, and the disclosure note when there is one. Plain ASCII
39
+ * separators - the replay is pasted into issues and must survive any terminal.
40
+ */
41
+ export declare function invocationCardLines(card: InvocationCard): string[];
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Invocation card layout (tui-invocation-cards section 3.7): what a `user` entry that
3
+ * carries an `invocation` says, as pure data. No Ink, no React. ASCII ONLY.
4
+ *
5
+ * THE CARD'S HEIGHT IS A FUNCTION OF ITS DATA AND THE USABLE WIDTH (I-4).
6
+ * `InvocationCard.tsx` renders exactly the rows `invocationCardRows` counts, and both
7
+ * read `INVOCATION_LIMITS` plus the one `invocationInputText` below - an estimate that
8
+ * disagrees with the painter is permanent, because an over-estimated entry is never
9
+ * mounted and so never measured (see `virtual-window.ts`).
10
+ */
11
+ import { expandTabs } from '../display-tab.js';
12
+ import { cellWidth, fitCells } from './cells.js';
13
+ /** Two spaces between the identity (`skill . pdf-forms`) and the facts. */
14
+ const FACT_GAP = ' ';
15
+ function seg(text, tone, bold = false) {
16
+ return { text, tone, bold };
17
+ }
18
+ function segmentsWidth(segments) {
19
+ return segments.reduce((sum, part) => sum + cellWidth(part.text), 0);
20
+ }
21
+ function identitySegments(card, glyphs, name) {
22
+ const parts = [seg(card.variant, 'label', true)];
23
+ if (name !== '')
24
+ parts.push(seg(` ${glyphs.midDot} `, 'muted'), seg(name, 'name', true));
25
+ return parts;
26
+ }
27
+ function factSegments(facts, glyphs) {
28
+ if (facts.length === 0)
29
+ return [];
30
+ const parts = [seg(FACT_GAP, 'muted')];
31
+ facts.forEach((fact, index) => {
32
+ if (index > 0)
33
+ parts.push(seg(` ${glyphs.midDot} `, 'muted'));
34
+ parts.push(seg(fact, 'muted'));
35
+ });
36
+ return parts;
37
+ }
38
+ /**
39
+ * The header row, already fitted to `usable` columns (RV-10). Narrow terminals drop
40
+ * facts from the right WHOLE before anything else is cut; only when the identity alone
41
+ * is too wide is the name shortened. Ink's `wrap="truncate"` stays on the row as a
42
+ * backstop, but it is never what produces the visible ellipsis.
43
+ */
44
+ export function invocationHeaderSegments(card, glyphs, usable) {
45
+ const width = Math.max(1, Math.floor(usable));
46
+ for (let kept = card.facts.length; kept >= 0; kept -= 1) {
47
+ const candidate = [
48
+ ...identitySegments(card, glyphs, card.name),
49
+ ...factSegments(card.facts.slice(0, kept), glyphs),
50
+ ];
51
+ if (segmentsWidth(candidate) <= width)
52
+ return candidate;
53
+ }
54
+ const head = identitySegments(card, glyphs, '');
55
+ const room = width - segmentsWidth(head) - cellWidth(` ${glyphs.midDot} `);
56
+ if (card.name !== '' && room > 1) {
57
+ return identitySegments(card, glyphs, fitCells(card.name, room, glyphs.ellipsis));
58
+ }
59
+ return [seg(fitCells(card.variant, width, glyphs.ellipsis), 'label', true)];
60
+ }
61
+ /** The text painted for `input`: tabs expanded per line, the same rule `UserEntry` follows. */
62
+ export function invocationInputText(card) {
63
+ return card.input.split('\n').map((line) => expandTabs(line)).join('\n');
64
+ }
65
+ /** Singular/plural for the hidden-characters tail. */
66
+ export function invocationHiddenText(card, glyphs) {
67
+ const unit = card.inputHidden === 1 ? 'char' : 'chars';
68
+ return `${glyphs.ellipsis} +${card.inputHidden} ${unit} not shown`;
69
+ }
70
+ /**
71
+ * Rows the card occupies at `usable` columns. `wrappedRows` is injected by the caller
72
+ * (`virtual-window.ts` owns the one wrapping rule) so this file stays import-cycle free.
73
+ */
74
+ export function invocationCardRows(card, usable, wrappedRows) {
75
+ const inputRows = card.input === '' ? 0 : wrappedRows(invocationInputText(card), usable);
76
+ return 1 + inputRows + (card.inputHidden > 0 ? 1 : 0) + (card.note ? 1 : 0);
77
+ }
78
+ const REPLAY_INDENT = ' ';
79
+ /**
80
+ * The lines the exit replay / export prints UNDER the command line (section 2.4):
81
+ * a bracketed fact line, and the disclosure note when there is one. Plain ASCII
82
+ * separators - the replay is pasted into issues and must survive any terminal.
83
+ */
84
+ export function invocationCardLines(card) {
85
+ const identity = card.name === '' ? card.variant : `${card.variant} ${card.name}`;
86
+ const detail = card.facts.length === 0 ? identity : `${identity} ${card.facts.join(' - ')}`;
87
+ const lines = [`${REPLAY_INDENT}[${detail}]`];
88
+ if (card.note)
89
+ lines.push(`${REPLAY_INDENT}${card.note}`);
90
+ return lines;
91
+ }
@@ -1,13 +1,13 @@
1
1
  import { formatCost, formatTokens, promptTokensOf } from '../../agent/usage.js';
2
2
  import { actionCandidates, clipStatusText, escapeClause } from '../interaction-hints.js';
3
- import { MODE_LABEL } from '../../agent/agent-mode.js';
3
+ import { isGovernedModeValue, modeWord } from '../governance-status.js';
4
4
  import { statusField, statusLinePlan, validContext } from './status-layout.js';
5
5
  /**
6
6
  * Uppercase mode word for the details row, mirroring the primary row's field:
7
7
  * same vocabulary (`MODE_LABEL`), same `CUR>PENDING` deferred-switch shape.
8
8
  */
9
9
  function detailModeText(mode, pendingMode) {
10
- const word = (value) => MODE_LABEL[value] ?? value.toUpperCase();
10
+ const word = (value) => modeWord(value, true) ?? value.toUpperCase();
11
11
  if (pendingMode)
12
12
  return `${word(mode || 'build')}>${word(pendingMode)}`;
13
13
  return word(mode || 'build');
@@ -41,7 +41,7 @@ export function planStatusDetail(input) {
41
41
  if (status.servicesActive?.live)
42
42
  add(statusField('services', 'Svc' + status.servicesActive.live));
43
43
  if (status.mode || status.pendingMode)
44
- add(statusField('mode', detailModeText(status.mode, status.pendingMode), 'accent'));
44
+ add(statusField('mode', detailModeText(status.mode, status.pendingMode), isGovernedModeValue(status.mode) || isGovernedModeValue(status.pendingMode) ? 'govern' : 'accent'));
45
45
  if (validContext(status.context)) {
46
46
  const approximate = status.context.source === 'estimate' || status.context.deltaTokens > 0 ? '~' : '';
47
47
  add(statusField('context', 'Context ' + approximate + formatTokens(status.context.occupied)
@@ -117,8 +117,11 @@ export interface StatusField {
117
117
  id: string;
118
118
  text: string;
119
119
  cells: number;
120
- /** 'accent' carries the mode word - the row's one always-on emphasis. */
121
- tone: 'normal' | 'muted' | 'warning' | 'error' | 'accent';
120
+ /**
121
+ * 'accent' carries the mode word - the row's one always-on emphasis. 'govern' is the
122
+ * same slot while the posture is GOVERN / PLAN+GOVERN (`governAccent`, its own hue).
123
+ */
124
+ tone: 'normal' | 'muted' | 'warning' | 'error' | 'accent' | 'govern';
122
125
  }
123
126
  export interface StatusLinePlan {
124
127
  fields: readonly StatusField[];
@@ -1,7 +1,7 @@
1
1
  import stringWidth from 'string-width';
2
2
  import stripAnsi from 'strip-ansi';
3
3
  import { formatDuration } from '../../agent/usage.js';
4
- import { MODE_LABEL } from '../../agent/agent-mode.js';
4
+ import { isGovernedModeValue, modeWord } from '../governance-status.js';
5
5
  import { interactionCopy as copy } from '../interaction-copy.js';
6
6
  export function cleanStatusText(text) {
7
7
  // eslint-disable-next-line no-control-regex
@@ -170,10 +170,12 @@ function modeFieldText(mode, pendingMode, full) {
170
170
  if (!mode && !pendingMode)
171
171
  return null;
172
172
  const word = (value) => {
173
- const label = MODE_LABEL[value];
174
- if (!label)
175
- return full ? value.toUpperCase() : (value[0] ?? '?').toUpperCase();
176
- return full ? label : label[0];
173
+ // `build` / `plan` answer exactly as before; the two governed postures are the only
174
+ // other values `modeWord` knows (`PG` is the short form of PLAN+GOVERN, since `P` is PLAN).
175
+ const known = modeWord(value, full);
176
+ if (known !== undefined)
177
+ return known;
178
+ return full ? value.toUpperCase() : (value[0] ?? '?').toUpperCase();
177
179
  };
178
180
  const current = word(mode || 'build');
179
181
  return pendingMode ? `${current}>${word(pendingMode)}` : current;
@@ -231,7 +233,9 @@ export function planPrimaryStatusFields(input) {
231
233
  return statusLinePlan([
232
234
  statusField('phase', glyph + (rung >= 5 ? '' : ' ') + label, phaseTone),
233
235
  ...(notice ? [statusField('feedback', notice, feedbackTone)] : []),
234
- ...(modeText ? [statusField('mode', modeText, 'accent')] : []),
236
+ ...(modeText
237
+ ? [statusField('mode', modeText, isGovernedModeValue(input.mode) || isGovernedModeValue(input.pendingMode) ? 'govern' : 'accent')]
238
+ : []),
235
239
  ...(forcedText ? [statusField('forcedTeam', forcedText, 'accent')] : []),
236
240
  statusField('context', (full ? 'Context ' : 'C') + pct
237
241
  + (input.compactionPaused === true ? pausedSuffix(full) : '')),
@@ -33,12 +33,14 @@
33
33
  * through the mounted set and the measured heights back to the offset.
34
34
  */
35
35
  import stringWidth from 'string-width';
36
+ import { governanceCardRows } from './governance-card.js';
36
37
  import { FAST_LIMITS } from '../../fast/limits.js';
37
38
  import { COMPACTION_LIMITS } from '../../compaction/limits.js';
38
39
  import { GUTTER_WIDTH } from './Gutter.js';
39
40
  import { expandTabs } from '../display-tab.js';
40
41
  import { USER_ENTRY_MAX_ROWS } from '../composer-limits.js';
41
42
  import { isLiveTeamCard } from '../team-status.js';
43
+ import { invocationCardRows } from './invocation-card.js';
42
44
  export const VIRTUAL_LIMITS = {
43
45
  /**
44
46
  * Entries kept mounted on each side of the visible band.
@@ -67,8 +69,12 @@ export const VIRTUAL_LIMITS = {
67
69
  */
68
70
  export function entryRevision(entry) {
69
71
  switch (entry.kind) {
72
+ // A card's shape is fixed at creation; the suffix only tells "card" from "plain" (and
73
+ // the three row-bearing parts), which is what makes a `queued -> user` rewrite re-measure.
70
74
  case 'user':
71
- return 'u';
75
+ return entry.invocation
76
+ ? `i${entry.invocation.input.length}.${entry.invocation.inputHidden > 0 ? 1 : 0}.${entry.invocation.note ? 1 : 0}`
77
+ : 'u';
72
78
  case 'notice':
73
79
  return 'n';
74
80
  // `thinkingMs` is O(1) and it CHANGES THE ROW: sealing the clock turns
@@ -153,6 +159,11 @@ export function entryRevision(entry) {
153
159
  */
154
160
  case 'service':
155
161
  return `v${entry.status}.${entry.rowsSeen}.${entry.url ? 1 : 0}.${entry.exitCode ?? -1}.${entry.terminal ? 1 : 0}`;
162
+ // A governance card is IMMUTABLE once appended (it is a settled record), so its
163
+ // revision only has to tell two cards apart: the shape terms below are what decide
164
+ // its height (`governanceCardRows`), and they cannot change without a new entry.
165
+ case 'governance':
166
+ return `g${entry.card.rows.length}.${entry.card.notes.length}.${entry.card.headline.length}`;
156
167
  default:
157
168
  return 'x';
158
169
  }
@@ -253,6 +264,11 @@ export function estimateEntryRows(entry, cols, density, expanded, thinkingVisibl
253
264
  const separation = density === 'comfortable' ? 1 : 0;
254
265
  switch (entry.kind) {
255
266
  case 'user':
267
+ // A CARD HAS ITS OWN, BOUNDED HEIGHT (tui-invocation-cards I-4): the same
268
+ // `invocationCardRows` the painter is built from, never the command line's wrapped rows.
269
+ if (entry.invocation) {
270
+ return separation + invocationCardRows(entry.invocation, usable, wrappedRows);
271
+ }
256
272
  // MIRRORS `UserEntry`'s RENDER CAP (+1 for the `... +N more lines` tail).
257
273
  // THESE TWO NUMBERS ARE ONE NUMBER (I-13). Capping the renderer alone
258
274
  // would leave this function believing a 5 000-line message is 5 000 rows
@@ -402,6 +418,11 @@ export function estimateEntryRows(entry, cols, density, expanded, thinkingVisibl
402
418
  const progress = entry.progress || entry.stalled ? SERVICE_PROGRESS_ROWS : 0;
403
419
  return separation + 1 + progress + url + tail;
404
420
  }
421
+ // EXACT, not an upper bound: every line of the card is `wrap="truncate"`, so its
422
+ // height is a function of its data alone (`layout/governance-card.ts`). It has to
423
+ // be added to BOTH of this file's switches - one alone yields scroll drift.
424
+ case 'governance':
425
+ return separation + governanceCardRows(entry.card);
405
426
  default:
406
427
  return separation + 1;
407
428
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * GovernanceBoardOverlay - the read-only `/gov board` overview
3
+ * (governance-mode section 10.5).
4
+ *
5
+ * ASCII ONLY: every symbol comes through `glyphs`.
6
+ *
7
+ * READ-ONLY IS THE SAFETY BOUNDARY, as for the Agents board: nothing here scores, rules
8
+ * or resolves anything. The only side effect is `e`, which asks the owner to copy the
9
+ * `board.md` path.
10
+ *
11
+ * `OverlayFrame` SELF-MANAGED MODE, with this file owning the window. The tab strip must
12
+ * stay put while the rows scroll, which the frame's controlled mode (it windows EVERY row
13
+ * it is given) cannot do; so the body is "one strip row + a window of rows" and the
14
+ * windowing is `sliceWindow` through `boardWindow`, the arithmetic the frame itself uses.
15
+ * `PgUp` / `PgDn` / the wheel arrive through `App`'s `controlled` overlay list as
16
+ * `scrollOffset`; Up/Down and Left/Right are this component's own keys, the same split the
17
+ * Agents board makes.
18
+ *
19
+ * NO `ink-spinner` and no motion prop (spinner-census): this is a reading surface.
20
+ */
21
+ import React from 'react';
22
+ import type { Theme } from '../theme.js';
23
+ import type { TermCapabilities } from '../capabilities.js';
24
+ import { type BoardTab, type GovernanceBoardData } from '../layout/governance-board.js';
25
+ export interface GovernanceBoardOverlayProps {
26
+ /** The board's data, or null when there is no governed run to show. */
27
+ data: GovernanceBoardData | null;
28
+ /** The overlay's FRAME width (`App`'s `viewCols`). */
29
+ cols: number;
30
+ maxRows: number;
31
+ scrollOffset: number;
32
+ onScrollClamp: (offset: number) => void;
33
+ isActive: boolean;
34
+ theme: Theme;
35
+ caps: TermCapabilities;
36
+ /** `e`: copy the board file path. Absent = the key reports the path as unavailable. */
37
+ onCopyPath?: (path: string) => void;
38
+ onNotice?: (level: 'info' | 'warn', text: string) => void;
39
+ initialTab?: BoardTab;
40
+ }
41
+ export declare function GovernanceBoardOverlay({ data, cols, maxRows, scrollOffset, onScrollClamp, isActive, theme, caps, onCopyPath, onNotice, initialTab, }: GovernanceBoardOverlayProps): React.ReactElement;