@aragon-agent/cli 0.6.44 → 0.6.45

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 (236) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +69 -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/plan-prompt.d.ts +7 -0
  10. package/dist/agent/plan-prompt.js +2 -1
  11. package/dist/agent/reducer.d.ts +33 -1
  12. package/dist/agent/reducer.js +45 -9
  13. package/dist/agent/system-prompt.d.ts +19 -0
  14. package/dist/agent/system-prompt.js +9 -1
  15. package/dist/cli.js +45 -1
  16. package/dist/commands/builtins.js +10 -4
  17. package/dist/commands/governance-command.d.ts +35 -0
  18. package/dist/commands/governance-command.js +292 -0
  19. package/dist/commands/governance-runs-table.d.ts +40 -0
  20. package/dist/commands/governance-runs-table.js +130 -0
  21. package/dist/commands/registry.d.ts +12 -0
  22. package/dist/config/app-paths.d.ts +10 -0
  23. package/dist/config/app-paths.js +12 -0
  24. package/dist/config/cli-commands.d.ts +18 -0
  25. package/dist/config/cli-commands.js +47 -1
  26. package/dist/config/env.d.ts +2 -1
  27. package/dist/config/env.js +10 -1
  28. package/dist/config/governance-config.d.ts +125 -0
  29. package/dist/config/governance-config.js +164 -0
  30. package/dist/config/governance-launch.d.ts +71 -0
  31. package/dist/config/governance-launch.js +58 -0
  32. package/dist/config/load.d.ts +15 -0
  33. package/dist/config/load.js +26 -1
  34. package/dist/config/model-profile-store.d.ts +4 -2
  35. package/dist/config/schema.d.ts +22 -0
  36. package/dist/config/schema.js +6 -0
  37. package/dist/config/store.js +14 -1
  38. package/dist/config/ui-state.d.ts +11 -0
  39. package/dist/config/ui-state.js +11 -0
  40. package/dist/diagnostics/doctor.js +20 -0
  41. package/dist/governance/card-normalize.d.ts +24 -0
  42. package/dist/governance/card-normalize.js +76 -0
  43. package/dist/governance/copy.d.ts +109 -0
  44. package/dist/governance/copy.js +223 -0
  45. package/dist/governance/engine/aftermath.d.ts +49 -0
  46. package/dist/governance/engine/aftermath.js +134 -0
  47. package/dist/governance/engine/artifact-persist.d.ts +35 -0
  48. package/dist/governance/engine/artifact-persist.js +86 -0
  49. package/dist/governance/engine/budget.d.ts +52 -0
  50. package/dist/governance/engine/budget.js +112 -0
  51. package/dist/governance/engine/cards.d.ts +18 -0
  52. package/dist/governance/engine/cards.js +186 -0
  53. package/dist/governance/engine/digest.d.ts +32 -0
  54. package/dist/governance/engine/digest.js +63 -0
  55. package/dist/governance/engine/engine-core.d.ts +219 -0
  56. package/dist/governance/engine/engine-core.js +562 -0
  57. package/dist/governance/engine/engine.d.ts +68 -0
  58. package/dist/governance/engine/engine.js +314 -0
  59. package/dist/governance/engine/estimate.d.ts +38 -0
  60. package/dist/governance/engine/estimate.js +46 -0
  61. package/dist/governance/engine/inspector.d.ts +16 -0
  62. package/dist/governance/engine/inspector.js +396 -0
  63. package/dist/governance/engine/integration.d.ts +35 -0
  64. package/dist/governance/engine/integration.js +220 -0
  65. package/dist/governance/engine/lifecycle.d.ts +29 -0
  66. package/dist/governance/engine/lifecycle.js +656 -0
  67. package/dist/governance/engine/pause.d.ts +56 -0
  68. package/dist/governance/engine/pause.js +107 -0
  69. package/dist/governance/engine/phase-members.d.ts +15 -0
  70. package/dist/governance/engine/phase-members.js +34 -0
  71. package/dist/governance/engine/phase-planner.d.ts +53 -0
  72. package/dist/governance/engine/phase-planner.js +282 -0
  73. package/dist/governance/engine/rebuild.d.ts +27 -0
  74. package/dist/governance/engine/rebuild.js +57 -0
  75. package/dist/governance/engine/report.d.ts +66 -0
  76. package/dist/governance/engine/report.js +220 -0
  77. package/dist/governance/engine/run-digest.d.ts +20 -0
  78. package/dist/governance/engine/run-digest.js +42 -0
  79. package/dist/governance/engine/spec-builder.d.ts +117 -0
  80. package/dist/governance/engine/spec-builder.js +321 -0
  81. package/dist/governance/engine/stage-runner.d.ts +22 -0
  82. package/dist/governance/engine/stage-runner.js +664 -0
  83. package/dist/governance/engine/start-input.d.ts +19 -0
  84. package/dist/governance/engine/start-input.js +111 -0
  85. package/dist/governance/engine/state-machine.d.ts +101 -0
  86. package/dist/governance/engine/state-machine.js +586 -0
  87. package/dist/governance/engine/submission-queries.d.ts +42 -0
  88. package/dist/governance/engine/submission-queries.js +97 -0
  89. package/dist/governance/engine/submission-render.d.ts +12 -0
  90. package/dist/governance/engine/submission-render.js +92 -0
  91. package/dist/governance/engine/submissions.d.ts +27 -0
  92. package/dist/governance/engine/submissions.js +455 -0
  93. package/dist/governance/engine/visibility.d.ts +104 -0
  94. package/dist/governance/engine/visibility.js +223 -0
  95. package/dist/governance/governed-dispatch.d.ts +19 -0
  96. package/dist/governance/governed-dispatch.js +17 -0
  97. package/dist/governance/limits.d.ts +107 -0
  98. package/dist/governance/limits.js +131 -0
  99. package/dist/governance/posture-transition.d.ts +38 -0
  100. package/dist/governance/posture-transition.js +44 -0
  101. package/dist/governance/posture.d.ts +35 -0
  102. package/dist/governance/posture.js +59 -0
  103. package/dist/governance/profiles.d.ts +48 -0
  104. package/dist/governance/profiles.js +140 -0
  105. package/dist/governance/prompts/orchestrator-block.d.ts +40 -0
  106. package/dist/governance/prompts/orchestrator-block.js +128 -0
  107. package/dist/governance/prompts/plan-addendum.d.ts +22 -0
  108. package/dist/governance/prompts/plan-addendum.js +60 -0
  109. package/dist/governance/prompts/refusals.d.ts +48 -0
  110. package/dist/governance/prompts/refusals.js +82 -0
  111. package/dist/governance/prompts/role-blocks.d.ts +40 -0
  112. package/dist/governance/prompts/role-blocks.js +255 -0
  113. package/dist/governance/report-reader.d.ts +31 -0
  114. package/dist/governance/report-reader.js +57 -0
  115. package/dist/governance/roles.d.ts +83 -0
  116. package/dist/governance/roles.js +142 -0
  117. package/dist/governance/rules/claims.d.ts +83 -0
  118. package/dist/governance/rules/claims.js +250 -0
  119. package/dist/governance/rules/elimination.d.ts +66 -0
  120. package/dist/governance/rules/elimination.js +105 -0
  121. package/dist/governance/rules/events-table.d.ts +46 -0
  122. package/dist/governance/rules/events-table.js +94 -0
  123. package/dist/governance/rules/failure-analysis.d.ts +28 -0
  124. package/dist/governance/rules/failure-analysis.js +93 -0
  125. package/dist/governance/rules/ledger-replay.d.ts +53 -0
  126. package/dist/governance/rules/ledger-replay.js +53 -0
  127. package/dist/governance/rules/ledger-state.d.ts +85 -0
  128. package/dist/governance/rules/ledger-state.js +175 -0
  129. package/dist/governance/rules/ledger.d.ts +228 -0
  130. package/dist/governance/rules/ledger.js +560 -0
  131. package/dist/governance/rules/rca.d.ts +68 -0
  132. package/dist/governance/rules/rca.js +220 -0
  133. package/dist/governance/rules/settlement.d.ts +129 -0
  134. package/dist/governance/rules/settlement.js +363 -0
  135. package/dist/governance/rules/suspicion.d.ts +66 -0
  136. package/dist/governance/rules/suspicion.js +169 -0
  137. package/dist/governance/store/artifacts.d.ts +81 -0
  138. package/dist/governance/store/artifacts.js +173 -0
  139. package/dist/governance/store/atomic.d.ts +79 -0
  140. package/dist/governance/store/atomic.js +119 -0
  141. package/dist/governance/store/events-log.d.ts +35 -0
  142. package/dist/governance/store/events-log.js +86 -0
  143. package/dist/governance/store/run-lock.d.ts +51 -0
  144. package/dist/governance/store/run-lock.js +122 -0
  145. package/dist/governance/store/run-store.d.ts +229 -0
  146. package/dist/governance/store/run-store.js +644 -0
  147. package/dist/governance/store/scoreboard-export.d.ts +53 -0
  148. package/dist/governance/store/scoreboard-export.js +65 -0
  149. package/dist/governance/tools/child-tools.d.ts +31 -0
  150. package/dist/governance/tools/child-tools.js +65 -0
  151. package/dist/governance/tools/guard.d.ts +71 -0
  152. package/dist/governance/tools/guard.js +194 -0
  153. package/dist/governance/tools/inspector-tools.d.ts +63 -0
  154. package/dist/governance/tools/inspector-tools.js +424 -0
  155. package/dist/governance/tools/lead-tools.d.ts +42 -0
  156. package/dist/governance/tools/lead-tools.js +226 -0
  157. package/dist/governance/tools/normalize-util.d.ts +101 -0
  158. package/dist/governance/tools/normalize-util.js +230 -0
  159. package/dist/governance/tools/scan-incomplete.d.ts +79 -0
  160. package/dist/governance/tools/scan-incomplete.js +486 -0
  161. package/dist/governance/tools/shared-tools.d.ts +44 -0
  162. package/dist/governance/tools/shared-tools.js +125 -0
  163. package/dist/governance/tools/submit-normalizers.d.ts +28 -0
  164. package/dist/governance/tools/submit-normalizers.js +474 -0
  165. package/dist/governance/tools/submit-tools.d.ts +26 -0
  166. package/dist/governance/tools/submit-tools.js +171 -0
  167. package/dist/governance/types.d.ts +661 -0
  168. package/dist/governance/types.js +27 -0
  169. package/dist/session/validate-session.js +32 -1
  170. package/dist/team/runtime.d.ts +8 -0
  171. package/dist/team/runtime.js +9 -0
  172. package/dist/team/subagent.d.ts +23 -0
  173. package/dist/team/subagent.js +14 -3
  174. package/dist/team/types.d.ts +6 -0
  175. package/dist/tools/index.d.ts +30 -7
  176. package/dist/tools/index.js +14 -0
  177. package/dist/tools/plan-tools.d.ts +14 -2
  178. package/dist/tools/plan-tools.js +5 -2
  179. package/dist/ui/App.js +145 -25
  180. package/dist/ui/BottomStatusRow.js +1 -1
  181. package/dist/ui/Composer.d.ts +7 -1
  182. package/dist/ui/Composer.js +9 -6
  183. package/dist/ui/GovernancePanel.d.ts +50 -0
  184. package/dist/ui/GovernancePanel.js +77 -0
  185. package/dist/ui/GovernanceRun.d.ts +18 -0
  186. package/dist/ui/GovernanceRun.js +11 -0
  187. package/dist/ui/StatusBar.d.ts +15 -2
  188. package/dist/ui/StatusBar.js +10 -4
  189. package/dist/ui/Transcript.js +3 -0
  190. package/dist/ui/entries/GovernanceCard.d.ts +30 -0
  191. package/dist/ui/entries/GovernanceCard.js +32 -0
  192. package/dist/ui/governance-copy.d.ts +69 -0
  193. package/dist/ui/governance-copy.js +104 -0
  194. package/dist/ui/governance-status.d.ts +145 -0
  195. package/dist/ui/governance-status.js +326 -0
  196. package/dist/ui/interaction-copy.d.ts +4 -0
  197. package/dist/ui/interaction-copy.js +5 -0
  198. package/dist/ui/interaction-hints.d.ts +17 -0
  199. package/dist/ui/interaction-hints.js +14 -2
  200. package/dist/ui/layout/governance-board.d.ts +121 -0
  201. package/dist/ui/layout/governance-board.js +382 -0
  202. package/dist/ui/layout/governance-card.d.ts +53 -0
  203. package/dist/ui/layout/governance-card.js +134 -0
  204. package/dist/ui/layout/governance-chip.d.ts +38 -0
  205. package/dist/ui/layout/governance-chip.js +64 -0
  206. package/dist/ui/layout/governance-panel.d.ts +85 -0
  207. package/dist/ui/layout/governance-panel.js +358 -0
  208. package/dist/ui/layout/governance-roster.d.ts +65 -0
  209. package/dist/ui/layout/governance-roster.js +275 -0
  210. package/dist/ui/layout/status-detail-layout.js +3 -3
  211. package/dist/ui/layout/status-layout.d.ts +5 -2
  212. package/dist/ui/layout/status-layout.js +10 -6
  213. package/dist/ui/layout/virtual-window.js +11 -0
  214. package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +41 -0
  215. package/dist/ui/overlays/GovernanceBoardOverlay.js +76 -0
  216. package/dist/ui/overlays/GovernanceSettingsPage.d.ts +48 -0
  217. package/dist/ui/overlays/GovernanceSettingsPage.js +93 -0
  218. package/dist/ui/overlays/HelpOverlay.js +4 -3
  219. package/dist/ui/overlays/QuestionOverlay.d.ts +9 -1
  220. package/dist/ui/overlays/QuestionOverlay.js +4 -2
  221. package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
  222. package/dist/ui/overlays/SettingsScreen.js +21 -1
  223. package/dist/ui/overlays/governance-settings.d.ts +79 -0
  224. package/dist/ui/overlays/governance-settings.js +140 -0
  225. package/dist/ui/overlays/model-profile-state.d.ts +6 -1
  226. package/dist/ui/overlays/model-profile-state.js +10 -0
  227. package/dist/ui/palettes.d.ts +17 -0
  228. package/dist/ui/palettes.js +9 -0
  229. package/dist/ui/scrollback/live-preview.js +2 -0
  230. package/dist/ui/scrollback/settled-boundary.js +3 -0
  231. package/dist/ui/shortcuts.js +2 -2
  232. package/dist/ui/theme.d.ts +7 -0
  233. package/dist/ui/theme.js +1 -0
  234. package/dist/ui/transcript-text.js +9 -0
  235. package/dist/ui/use-wheel-routing.js +3 -1
  236. package/package.json +2 -2
package/dist/ui/App.js CHANGED
@@ -16,7 +16,7 @@ import { computeCost } from '../agent/usage.js';
16
16
  import { parseMaxTokensInput, DEFAULT_TRANSCRIPT_RETAIN, DEFAULT_TRANSCRIPT_WINDOW, } from '../config/schema.js';
17
17
  import { updatePersistedConfig } from '../config/store.js';
18
18
  import { appendPrompt, loadPromptHistory } from '../config/prompt-history.js';
19
- import { bumpSubmitCount, setMouseNoticeVersion, setPromptKeyNoticeVersion, setScrollModeNoticeVersion, setVtInputNoticeVersion, } from '../config/ui-state.js';
19
+ import { bumpSubmitCount, getGovernanceIntroSeen, setGovernanceIntroSeen, setMouseNoticeVersion, setPromptKeyNoticeVersion, setScrollModeNoticeVersion, setVtInputNoticeVersion, } from '../config/ui-state.js';
20
20
  import { getLogger } from '../logging/logger.js';
21
21
  import { isTerminalStatus } from '../proc/types.js';
22
22
  import { getSecrets } from '../logging/secret-registry.js';
@@ -33,6 +33,13 @@ import { Composer } from './Composer.js';
33
33
  import { StatusBar } from './StatusBar.js';
34
34
  import { secondsLeft } from '../agent/retry-view.js';
35
35
  import { TeamPanel } from './TeamPanel.js';
36
+ import { GovernancePanel, useEliminatedSince } from './GovernancePanel.js';
37
+ import { GovernanceBoardOverlay } from './overlays/GovernanceBoardOverlay.js';
38
+ import { governancePanelMode, liveRosterSurface } from './governance-status.js';
39
+ import { buildGovernancePanelLayout } from './layout/governance-panel.js';
40
+ import { boardDataFromRun, boardDataFromSnapshot, } from './layout/governance-board.js';
41
+ import { isGovernancePosture, nextPosture, splitPosture, } from '../governance/posture.js';
42
+ import { GOVERN_INTRO_NOTICE, PLAN_APPROVED_TOAST, PLAN_GOVERN_INTRO_NOTICE, posturePendingToast, postureAppliedToast, stageRunningToast, } from '../governance/copy.js';
36
43
  import { TodoPanel } from './TodoPanel.js';
37
44
  import { BottomStatusRow } from './BottomStatusRow.js';
38
45
  // Only the updater's pure visibility predicate enters the status detail plan.
@@ -88,7 +95,7 @@ import { PlanReviewOverlay } from './overlays/PlanReviewOverlay.js';
88
95
  import { CommandRegistry, runSlashInput } from '../commands/registry.js';
89
96
  import { registerBuiltinCommands } from '../commands/builtins.js';
90
97
  import { makeSkillsCommand, registerSkillCommands } from '../commands/skills.js';
91
- import { MODE_LABEL, MODE_TOGGLE_KEYS, nextMode } from '../agent/agent-mode.js';
98
+ import { MODE_TOGGLE_KEYS } from '../agent/agent-mode.js';
92
99
  import { createClipboardCoordinator } from './clipboard-task.js';
93
100
  import { QueuePanel } from './QueuePanel.js';
94
101
  import { QueueOverlay } from './overlays/QueueOverlay.js';
@@ -140,6 +147,16 @@ export function resolveSettingsMaxTokens(raw, previous) {
140
147
  toast: ['warn', 'Max tokens must be a number or "auto".'],
141
148
  };
142
149
  }
150
+ /**
151
+ * The `/gov board` overlay's data: the full run when the controller can supply it (all four
152
+ * tabs), else the snapshot alone (the Work packages and Claims tabs then say they are empty).
153
+ */
154
+ function governanceBoardData(controller, snapshot) {
155
+ if (snapshot === null)
156
+ return null;
157
+ const input = controller.getGovernanceBoardInput?.();
158
+ return input ? boardDataFromRun({ snapshot, ...input }) : boardDataFromSnapshot(snapshot);
159
+ }
143
160
  /** Services in a non-terminal state, from the live snapshot map. */
144
161
  function liveServiceCount(services) {
145
162
  let n = 0;
@@ -354,6 +371,9 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
354
371
  const reducedMotion = cfg.reducedMotion ?? false;
355
372
  const stateRef = useRef(state);
356
373
  stateRef.current = state;
374
+ // The first-seen-eliminated map for the legion panel (a replaced member shows for one
375
+ // phase). A hook, so it is called unconditionally here and read further down.
376
+ const eliminatedSince = useEliminatedSince(state.governance);
357
377
  /**
358
378
  * The task label's two guards (tui-shortcut-drawer-and-task-summary D-10).
359
379
  *
@@ -826,14 +846,22 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
826
846
  submitRef.current(gate.message, { userInitiated: false, todoPolicy: 'continue' });
827
847
  }, FORCED_NUDGE_GRACE_MS);
828
848
  }
829
- // A deferred `plan -> build` becomes real here and nowhere else (§3.2).
830
- const applied = controller.applyPendingMode();
849
+ // A deferred `plan -> build` (or any posture change that waited for a governed
850
+ // stage, governance-mode D-23) becomes real here and nowhere else (§3.2).
851
+ const applied = controller.applyPendingPosture();
831
852
  if (applied) {
832
- dispatch({ type: 'setAgentMode', mode: applied.effective, pending: null });
853
+ const split = splitPosture(applied.effective);
854
+ dispatch({
855
+ type: 'setAgentMode',
856
+ mode: split.mode,
857
+ pending: null,
858
+ posture: applied.effective,
859
+ pendingPosture: applied.pending,
860
+ });
833
861
  dispatch({
834
862
  type: 'pushToast',
835
863
  level: 'info',
836
- text: `${MODE_LABEL[applied.effective]} mode.`,
864
+ text: postureAppliedToast(applied.effective),
837
865
  });
838
866
  }
839
867
  });
@@ -965,6 +993,25 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
965
993
  controller.dispose();
966
994
  };
967
995
  }, [controller]);
996
+ // --- Governance event stream (governance-mode 8.6 / 14.4). --------------
997
+ //
998
+ // ANOTHER CLI-LOCAL SUBSCRIPTION, for the reason the team one records: `GovernanceEvent`
999
+ // is deliberately not a member of core's `AgentEvent` union. The wiring throttles the
1000
+ // chatty updates; this effect only folds snapshots into the view, appends the four kinds
1001
+ // of settlement card (charter, phase, escalation, final) and clears the live snapshot when
1002
+ // the run ends. A no-op unsubscribe when governance was not registered.
1003
+ useEffect(() => {
1004
+ return controller.subscribeGovernance((event) => {
1005
+ dispatch({ type: 'govSnapshot', snapshot: event.snapshot });
1006
+ const card = event.type === 'run_started' || event.type === 'stage_ended' ? event.card
1007
+ : event.type === 'escalation' || event.type === 'run_closed' ? event.card
1008
+ : null;
1009
+ if (card)
1010
+ dispatch({ type: 'governanceCard', card });
1011
+ if (event.type === 'run_closed')
1012
+ dispatch({ type: 'govEnd' });
1013
+ });
1014
+ }, [controller]);
968
1015
  // --- Todo event stream (todo-plan-execution §3.8 / §5.2). --------------
969
1016
  //
970
1017
  // A THIRD SUBSCRIPTION, for the reason the team one records: `TodoEvent` is
@@ -1386,7 +1433,14 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
1386
1433
  // reducer's `'build'` seed would render one wrong frame without this.
1387
1434
  useEffect(() => {
1388
1435
  const current = controller.getAgentMode();
1389
- dispatch({ type: 'setAgentMode', mode: current, pending: null });
1436
+ const posture = controller.getPostureState();
1437
+ dispatch({
1438
+ type: 'setAgentMode',
1439
+ mode: current,
1440
+ pending: null,
1441
+ posture: posture.effective,
1442
+ pendingPosture: posture.pending,
1443
+ });
1390
1444
  }, [controller]);
1391
1445
  // --- Let the controller drive command-list rebuilds. -------------------
1392
1446
  useEffect(() => {
@@ -1776,19 +1830,50 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
1776
1830
  * what was requested: dispatching the request is the one way to make the badge
1777
1831
  * claim a permission the gate will not honour.
1778
1832
  */
1779
- const applyMode = (next, opts = {}) => {
1780
- const applied = controller.setAgentMode(next, opts.force ? { force: true } : {});
1781
- dispatch({ type: 'setAgentMode', mode: applied.effective, pending: applied.pending });
1833
+ const applyPosture = (next, opts = {}) => {
1834
+ const applied = controller.setPosture(next, opts.force ? { force: true } : {});
1835
+ const split = splitPosture(applied.effective);
1836
+ // THE ADOPTED VALUES, all four in one dispatch (governance-mode 4.3).
1837
+ dispatch({
1838
+ type: 'setAgentMode',
1839
+ mode: split.mode,
1840
+ pending: applied.pending ? splitPosture(applied.pending).mode : null,
1841
+ posture: applied.effective,
1842
+ pendingPosture: applied.pending,
1843
+ });
1782
1844
  if (opts.silent)
1783
1845
  return applied;
1784
1846
  if (applied.pending) {
1785
- toast('info', `${MODE_LABEL[applied.pending]} mode applies after this run.`);
1847
+ toast('info', controller.isGovernanceStageRunning()
1848
+ ? stageRunningToast(applied.pending)
1849
+ : posturePendingToast(applied.pending));
1786
1850
  }
1787
1851
  else {
1788
- toast('info', `${MODE_LABEL[applied.effective]} mode.`);
1852
+ toast('info', postureAppliedToast(applied.effective));
1853
+ // One-time, across launches: what GOVERN means (governance-mode 4.3).
1854
+ if (isGovernancePosture(applied.effective) && !getGovernanceIntroSeen()) {
1855
+ setGovernanceIntroSeen(true);
1856
+ dispatch({
1857
+ type: 'notice',
1858
+ level: 'info',
1859
+ text: applied.effective === 'plan_govern' ? PLAN_GOVERN_INTRO_NOTICE : GOVERN_INTRO_NOTICE,
1860
+ });
1861
+ }
1789
1862
  }
1790
1863
  return applied;
1791
1864
  };
1865
+ /**
1866
+ * The gate-mode view of the same write path, for `/plan`: a plain `plan` / `build`
1867
+ * request names a classic posture, so it CLEARS governance (an explicit command beats
1868
+ * an implicit carry-over, governance-mode 4.2).
1869
+ */
1870
+ const applyMode = (next, opts = {}) => {
1871
+ const applied = applyPosture(next, opts);
1872
+ return {
1873
+ effective: splitPosture(applied.effective).mode,
1874
+ pending: applied.pending ? splitPosture(applied.pending).mode : null,
1875
+ };
1876
+ };
1792
1877
  /**
1793
1878
  * Cancel any in-flight label call and make its result unadoptable.
1794
1879
  *
@@ -1971,6 +2056,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
1971
2056
  },
1972
2057
  refreshSkills: () => setSkillsNonce((n) => n + 1),
1973
2058
  applyAgentMode: applyMode,
2059
+ applyPosture: (next, opts) => applyPosture(next, opts ?? {}),
1974
2060
  // Read at command time, so `/todo status` reports the live count rather than
1975
2061
  // whatever it was when this context was built.
1976
2062
  followBudget: budgetRef.current,
@@ -2275,8 +2361,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2275
2361
  // here would read `'plan'` and the badge would lag by a frame. Writing it
2276
2362
  // first through the same single path makes the tool's call an idempotent
2277
2363
  // no-op and keeps the mirror exact (§3.1 / R-P7).
2278
- applyMode('build', { force: true, silent: true });
2279
- toast('success', 'Plan approved. Implementing.');
2364
+ // PLAN+GOVERN hands over to the team instead of implementing: the same single write
2365
+ // path, with governance kept on (governance-mode 9.2).
2366
+ const governed = controller.getPosture() === 'plan_govern';
2367
+ applyPosture(governed ? 'govern' : 'build', { force: true, silent: true });
2368
+ toast('success', governed ? PLAN_APPROVED_TOAST : 'Plan approved. Implementing.');
2280
2369
  }
2281
2370
  else {
2282
2371
  toast('info', 'Revision requested.');
@@ -2593,6 +2682,9 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2593
2682
  // The Agents board pages through `overlayScroll` too, but keeps Up/Down for its
2594
2683
  // CHILD CURSOR - the same trade `settings` makes with its field cursor.
2595
2684
  || overlay === 'agents'
2685
+ // The governance board (governance-mode 10.5): without this line a PgDn in the
2686
+ // board falls through to the transcript behind it.
2687
+ || overlay === 'governance'
2596
2688
  // The prompt surfaces page themselves through `overlayScroll` too
2597
2689
  // (quick-prompt-library appendix B-1). Without them a `PgDn` in the list does
2598
2690
  // nothing and the wheel falls through to the transcript behind it.
@@ -2630,7 +2722,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2630
2722
  if ((key.tab && key.shift) || (key.ctrl && (input === 'n' || input === 'N'))) {
2631
2723
  if (stateRef.current.overlay)
2632
2724
  return;
2633
- applyMode(nextMode(controller.getAgentMode()));
2725
+ // The cycle's basis is `pending ?? effective`: with a change already waiting, a second
2726
+ // press must advance from it, or it would hold on the same target (governance-mode 4.1).
2727
+ const postureNow = controller.getPostureState();
2728
+ applyPosture(nextPosture(postureNow.pending ?? postureNow.effective, controller.getPostureCycle()));
2634
2729
  return;
2635
2730
  }
2636
2731
  // Moved off Ctrl+L by D-1, because Ctrl+L is the branch key now.
@@ -2945,10 +3040,32 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2945
3040
  ? Math.max(0, viewportBudget - 1)
2946
3041
  : Math.max(0, viewportBudget - composerBaseRows - queueLayout.rows - 1),
2947
3042
  });
3043
+ // THE LEGION PANEL REPLACES THE TEAM ROSTER while a governed stage runs (D-17, AC-25):
3044
+ // one live roster, never two. Between stages it is a two-line run strip, and it yields to
3045
+ // a non-governed dispatch if one is somehow live. Its height feeds the same two budgets
3046
+ // the roster's does, so the band and the rail never learn a second number.
3047
+ // PLAN+GOVERN's interview chip on the question overlay (governance-mode 9.3).
3048
+ const interviewState = state.posture === 'plan_govern' ? controller.getGovernanceInterview?.() : null;
3049
+ const interviewProps = interviewState
3050
+ ? { interview: { round: interviewState.answered, required: interviewState.required } }
3051
+ : {};
3052
+ const governanceMode = governancePanelMode(state.governance);
3053
+ const governanceLayout = state.governance !== null
3054
+ && liveRosterSurface(state.governance, state.team !== null) === 'governance'
3055
+ ? buildGovernancePanelLayout({
3056
+ cols: native ? Math.max(1, cols - 1) : baseRail.contentCols, rows,
3057
+ snapshot: state.governance, glyphs, reducedMotion, now: Date.now(),
3058
+ mode: governanceMode === 'panel' ? 'panel' : 'run-strip', eliminatedSince,
3059
+ availableRows: native
3060
+ ? Math.max(0, viewportBudget - 1)
3061
+ : Math.max(0, viewportBudget - composerBaseRows - queueLayout.rows - 1),
3062
+ })
3063
+ : null;
3064
+ const teamPanelRows = governanceLayout ? governanceLayout.height : teamLayout.rowCount;
2948
3065
  const railLayout = buildTodoRailLayout({
2949
3066
  cols: cols - 1, panelEnabled: cfg.todo.panel, overlayOpen: false,
2950
3067
  itemCount: state.todos?.items.length ?? 0, viewportBudget,
2951
- teamRows: teamLayout.rowCount, queueRows: queueLayout.rows, popupRows: 0, composerBaseRows,
3068
+ teamRows: teamPanelRows, queueRows: queueLayout.rows, popupRows: 0, composerBaseRows,
2952
3069
  });
2953
3070
  const { visible: railVisible, width: railWidth, rows: railRows, contentCols } = railLayout;
2954
3071
  // The FULL-SCREEN rail only: native mode draws its rail inside the live region's
@@ -2983,7 +3100,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2983
3100
  // second time (`buildTeamPanelLayout` stays the single authority).
2984
3101
  itemCount: state.todos?.items.length ?? 0,
2985
3102
  panelEnabled: cfg.todo.panel,
2986
- teamRowsNatural: teamLayout.rowCount,
3103
+ teamRowsNatural: teamPanelRows,
2987
3104
  // The top give-way measures a window entry's height, and an expanded tool card is a
2988
3105
  // different height from a collapsed one - a forgotten value would print rows the region
2989
3106
  // is not drawing.
@@ -3079,14 +3196,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
3079
3196
  // mounted with `{...props}` and an undeclared prop arrives as
3080
3197
  // `undefined`. A construction-time snapshot, so reading it per frame is
3081
3198
  // honest (I-7).
3082
- teamRegistered: controller.isTeamRegistered(), probe: probe, onProfileSave: handleProfileSave, onReload: () => controller.createModelSettingsDraft(), onClose: () => dispatch({ type: 'setOverlay', overlay: null }),
3199
+ teamRegistered: controller.isTeamRegistered(), governanceRegistered: controller.isGovernanceRegistered(), probe: probe, onProfileSave: handleProfileSave, onReload: () => controller.createModelSettingsDraft(), onClose: () => dispatch({ type: 'setOverlay', overlay: null }),
3083
3200
  // Read from `cfg` (render scope, re-read every frame), so `/retry off`
3084
3201
  // while the screen is open shows the new value on the next paint.
3085
3202
  retrySummary: cfg.retry.enabled && cfg.retry.maxRetries > 0 ? String(cfg.retry.maxRetries) : 'off' })) : overlay === 'agents' ? (
3086
3203
  // THE BRANCH AND THE `Overlay` MEMBER SHIP TOGETHER (R-11): a missing branch is not
3087
3204
  // a type error, it is a SILENT BLANK FRAME, because `active={overlay === null}` has
3088
3205
  // already disabled the transcript underneath.
3089
- _jsx(AgentsBoardOverlay, { team: state.team, entries: state.entries, cols: viewCols, maxRows: overlayMaxRows, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, isActive: !tooSmall, native: native, theme: theme, caps: caps })) : overlay === 'confirm' && confirmState ? (_jsx(ConfirmDialog, { isActive: !tooSmall, state: confirmState, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onClose: closeConfirm })) : overlay === 'question' && humanRequest?.kind === 'questions' ? (_jsx(QuestionOverlay, { isActive: !tooSmall, questions: humanRequest.questions, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onSubmit: (answers) => resolveHuman({ kind: 'answers', answers, cancelled: false }) })) : overlay === 'plan' && humanRequest?.kind === 'plan' ? (_jsx(PlanReviewOverlay, { isActive: !tooSmall, plan: humanRequest.plan, maxRows: overlayMaxRows, cols: viewCols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, onVerdict: handlePlanVerdict })) : null;
3206
+ _jsx(AgentsBoardOverlay, { team: state.team, entries: state.entries, cols: viewCols, maxRows: overlayMaxRows, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, isActive: !tooSmall, native: native, theme: theme, caps: caps })) : overlay === 'governance' ? (
3207
+ // THE BRANCH AND THE `Overlay` MEMBER SHIP TOGETHER (R-11), like `agents` above.
3208
+ _jsx(GovernanceBoardOverlay, { data: governanceBoardData(controller, state.governance), cols: viewCols, maxRows: overlayMaxRows, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, isActive: !tooSmall, theme: theme, caps: caps, onCopyPath: (path) => { void copyCoordinator.requestCopy({ text: path, lines: 1 }); }, onNotice: toast })) : overlay === 'confirm' && confirmState ? (_jsx(ConfirmDialog, { isActive: !tooSmall, state: confirmState, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onClose: closeConfirm })) : overlay === 'question' && humanRequest?.kind === 'questions' ? (_jsx(QuestionOverlay, { isActive: !tooSmall, questions: humanRequest.questions, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onSubmit: (answers) => resolveHuman({ kind: 'answers', answers, cancelled: false }), ...interviewProps })) : overlay === 'plan' && humanRequest?.kind === 'plan' ? (_jsx(PlanReviewOverlay, { isActive: !tooSmall, plan: humanRequest.plan, maxRows: overlayMaxRows, cols: viewCols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, onVerdict: handlePlanVerdict })) : null;
3090
3209
  const density = cfg.density;
3091
3210
  const transcriptWindow = cfg.transcriptWindow ?? DEFAULT_TRANSCRIPT_WINDOW;
3092
3211
  // The fixed status bar remains visible underneath every overlay and toast.
@@ -3207,7 +3326,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
3207
3326
  stopping: interruptGesture.current.phase === 'stopping',
3208
3327
  interruptPhase: interruptGesture.current.phase,
3209
3328
  compacting: compactionLive || !!state.compaction?.inFlight,
3210
- mode: state.agentMode, pendingMode: state.pendingAgentMode,
3329
+ mode: state.posture, pendingMode: state.pendingPosture,
3211
3330
  servicesActive: { live: liveServices }, ecoRung: governor.rung,
3212
3331
  // THE STATUS-DETAIL ROW COUNTS LIKE THE ROSTER AND THE CHIP (A-1): this was the
3213
3332
  // last inline phase filter, left behind when the StatusBar chip converged, and it
@@ -3227,11 +3346,12 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
3227
3346
  selectionPending: selectionController?.hasPendingSelection() ?? false,
3228
3347
  copyInFlight: copyState.busy, copyCleanupPending: copyState.cleanupPending,
3229
3348
  hasDraft: hasDraftRef.current, services: liveServices,
3230
- hintsEnabled: cfg.hints, modeToggleKey,
3349
+ hintsEnabled: cfg.hints, modeToggleKey, governed: isGovernancePosture(state.posture),
3350
+ ...(state.governance?.pauseRequested === true ? { govPausePending: true } : {}),
3231
3351
  interruptPhase: interruptGesture.current.phase,
3232
3352
  updateAvailable: !!updateSnapshot && shouldRenderUpdateLine(updateSnapshot) } });
3233
- const composer = (_jsx(Composer, { deleteDisambiguated: terminal?.deleteDisambiguated ?? false, onCompletionContextChange: setCompletion, cols: frameBudget.composerCols, terminalRows: rows, statusExpanded: statusExpanded, onInteraction: returnToComposer, reducedMotion: cfg.reducedMotion ?? false, onEscape: handleComposerEscape, onEscapeDismiss: cancelConfirmation, isActive: overlay === null && !tooSmall, running: state.status === 'running', history: promptHistory, commands: commandOptions, cwd: controller.getCwd(), showHint: false, submitCount: cfg.submitCount, hintsEnabled: cfg.hints, agentMode: state.agentMode, services: liveServices, modeToggleKey: modeToggleKey, popupMaxRows: popupMaxRows, popupMaxHeight: frameBudget.popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onDraftRows: onDraftRows, onDraftPresence: onDraftPresence, draftClearNonce: draftClearNonce, ...(draftFill ? { draftFill } : {}), insertMode: cfg.prompts?.insertMode ?? 'insert', onNotice: notify, theme: theme, caps: caps, onSubmit: handleSubmit, onHelp: () => dispatch({ type: 'setOverlay', overlay: 'help' }) }));
3234
- const team = state.team ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: contentCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
3353
+ const composer = (_jsx(Composer, { deleteDisambiguated: terminal?.deleteDisambiguated ?? false, onCompletionContextChange: setCompletion, cols: frameBudget.composerCols, terminalRows: rows, statusExpanded: statusExpanded, onInteraction: returnToComposer, reducedMotion: cfg.reducedMotion ?? false, onEscape: handleComposerEscape, onEscapeDismiss: cancelConfirmation, isActive: overlay === null && !tooSmall, running: state.status === 'running', history: promptHistory, commands: commandOptions, cwd: controller.getCwd(), showHint: false, submitCount: cfg.submitCount, hintsEnabled: cfg.hints, agentMode: state.agentMode, posture: state.posture, services: liveServices, modeToggleKey: modeToggleKey, popupMaxRows: popupMaxRows, popupMaxHeight: frameBudget.popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onDraftRows: onDraftRows, onDraftPresence: onDraftPresence, draftClearNonce: draftClearNonce, ...(draftFill ? { draftFill } : {}), insertMode: cfg.prompts?.insertMode ?? 'insert', onNotice: notify, theme: theme, caps: caps, onSubmit: handleSubmit, onHelp: () => dispatch({ type: 'setOverlay', overlay: 'help' }) }));
3354
+ const team = governanceLayout ? (_jsx(GovernancePanel, { layout: governanceLayout, theme: theme, caps: caps })) : state.team ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: contentCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
3235
3355
  // --- Native panel band (release-gate-and-native-panels §6.3/§6.4) -------------
3236
3356
  //
3237
3357
  // SEPARATE NODES, NOT THE TWO ABOVE (RV-P1-7). Those carry `cols={contentCols}` for
@@ -3252,7 +3372,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
3252
3372
  // out against `previewCols` would stop its clock one rail short of the screen's edge.
3253
3373
  // `teamRowGeometry` is continuous in `cols`, so the number that decides whether the
3254
3374
  // panel mounts IS the number it lays out against.
3255
- const nativeTeam = state.team && nativeBand ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: nativeBandCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
3375
+ const nativeTeam = governanceLayout && nativeBand ? (_jsx(GovernancePanel, { layout: governanceLayout, theme: theme, caps: caps })) : state.team && nativeBand ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: nativeBandCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
3256
3376
  const rail = fullscreenRailVisible && state.todos ? (_jsx(TodoPanel, { snapshot: state.todos, width: railWidth, rows: railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
3257
3377
  const viewport = (_jsxs(ScrollViewport, { active: overlay === null && !tooSmall, overlay: overlayNode, footer: _jsxs(_Fragment, { children: [team, _jsx(QueuePanel, { layout: queueLayout, theme: theme })] }), rail: rail, scrollbar: terminal?.scrollbar, resetKey: scrollResetKey, intent: scrollIntent, pinToBottomNonce: pinToBottomNonce, onScrolledLinesChange: setScrolledLines, onViewportShiftChange: onViewportShiftChange, onGeometryChange: onGeometryChange, geometryKey: `${rows}:${cols}:${viewportBudget}:${frameBudget.composerSlotRows}:${frameBudget.statusRows}:${contentCols}:${overlay}:${tooSmall}`, tailRowsRef: tailSink, hold: selectionHold, resumeMs: cfg.scrollResumeMs, showScrollIndicator: true, cols: contentCols, theme: theme, caps: caps, children: [opener, _jsx(TranscriptList, { entries: transcriptEntries, expandedToolIds: state.expandedToolIds, thinkingVisible: state.thinkingVisible, reducedMotion: viewReducedMotion, density: density, nowSec: nowSec, theme: theme, caps: caps, windowSize: transcriptWindow, cols: contentCols, heights: heights, mountedSink: mountedCount, tailSink: tailSink })] }));
3258
3378
  const detailsNode = _jsx(BottomStatusRow, { plan: details, columns: viewCols, theme: theme });
@@ -3264,7 +3384,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
3264
3384
  // partially stale. The window and its `known` flag now travel INSIDE
3265
3385
  // it, resolved by the meter - which is what makes the user's
3266
3386
  // `contextWindow` override reach the bar.
3267
- context: state.context, status: state.status, elapsedMs: elapsedMs, thinkingLevel: cfg.thinkingLevel, tokPerSec: tokPerSec, theme: theme, caps: caps, scrolledLines: scrolledLines, redrawNonce: redrawNonce, agentMode: state.agentMode, pendingAgentMode: state.pendingAgentMode, ...(forcedTeam ? { forcedTeam } : {}), ecoRung: governor.rung, ...(state.team
3387
+ context: state.context, status: state.status, elapsedMs: elapsedMs, thinkingLevel: cfg.thinkingLevel, tokPerSec: tokPerSec, theme: theme, caps: caps, scrolledLines: scrolledLines, redrawNonce: redrawNonce, agentMode: state.posture, pendingAgentMode: state.pendingPosture, governance: state.governance, ...(forcedTeam ? { forcedTeam } : {}), ecoRung: governor.rung, ...(state.team
3268
3388
  ? {
3269
3389
  // `rosterCounts`, NOT a fourth inline phase filter (A-1): the chip and
3270
3390
  // the roster header above it must never disagree.
@@ -4,6 +4,6 @@ import React from 'react';
4
4
  import { Box, Text } from 'ink';
5
5
  export function BottomStatusRow({ plan, columns, theme }) {
6
6
  const tones = { normal: theme.primary, muted: theme.muted, warning: theme.noticeWarn, error: theme.noticeError,
7
- accent: theme.accent };
7
+ accent: theme.accent, govern: theme.governAccent };
8
8
  return _jsxs(Box, { height: 1, width: columns, flexShrink: 0, overflow: "hidden", children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { children: " " }) }), plan.fields.map((field, index) => _jsxs(React.Fragment, { children: [index > 0 && _jsx(Box, { width: plan.separatorCells, flexShrink: 0, children: _jsx(Text, { color: theme.muted, children: plan.separator }) }), _jsx(Box, { width: field.cells, flexShrink: 0, children: _jsx(Text, { wrap: "truncate", color: tones[field.tone], children: field.text }) })] }, field.id))] });
9
9
  }
@@ -44,6 +44,12 @@ export interface ComposerProps {
44
44
  hintsEnabled: boolean;
45
45
  /** Mirror of the controller's effective mode; drives the border. */
46
46
  agentMode: AgentMode;
47
+ /**
48
+ * The adopted posture (governance-mode 10.2). A governed one paints the idle/draft
49
+ * border in `governAccent`, the same slot PLAN uses `accent` for. Absent = the
50
+ * pre-governance border logic, unchanged.
51
+ */
52
+ posture?: string;
47
53
  /** Compatibility prop; the global action row names the effective mode key. */
48
54
  modeToggleKey?: string;
49
55
  popupMaxRows?: number;
@@ -105,5 +111,5 @@ declare function hintText(opts: {
105
111
  }): string;
106
112
  /** Compatibility export for existing hint contract tests. */
107
113
  export declare const hintTextForTest: typeof hintText;
108
- export declare function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onDraftPresence, draftClearNonce, draftFill, insertMode, onNotice, theme, caps, onSubmit, onHelp, }: ComposerProps): React.ReactElement;
114
+ export declare function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, posture, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onDraftPresence, draftClearNonce, draftFill, insertMode, onNotice, theme, caps, onSubmit, onHelp, }: ComposerProps): React.ReactElement;
109
115
  export {};
@@ -5,6 +5,7 @@ import { Box } from 'ink';
5
5
  import { HINT_FADE_AFTER } from '../config/schema.js';
6
6
  import { PromptInput } from './PromptInput.js';
7
7
  import { MODE_LABEL, nextMode } from '../agent/agent-mode.js';
8
+ import { isGovernedModeValue } from './governance-status.js';
8
9
  /** Legacy helper retained for consumers and contract tests; not rendered by Composer. */
9
10
  export function runningHintClauses(opts) {
10
11
  const { glyphs, services } = opts;
@@ -36,7 +37,7 @@ function hintText(opts) {
36
37
  }
37
38
  /** Compatibility export for existing hint contract tests. */
38
39
  export const hintTextForTest = hintText;
39
- export function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onDraftPresence, draftClearNonce, draftFill, insertMode, onNotice, theme, caps, onSubmit, onHelp, }) {
40
+ export function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, posture, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onDraftPresence, draftClearNonce, draftFill, insertMode, onNotice, theme, caps, onSubmit, onHelp, }) {
40
41
  const [hasDraft, setHasDraft] = useState(false);
41
42
  const onDraftChange = useCallback((next) => {
42
43
  setHasDraft(next.hasDraft);
@@ -52,10 +53,12 @@ export function Composer({ deleteDisambiguated, onCompletionContextChange, cols,
52
53
  ? theme.toolPending
53
54
  : running
54
55
  ? theme.toolRunning
55
- : agentMode === 'plan'
56
- ? theme.accent
57
- : hasDraft
58
- ? theme.focusBorder ?? theme.primary
59
- : theme.idleBorder ?? theme.border;
56
+ : isGovernedModeValue(posture)
57
+ ? theme.governAccent
58
+ : agentMode === 'plan'
59
+ ? theme.accent
60
+ : hasDraft
61
+ ? theme.focusBorder ?? theme.primary
62
+ : theme.idleBorder ?? theme.border;
60
63
  return (_jsx(Box, { ref: measureRef, width: cols, flexDirection: "column", flexShrink: 0, children: _jsx(PromptInput, { deleteDisambiguated: deleteDisambiguated, onCompletionContextChange: onCompletionContextChange, cols: cols, terminalRows: terminalRows, statusExpanded: statusExpanded, cursorVisible: cursorVisible, onInteraction: onInteraction, isActive: isActive, reducedMotion: reducedMotion, onEscape: onEscape, onEscapeDismiss: onEscapeDismiss, running: running, history: history, commands: commands, cwd: cwd, theme: theme, caps: caps, popupMaxRows: popupMaxRows, popupMaxHeight: popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onSubmit: onSubmit, onHelp: onHelp, agentMode: agentMode, borderColor: borderColor, onDraftChange: onDraftChange, draftClearNonce: draftClearNonce, draftFill: draftFill, insertMode: insertMode, onNotice: onNotice }) }));
61
64
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * GovernancePanel - the legion panel (governance-mode section 10.3).
3
+ *
4
+ * ASCII ONLY: every glyph comes from `pickGlyphs`, through the layout.
5
+ *
6
+ * THIS COMPONENT ONLY COLOURS AND PLACES. Every decision - which tier, which columns, how
7
+ * wide, what each cell says, which row is hidden - was made by
8
+ * `layout/governance-panel.ts`, and the `layout` prop is REQUIRED: `App` builds it once to
9
+ * learn the panel's height (the native band and the TODO rail both budget against it) and
10
+ * hands the very same object here, so the number that decides whether the panel mounts is
11
+ * the number it is drawn from. A panel that re-planned itself could draw a taller tier than
12
+ * the one the band reserved, and the band clips the overflow in silence.
13
+ *
14
+ * IT REPLACES `TeamPanel` WHILE A GOVERNED STAGE RUNS (D-17), it does not stack with it:
15
+ * the repo has been bitten by a "double roster" once (tui-subagent-double-roster), and a
16
+ * governed dispatch is the one case where two live rosters would be the same children
17
+ * listed twice.
18
+ *
19
+ * NO `ink-spinner` HERE, on purpose. While a governed stage runs the lead's turn is
20
+ * running, the activity row owns the one spinner (single-spinner-while-running), and every
21
+ * panel is handed the widened reduced-motion signal anyway - an animation here would never
22
+ * play. The working marker is `statusGlyph(...)`, and `reducedMotion` is the USER's
23
+ * preference: it only decides whether a score change flashes for four seconds or stays put.
24
+ *
25
+ * COLUMNS ARE FIXED-WIDTH BOXES with exactly one elastic cell (`governance-roster.ts`), the
26
+ * lesson `TeamPanel` records at length: a flex-grown column under a fixed-height parent
27
+ * stretches to its widest child and the cells to its right lose their offsets.
28
+ */
29
+ import React from 'react';
30
+ import type { GovernanceSnapshot } from '../governance/types.js';
31
+ import type { Theme } from './theme.js';
32
+ import type { TermCapabilities } from './capabilities.js';
33
+ import { type EliminatedSince } from './governance-status.js';
34
+ import type { GovernancePanelLayout } from './layout/governance-panel.js';
35
+ /**
36
+ * The first-seen-eliminated map, kept across snapshots so a replaced member shows its
37
+ * `replaced by ...` note for exactly one phase. Reset when the run changes or ends.
38
+ *
39
+ * A ref written during render is acceptable here because the write is idempotent
40
+ * (`trackEliminated` returns the same object when nothing is new) and no other component
41
+ * reads the ref - `App` reads only the returned value.
42
+ */
43
+ export declare function useEliminatedSince(snapshot: GovernanceSnapshot | null): EliminatedSince;
44
+ export interface GovernancePanelProps {
45
+ /** Built by `buildGovernancePanelLayout`; the SAME object `App` read the height from. */
46
+ layout: GovernancePanelLayout;
47
+ theme: Theme;
48
+ caps: TermCapabilities;
49
+ }
50
+ export declare function GovernancePanel({ layout, theme, caps }: GovernancePanelProps): React.ReactElement | null;
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * GovernancePanel - the legion panel (governance-mode section 10.3).
4
+ *
5
+ * ASCII ONLY: every glyph comes from `pickGlyphs`, through the layout.
6
+ *
7
+ * THIS COMPONENT ONLY COLOURS AND PLACES. Every decision - which tier, which columns, how
8
+ * wide, what each cell says, which row is hidden - was made by
9
+ * `layout/governance-panel.ts`, and the `layout` prop is REQUIRED: `App` builds it once to
10
+ * learn the panel's height (the native band and the TODO rail both budget against it) and
11
+ * hands the very same object here, so the number that decides whether the panel mounts is
12
+ * the number it is drawn from. A panel that re-planned itself could draw a taller tier than
13
+ * the one the band reserved, and the band clips the overflow in silence.
14
+ *
15
+ * IT REPLACES `TeamPanel` WHILE A GOVERNED STAGE RUNS (D-17), it does not stack with it:
16
+ * the repo has been bitten by a "double roster" once (tui-subagent-double-roster), and a
17
+ * governed dispatch is the one case where two live rosters would be the same children
18
+ * listed twice.
19
+ *
20
+ * NO `ink-spinner` HERE, on purpose. While a governed stage runs the lead's turn is
21
+ * running, the activity row owns the one spinner (single-spinner-while-running), and every
22
+ * panel is handed the widened reduced-motion signal anyway - an animation here would never
23
+ * play. The working marker is `statusGlyph(...)`, and `reducedMotion` is the USER's
24
+ * preference: it only decides whether a score change flashes for four seconds or stays put.
25
+ *
26
+ * COLUMNS ARE FIXED-WIDTH BOXES with exactly one elastic cell (`governance-roster.ts`), the
27
+ * lesson `TeamPanel` records at length: a flex-grown column under a fixed-height parent
28
+ * stretches to its widest child and the cells to its right lose their offsets.
29
+ */
30
+ import { useRef } from 'react';
31
+ import { Box, Text } from 'ink';
32
+ import { pickGlyphs } from './glyphs.js';
33
+ import { trackEliminated } from './governance-status.js';
34
+ import { GovernanceLine } from './GovernanceRun.js';
35
+ import { PANEL_MARGIN_CELLS, } from './layout/governance-roster.js';
36
+ /**
37
+ * The first-seen-eliminated map, kept across snapshots so a replaced member shows its
38
+ * `replaced by ...` note for exactly one phase. Reset when the run changes or ends.
39
+ *
40
+ * A ref written during render is acceptable here because the write is idempotent
41
+ * (`trackEliminated` returns the same object when nothing is new) and no other component
42
+ * reads the ref - `App` reads only the returned value.
43
+ */
44
+ export function useEliminatedSince(snapshot) {
45
+ const ref = useRef({ runId: null, since: {} });
46
+ if (snapshot === null) {
47
+ if (ref.current.runId !== null)
48
+ ref.current = { runId: null, since: {} };
49
+ return ref.current.since;
50
+ }
51
+ const base = ref.current.runId === snapshot.runId ? ref.current.since : {};
52
+ ref.current = { runId: snapshot.runId, since: trackEliminated(base, snapshot) };
53
+ return ref.current.since;
54
+ }
55
+ function Margin() {
56
+ return (_jsx(Box, { width: PANEL_MARGIN_CELLS, flexShrink: 0, children: _jsx(Text, { children: " " }) }));
57
+ }
58
+ function LineView({ line, theme }) {
59
+ return (_jsxs(Box, { flexDirection: "row", width: "100%", height: 1, flexShrink: 0, overflow: "hidden", children: [_jsx(Margin, {}), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(GovernanceLine, { segments: line.left, theme: theme }) }), line.rightCells > 0 && (_jsx(Box, { width: line.rightCells, flexShrink: 0, justifyContent: "flex-end", children: _jsx(GovernanceLine, { segments: line.right, theme: theme }) }))] }));
60
+ }
61
+ function CellView({ cell, theme }) {
62
+ const content = _jsx(GovernanceLine, { segments: cell.segments, theme: theme });
63
+ if (cell.elastic) {
64
+ return _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: content });
65
+ }
66
+ return (_jsx(Box, { width: cell.width, flexShrink: 0, overflow: "hidden", justifyContent: cell.align === 'right' ? 'flex-end' : 'flex-start', children: content }));
67
+ }
68
+ function RosterView({ line, theme }) {
69
+ return (_jsxs(Box, { flexDirection: "row", width: "100%", height: 1, flexShrink: 0, overflow: "hidden", children: [_jsx(Margin, {}), line.cells.map((cell) => (_jsx(CellView, { cell: cell, theme: theme }, cell.key)))] }));
70
+ }
71
+ export function GovernancePanel({ layout, theme, caps }) {
72
+ if (layout.height === 0 || layout.header === null)
73
+ return null;
74
+ const glyphs = pickGlyphs(caps);
75
+ const rule = layout.ruleCells > 0 ? (_jsxs(Box, { flexDirection: "row", width: "100%", height: 1, flexShrink: 0, overflow: "hidden", children: [_jsx(Margin, {}), _jsx(Text, { wrap: "truncate", color: theme.border, children: glyphs.hrule.repeat(layout.ruleCells) })] })) : null;
76
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", flexShrink: 0, children: [_jsx(LineView, { line: layout.header, theme: theme }), layout.pipeline !== null && _jsx(LineView, { line: layout.pipeline, theme: theme }), rule, layout.rosterRows.map((line) => (_jsx(RosterView, { line: line, theme: theme }, line.key))), layout.overflow !== null && (_jsxs(Box, { flexDirection: "row", width: "100%", height: 1, flexShrink: 0, overflow: "hidden", children: [_jsx(Margin, {}), _jsx(Text, { wrap: "truncate", color: theme.muted, children: layout.overflow })] })), rule, layout.ticker.map((line, index) => (_jsx(LineView, { line: line, theme: theme }, `ticker-${index}`)))] }));
77
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * GovernanceRun - paints one `Segment[]` run: the single place a governance layout's
3
+ * semantic tones become Ink colours.
4
+ *
5
+ * ASCII ONLY. The panel, the card and the board all draw the same `Segment` vocabulary,
6
+ * and three private copies of this loop is how their bold / tone handling drifts apart.
7
+ */
8
+ import React from 'react';
9
+ import type { Theme } from './theme.js';
10
+ import { type Segment } from './governance-status.js';
11
+ export interface GovernanceRunProps {
12
+ segments: readonly Segment[];
13
+ theme: Theme;
14
+ }
15
+ /** The segments as nested `Text`; the CALLER owns wrapping and truncation. */
16
+ export declare function GovernanceRun({ segments, theme }: GovernanceRunProps): React.ReactElement;
17
+ /** One display row: the run inside a `wrap="truncate"` `Text`, so it can never wrap. */
18
+ export declare function GovernanceLine({ segments, theme }: GovernanceRunProps): React.ReactElement;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ import { toneColor } from './governance-status.js';
4
+ /** The segments as nested `Text`; the CALLER owns wrapping and truncation. */
5
+ export function GovernanceRun({ segments, theme }) {
6
+ return (_jsx(_Fragment, { children: segments.map((part, index) => (_jsx(Text, { color: toneColor(part.tone, theme), ...(part.bold ? { bold: true } : {}), children: part.text }, index))) }));
7
+ }
8
+ /** One display row: the run inside a `wrap="truncate"` `Text`, so it can never wrap. */
9
+ export function GovernanceLine({ segments, theme }) {
10
+ return (_jsx(Text, { wrap: "truncate", children: _jsx(GovernanceRun, { segments: segments, theme: theme }) }));
11
+ }
@@ -5,6 +5,8 @@ import type { TermCapabilities } from './capabilities.js';
5
5
  import type { UsageTotal } from '../agent/reducer.js';
6
6
  import type { ContextUsageSnapshot } from '../compaction/types.js';
7
7
  import type { AgentMode } from '../agent/agent-mode.js';
8
+ import type { GovernanceSnapshot } from '../governance/types.js';
9
+ import type { Posture } from '../governance/posture.js';
8
10
  import type { PendingSteering } from '../agent/queued-messages.js';
9
11
  import { type RunPhase } from './layout/status-layout.js';
10
12
  import type { EscapeAction, StatusFeedback } from './status-feedback.js';
@@ -63,8 +65,19 @@ export interface StatusBarProps {
63
65
  modified: boolean;
64
66
  otherNames: readonly string[];
65
67
  } | null;
66
- agentMode?: AgentMode;
67
- pendingAgentMode?: AgentMode | null;
68
+ /**
69
+ * The mode word's source. A `Posture` is also accepted (governance-mode 10.2): its
70
+ * governed members render as `GOVERN` / `PLAN+GOVERN` in the governance hue, and
71
+ * `build` / `plan` render exactly as the `AgentMode` of the same name always did.
72
+ */
73
+ agentMode?: AgentMode | Posture;
74
+ pendingAgentMode?: AgentMode | Posture | null;
75
+ /**
76
+ * The live governed-run snapshot, or absent. Drives the governance chip
77
+ * (`GOV p2/3 REVIEW`); absent plans a hidden chip, which keeps this row byte-identical
78
+ * to the pre-governance build.
79
+ */
80
+ governance?: GovernanceSnapshot | null;
68
81
  /**
69
82
  * Forced team mode is armed, with the count it was armed with
70
83
  * (team-command-forced-dispatch §3.8 / D-9). ABSENT = not armed, and every