@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
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.
@@ -85,10 +92,11 @@ import { SettingsScreen, compactionSettingsFrom, fastSettingsFrom, } from './ove
85
92
  import { ConfirmDialog } from './overlays/ConfirmDialog.js';
86
93
  import { QuestionOverlay } from './overlays/QuestionOverlay.js';
87
94
  import { PlanReviewOverlay } from './overlays/PlanReviewOverlay.js';
88
- import { CommandRegistry, runSlashInput } from '../commands/registry.js';
95
+ import { CommandRegistry, parseSlashInput, runSlashInput, } from '../commands/registry.js';
96
+ import { buildInvocationCard, invocationCardsEnabled } from '../agent/invocation-card.js';
89
97
  import { registerBuiltinCommands } from '../commands/builtins.js';
90
98
  import { makeSkillsCommand, registerSkillCommands } from '../commands/skills.js';
91
- import { MODE_LABEL, MODE_TOGGLE_KEYS, nextMode } from '../agent/agent-mode.js';
99
+ import { MODE_TOGGLE_KEYS } from '../agent/agent-mode.js';
92
100
  import { createClipboardCoordinator } from './clipboard-task.js';
93
101
  import { QueuePanel } from './QueuePanel.js';
94
102
  import { QueueOverlay } from './overlays/QueueOverlay.js';
@@ -140,6 +148,16 @@ export function resolveSettingsMaxTokens(raw, previous) {
140
148
  toast: ['warn', 'Max tokens must be a number or "auto".'],
141
149
  };
142
150
  }
151
+ /**
152
+ * The `/gov board` overlay's data: the full run when the controller can supply it (all four
153
+ * tabs), else the snapshot alone (the Work packages and Claims tabs then say they are empty).
154
+ */
155
+ function governanceBoardData(controller, snapshot) {
156
+ if (snapshot === null)
157
+ return null;
158
+ const input = controller.getGovernanceBoardInput?.();
159
+ return input ? boardDataFromRun({ snapshot, ...input }) : boardDataFromSnapshot(snapshot);
160
+ }
143
161
  /** Services in a non-terminal state, from the live snapshot map. */
144
162
  function liveServiceCount(services) {
145
163
  let n = 0;
@@ -354,6 +372,9 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
354
372
  const reducedMotion = cfg.reducedMotion ?? false;
355
373
  const stateRef = useRef(state);
356
374
  stateRef.current = state;
375
+ // The first-seen-eliminated map for the legion panel (a replaced member shows for one
376
+ // phase). A hook, so it is called unconditionally here and read further down.
377
+ const eliminatedSince = useEliminatedSince(state.governance);
357
378
  /**
358
379
  * The task label's two guards (tui-shortcut-drawer-and-task-summary D-10).
359
380
  *
@@ -826,14 +847,22 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
826
847
  submitRef.current(gate.message, { userInitiated: false, todoPolicy: 'continue' });
827
848
  }, FORCED_NUDGE_GRACE_MS);
828
849
  }
829
- // A deferred `plan -> build` becomes real here and nowhere else (§3.2).
830
- const applied = controller.applyPendingMode();
850
+ // A deferred `plan -> build` (or any posture change that waited for a governed
851
+ // stage, governance-mode D-23) becomes real here and nowhere else (§3.2).
852
+ const applied = controller.applyPendingPosture();
831
853
  if (applied) {
832
- dispatch({ type: 'setAgentMode', mode: applied.effective, pending: null });
854
+ const split = splitPosture(applied.effective);
855
+ dispatch({
856
+ type: 'setAgentMode',
857
+ mode: split.mode,
858
+ pending: null,
859
+ posture: applied.effective,
860
+ pendingPosture: applied.pending,
861
+ });
833
862
  dispatch({
834
863
  type: 'pushToast',
835
864
  level: 'info',
836
- text: `${MODE_LABEL[applied.effective]} mode.`,
865
+ text: postureAppliedToast(applied.effective),
837
866
  });
838
867
  }
839
868
  });
@@ -965,6 +994,25 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
965
994
  controller.dispose();
966
995
  };
967
996
  }, [controller]);
997
+ // --- Governance event stream (governance-mode 8.6 / 14.4). --------------
998
+ //
999
+ // ANOTHER CLI-LOCAL SUBSCRIPTION, for the reason the team one records: `GovernanceEvent`
1000
+ // is deliberately not a member of core's `AgentEvent` union. The wiring throttles the
1001
+ // chatty updates; this effect only folds snapshots into the view, appends the four kinds
1002
+ // of settlement card (charter, phase, escalation, final) and clears the live snapshot when
1003
+ // the run ends. A no-op unsubscribe when governance was not registered.
1004
+ useEffect(() => {
1005
+ return controller.subscribeGovernance((event) => {
1006
+ dispatch({ type: 'govSnapshot', snapshot: event.snapshot });
1007
+ const card = event.type === 'run_started' || event.type === 'stage_ended' ? event.card
1008
+ : event.type === 'escalation' || event.type === 'run_closed' ? event.card
1009
+ : null;
1010
+ if (card)
1011
+ dispatch({ type: 'governanceCard', card });
1012
+ if (event.type === 'run_closed')
1013
+ dispatch({ type: 'govEnd' });
1014
+ });
1015
+ }, [controller]);
968
1016
  // --- Todo event stream (todo-plan-execution §3.8 / §5.2). --------------
969
1017
  //
970
1018
  // A THIRD SUBSCRIPTION, for the reason the team one records: `TodoEvent` is
@@ -1386,7 +1434,14 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
1386
1434
  // reducer's `'build'` seed would render one wrong frame without this.
1387
1435
  useEffect(() => {
1388
1436
  const current = controller.getAgentMode();
1389
- dispatch({ type: 'setAgentMode', mode: current, pending: null });
1437
+ const posture = controller.getPostureState();
1438
+ dispatch({
1439
+ type: 'setAgentMode',
1440
+ mode: current,
1441
+ pending: null,
1442
+ posture: posture.effective,
1443
+ pendingPosture: posture.pending,
1444
+ });
1390
1445
  }, [controller]);
1391
1446
  // --- Let the controller drive command-list rebuilds. -------------------
1392
1447
  useEffect(() => {
@@ -1776,19 +1831,50 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
1776
1831
  * what was requested: dispatching the request is the one way to make the badge
1777
1832
  * claim a permission the gate will not honour.
1778
1833
  */
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 });
1834
+ const applyPosture = (next, opts = {}) => {
1835
+ const applied = controller.setPosture(next, opts.force ? { force: true } : {});
1836
+ const split = splitPosture(applied.effective);
1837
+ // THE ADOPTED VALUES, all four in one dispatch (governance-mode 4.3).
1838
+ dispatch({
1839
+ type: 'setAgentMode',
1840
+ mode: split.mode,
1841
+ pending: applied.pending ? splitPosture(applied.pending).mode : null,
1842
+ posture: applied.effective,
1843
+ pendingPosture: applied.pending,
1844
+ });
1782
1845
  if (opts.silent)
1783
1846
  return applied;
1784
1847
  if (applied.pending) {
1785
- toast('info', `${MODE_LABEL[applied.pending]} mode applies after this run.`);
1848
+ toast('info', controller.isGovernanceStageRunning()
1849
+ ? stageRunningToast(applied.pending)
1850
+ : posturePendingToast(applied.pending));
1786
1851
  }
1787
1852
  else {
1788
- toast('info', `${MODE_LABEL[applied.effective]} mode.`);
1853
+ toast('info', postureAppliedToast(applied.effective));
1854
+ // One-time, across launches: what GOVERN means (governance-mode 4.3).
1855
+ if (isGovernancePosture(applied.effective) && !getGovernanceIntroSeen()) {
1856
+ setGovernanceIntroSeen(true);
1857
+ dispatch({
1858
+ type: 'notice',
1859
+ level: 'info',
1860
+ text: applied.effective === 'plan_govern' ? PLAN_GOVERN_INTRO_NOTICE : GOVERN_INTRO_NOTICE,
1861
+ });
1862
+ }
1789
1863
  }
1790
1864
  return applied;
1791
1865
  };
1866
+ /**
1867
+ * The gate-mode view of the same write path, for `/plan`: a plain `plan` / `build`
1868
+ * request names a classic posture, so it CLEARS governance (an explicit command beats
1869
+ * an implicit carry-over, governance-mode 4.2).
1870
+ */
1871
+ const applyMode = (next, opts = {}) => {
1872
+ const applied = applyPosture(next, opts);
1873
+ return {
1874
+ effective: splitPosture(applied.effective).mode,
1875
+ pending: applied.pending ? splitPosture(applied.pending).mode : null,
1876
+ };
1877
+ };
1792
1878
  /**
1793
1879
  * Cancel any in-flight label call and make its result unadoptable.
1794
1880
  *
@@ -1857,8 +1943,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
1857
1943
  dispatch({ type: 'taskSummaryReady', view: outcome.view });
1858
1944
  });
1859
1945
  };
1946
+ const invocationCardsOn = invocationCardsEnabled();
1860
1947
  const makeCtx = (args) => ({
1861
1948
  args,
1949
+ invocationCards: invocationCardsOn,
1862
1950
  requestCopy: copyCoordinator.requestCopy,
1863
1951
  controller,
1864
1952
  state: stateRef.current,
@@ -1971,6 +2059,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
1971
2059
  },
1972
2060
  refreshSkills: () => setSkillsNonce((n) => n + 1),
1973
2061
  applyAgentMode: applyMode,
2062
+ applyPosture: (next, opts) => applyPosture(next, opts ?? {}),
1974
2063
  // Read at command time, so `/todo status` reports the live count rather than
1975
2064
  // whatever it was when this context was built.
1976
2065
  followBudget: budgetRef.current,
@@ -2035,10 +2124,17 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2035
2124
  // must be the one that runs, whichever branch it takes.
2036
2125
  cancelFollowThrough();
2037
2126
  cancelConfirmation();
2127
+ // THE ONE PLACE A CARD IS BUILT (tui-invocation-cards I-3 / I-6): redact -> normalise ->
2128
+ // limit, with the same redactor the `Command:` echo uses. `message` below is the model's
2129
+ // input and is never touched; the card only replaces what the TRANSCRIPT shows.
2130
+ const card = invocationCardsOn && opts.invocation
2131
+ ? buildInvocationCard(opts.invocation, (text) => redactText(text, getSecrets()))
2132
+ : undefined;
2133
+ const shown = card?.commandLine ?? message;
2038
2134
  if (interactionPhase.current === 'running' || controller.isCompactionBusy?.()) {
2039
2135
  try {
2040
2136
  const queueId = controller.queueUserMessage(message);
2041
- dispatch({ type: 'steerQueued', queueId, text: message });
2137
+ dispatch({ type: 'steerQueued', queueId, text: shown, ...(card ? { invocation: card } : {}) });
2042
2138
  return { accepted: true };
2043
2139
  }
2044
2140
  catch (error) {
@@ -2057,7 +2153,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2057
2153
  // the user has learned, so suppressing the fade is correct rather than
2058
2154
  // incidental — which is why the parameter is named for the CAUSE (P2-8).
2059
2155
  if (opts.userInitiated !== false) {
2060
- recordPrompt(message);
2156
+ // A card's command line was already recorded by `executeSlashInput`; the payload
2157
+ // behind it must never reach the up-arrow history (I-9: the gate is unchanged).
2158
+ if (!card)
2159
+ recordPrompt(message);
2061
2160
  // THE ONE PLACE THE NUDGE BUDGET IS REFILLED (P0-2 / I-10). Same predicate
2062
2161
  // as `recordPrompt`, and for the same reason: the question is "did a human
2063
2162
  // ask for this?", and only a human may hand the gate another attempt. A
@@ -2068,7 +2167,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2068
2167
  // Mirrors where the reducer clears its own copies, in the same action
2069
2168
  // (C-11): a new turn's end reason must not inherit the previous one's.
2070
2169
  endReasonRef.current = { aborted: false, errored: false };
2071
- dispatch({ type: 'submit', text: message });
2170
+ dispatch({ type: 'submit', text: shown, ...(card ? { invocation: card } : {}) });
2072
2171
  const pre = controller.preflight();
2073
2172
  if (!pre.ok) {
2074
2173
  dispatch({ type: 'runError' });
@@ -2088,7 +2187,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2088
2187
  // model call that describes a turn nobody ran; and below, in the queueing
2089
2188
  // branch, the message is steering rather than a new task.
2090
2189
  if (opts.userInitiated !== false)
2091
- ensureTaskSummary(message);
2190
+ ensureTaskSummary(shown);
2092
2191
  void controller.prompt(message, { todoPolicy: opts.todoPolicy ?? 'new-task' }).then((outcome) => {
2093
2192
  if (!mounted.current || sequence !== submissionSequence.current)
2094
2193
  return;
@@ -2127,7 +2226,13 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2127
2226
  // Retain a recoverable command before any asynchronous handler can fail.
2128
2227
  const safe = redactText(raw, getSecrets());
2129
2228
  recordPrompt(safe);
2130
- notify('info', `Command: ${safe}`);
2229
+ // The echo is suppressed BEFORE the command runs and never deleted afterwards: printed
2230
+ // scrollback rows cannot be rewritten (I-5). The predicate is the command's own (I-8).
2231
+ const parsed = parseSlashInput(raw);
2232
+ const rendersCard = invocationCardsOn && parsed !== null
2233
+ && registry.get(parsed.name)?.rendersInvocationCard?.(parsed.args) === true;
2234
+ if (!rendersCard)
2235
+ notify('info', `Command: ${safe}`);
2131
2236
  try {
2132
2237
  await runSlashInput(registry, raw, makeCtx);
2133
2238
  }
@@ -2275,8 +2380,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2275
2380
  // here would read `'plan'` and the badge would lag by a frame. Writing it
2276
2381
  // first through the same single path makes the tool's call an idempotent
2277
2382
  // no-op and keeps the mirror exact (§3.1 / R-P7).
2278
- applyMode('build', { force: true, silent: true });
2279
- toast('success', 'Plan approved. Implementing.');
2383
+ // PLAN+GOVERN hands over to the team instead of implementing: the same single write
2384
+ // path, with governance kept on (governance-mode 9.2).
2385
+ const governed = controller.getPosture() === 'plan_govern';
2386
+ applyPosture(governed ? 'govern' : 'build', { force: true, silent: true });
2387
+ toast('success', governed ? PLAN_APPROVED_TOAST : 'Plan approved. Implementing.');
2280
2388
  }
2281
2389
  else {
2282
2390
  toast('info', 'Revision requested.');
@@ -2593,6 +2701,9 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2593
2701
  // The Agents board pages through `overlayScroll` too, but keeps Up/Down for its
2594
2702
  // CHILD CURSOR - the same trade `settings` makes with its field cursor.
2595
2703
  || overlay === 'agents'
2704
+ // The governance board (governance-mode 10.5): without this line a PgDn in the
2705
+ // board falls through to the transcript behind it.
2706
+ || overlay === 'governance'
2596
2707
  // The prompt surfaces page themselves through `overlayScroll` too
2597
2708
  // (quick-prompt-library appendix B-1). Without them a `PgDn` in the list does
2598
2709
  // nothing and the wheel falls through to the transcript behind it.
@@ -2630,7 +2741,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2630
2741
  if ((key.tab && key.shift) || (key.ctrl && (input === 'n' || input === 'N'))) {
2631
2742
  if (stateRef.current.overlay)
2632
2743
  return;
2633
- applyMode(nextMode(controller.getAgentMode()));
2744
+ // The cycle's basis is `pending ?? effective`: with a change already waiting, a second
2745
+ // press must advance from it, or it would hold on the same target (governance-mode 4.1).
2746
+ const postureNow = controller.getPostureState();
2747
+ applyPosture(nextPosture(postureNow.pending ?? postureNow.effective, controller.getPostureCycle()));
2634
2748
  return;
2635
2749
  }
2636
2750
  // Moved off Ctrl+L by D-1, because Ctrl+L is the branch key now.
@@ -2945,10 +3059,32 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2945
3059
  ? Math.max(0, viewportBudget - 1)
2946
3060
  : Math.max(0, viewportBudget - composerBaseRows - queueLayout.rows - 1),
2947
3061
  });
3062
+ // THE LEGION PANEL REPLACES THE TEAM ROSTER while a governed stage runs (D-17, AC-25):
3063
+ // one live roster, never two. Between stages it is a two-line run strip, and it yields to
3064
+ // a non-governed dispatch if one is somehow live. Its height feeds the same two budgets
3065
+ // the roster's does, so the band and the rail never learn a second number.
3066
+ // PLAN+GOVERN's interview chip on the question overlay (governance-mode 9.3).
3067
+ const interviewState = state.posture === 'plan_govern' ? controller.getGovernanceInterview?.() : null;
3068
+ const interviewProps = interviewState
3069
+ ? { interview: { round: interviewState.answered, required: interviewState.required } }
3070
+ : {};
3071
+ const governanceMode = governancePanelMode(state.governance);
3072
+ const governanceLayout = state.governance !== null
3073
+ && liveRosterSurface(state.governance, state.team !== null) === 'governance'
3074
+ ? buildGovernancePanelLayout({
3075
+ cols: native ? Math.max(1, cols - 1) : baseRail.contentCols, rows,
3076
+ snapshot: state.governance, glyphs, reducedMotion, now: Date.now(),
3077
+ mode: governanceMode === 'panel' ? 'panel' : 'run-strip', eliminatedSince,
3078
+ availableRows: native
3079
+ ? Math.max(0, viewportBudget - 1)
3080
+ : Math.max(0, viewportBudget - composerBaseRows - queueLayout.rows - 1),
3081
+ })
3082
+ : null;
3083
+ const teamPanelRows = governanceLayout ? governanceLayout.height : teamLayout.rowCount;
2948
3084
  const railLayout = buildTodoRailLayout({
2949
3085
  cols: cols - 1, panelEnabled: cfg.todo.panel, overlayOpen: false,
2950
3086
  itemCount: state.todos?.items.length ?? 0, viewportBudget,
2951
- teamRows: teamLayout.rowCount, queueRows: queueLayout.rows, popupRows: 0, composerBaseRows,
3087
+ teamRows: teamPanelRows, queueRows: queueLayout.rows, popupRows: 0, composerBaseRows,
2952
3088
  });
2953
3089
  const { visible: railVisible, width: railWidth, rows: railRows, contentCols } = railLayout;
2954
3090
  // The FULL-SCREEN rail only: native mode draws its rail inside the live region's
@@ -2983,7 +3119,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
2983
3119
  // second time (`buildTeamPanelLayout` stays the single authority).
2984
3120
  itemCount: state.todos?.items.length ?? 0,
2985
3121
  panelEnabled: cfg.todo.panel,
2986
- teamRowsNatural: teamLayout.rowCount,
3122
+ teamRowsNatural: teamPanelRows,
2987
3123
  // The top give-way measures a window entry's height, and an expanded tool card is a
2988
3124
  // different height from a collapsed one - a forgotten value would print rows the region
2989
3125
  // is not drawing.
@@ -3079,14 +3215,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
3079
3215
  // mounted with `{...props}` and an undeclared prop arrives as
3080
3216
  // `undefined`. A construction-time snapshot, so reading it per frame is
3081
3217
  // honest (I-7).
3082
- teamRegistered: controller.isTeamRegistered(), probe: probe, onProfileSave: handleProfileSave, onReload: () => controller.createModelSettingsDraft(), onClose: () => dispatch({ type: 'setOverlay', overlay: null }),
3218
+ teamRegistered: controller.isTeamRegistered(), governanceRegistered: controller.isGovernanceRegistered(), probe: probe, onProfileSave: handleProfileSave, onReload: () => controller.createModelSettingsDraft(), onClose: () => dispatch({ type: 'setOverlay', overlay: null }),
3083
3219
  // Read from `cfg` (render scope, re-read every frame), so `/retry off`
3084
3220
  // while the screen is open shows the new value on the next paint.
3085
3221
  retrySummary: cfg.retry.enabled && cfg.retry.maxRetries > 0 ? String(cfg.retry.maxRetries) : 'off' })) : overlay === 'agents' ? (
3086
3222
  // THE BRANCH AND THE `Overlay` MEMBER SHIP TOGETHER (R-11): a missing branch is not
3087
3223
  // a type error, it is a SILENT BLANK FRAME, because `active={overlay === null}` has
3088
3224
  // 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;
3225
+ _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' ? (
3226
+ // THE BRANCH AND THE `Overlay` MEMBER SHIP TOGETHER (R-11), like `agents` above.
3227
+ _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
3228
  const density = cfg.density;
3091
3229
  const transcriptWindow = cfg.transcriptWindow ?? DEFAULT_TRANSCRIPT_WINDOW;
3092
3230
  // The fixed status bar remains visible underneath every overlay and toast.
@@ -3207,7 +3345,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
3207
3345
  stopping: interruptGesture.current.phase === 'stopping',
3208
3346
  interruptPhase: interruptGesture.current.phase,
3209
3347
  compacting: compactionLive || !!state.compaction?.inFlight,
3210
- mode: state.agentMode, pendingMode: state.pendingAgentMode,
3348
+ mode: state.posture, pendingMode: state.pendingPosture,
3211
3349
  servicesActive: { live: liveServices }, ecoRung: governor.rung,
3212
3350
  // THE STATUS-DETAIL ROW COUNTS LIKE THE ROSTER AND THE CHIP (A-1): this was the
3213
3351
  // last inline phase filter, left behind when the StatusBar chip converged, and it
@@ -3227,11 +3365,12 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
3227
3365
  selectionPending: selectionController?.hasPendingSelection() ?? false,
3228
3366
  copyInFlight: copyState.busy, copyCleanupPending: copyState.cleanupPending,
3229
3367
  hasDraft: hasDraftRef.current, services: liveServices,
3230
- hintsEnabled: cfg.hints, modeToggleKey,
3368
+ hintsEnabled: cfg.hints, modeToggleKey, governed: isGovernancePosture(state.posture),
3369
+ ...(state.governance?.pauseRequested === true ? { govPausePending: true } : {}),
3231
3370
  interruptPhase: interruptGesture.current.phase,
3232
3371
  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;
3372
+ 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' }) }));
3373
+ 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
3374
  // --- Native panel band (release-gate-and-native-panels §6.3/§6.4) -------------
3236
3375
  //
3237
3376
  // SEPARATE NODES, NOT THE TWO ABOVE (RV-P1-7). Those carry `cols={contentCols}` for
@@ -3252,7 +3391,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
3252
3391
  // out against `previewCols` would stop its clock one rail short of the screen's edge.
3253
3392
  // `teamRowGeometry` is continuous in `cols`, so the number that decides whether the
3254
3393
  // 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;
3394
+ 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
3395
  const rail = fullscreenRailVisible && state.todos ? (_jsx(TodoPanel, { snapshot: state.todos, width: railWidth, rows: railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
3257
3396
  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
3397
  const detailsNode = _jsx(BottomStatusRow, { plan: details, columns: viewCols, theme: theme });
@@ -3264,7 +3403,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
3264
3403
  // partially stale. The window and its `known` flag now travel INSIDE
3265
3404
  // it, resolved by the meter - which is what makes the user's
3266
3405
  // `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
3406
+ 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
3407
  ? {
3269
3408
  // `rosterCounts`, NOT a fourth inline phase filter (A-1): the chip and
3270
3409
  // 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;