@aragon-agent/cli 0.6.44 → 0.6.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +120 -2
  3. package/dist/agent/controller.d.ts +69 -0
  4. package/dist/agent/controller.js +224 -11
  5. package/dist/agent/governance-view.d.ts +40 -0
  6. package/dist/agent/governance-view.js +40 -0
  7. package/dist/agent/governance-wiring.d.ts +162 -0
  8. package/dist/agent/governance-wiring.js +470 -0
  9. package/dist/agent/invocation-card.d.ts +77 -0
  10. package/dist/agent/invocation-card.js +169 -0
  11. package/dist/agent/plan-prompt.d.ts +7 -0
  12. package/dist/agent/plan-prompt.js +2 -1
  13. package/dist/agent/prompt-options.d.ts +6 -0
  14. package/dist/agent/queued-messages.d.ts +3 -0
  15. package/dist/agent/queued-messages.js +4 -1
  16. package/dist/agent/reducer.d.ts +38 -1
  17. package/dist/agent/reducer.js +57 -12
  18. package/dist/agent/system-prompt.d.ts +19 -0
  19. package/dist/agent/system-prompt.js +9 -1
  20. package/dist/cli.js +45 -1
  21. package/dist/commands/builtins.js +22 -4
  22. package/dist/commands/governance-command.d.ts +35 -0
  23. package/dist/commands/governance-command.js +304 -0
  24. package/dist/commands/governance-runs-table.d.ts +40 -0
  25. package/dist/commands/governance-runs-table.js +130 -0
  26. package/dist/commands/index-build.d.ts +8 -0
  27. package/dist/commands/index-build.js +11 -0
  28. package/dist/commands/registry.d.ts +35 -0
  29. package/dist/commands/registry.js +20 -7
  30. package/dist/commands/skills.js +36 -2
  31. package/dist/commands/subagent-command.js +3 -1
  32. package/dist/commands/team-command.d.ts +6 -0
  33. package/dist/commands/team-command.js +40 -3
  34. package/dist/config/app-paths.d.ts +10 -0
  35. package/dist/config/app-paths.js +12 -0
  36. package/dist/config/cli-commands.d.ts +18 -0
  37. package/dist/config/cli-commands.js +47 -1
  38. package/dist/config/env.d.ts +2 -1
  39. package/dist/config/env.js +10 -1
  40. package/dist/config/governance-config.d.ts +125 -0
  41. package/dist/config/governance-config.js +164 -0
  42. package/dist/config/governance-launch.d.ts +71 -0
  43. package/dist/config/governance-launch.js +58 -0
  44. package/dist/config/load.d.ts +15 -0
  45. package/dist/config/load.js +26 -1
  46. package/dist/config/model-profile-store.d.ts +4 -2
  47. package/dist/config/schema.d.ts +22 -0
  48. package/dist/config/schema.js +6 -0
  49. package/dist/config/store.js +14 -1
  50. package/dist/config/ui-state.d.ts +11 -0
  51. package/dist/config/ui-state.js +11 -0
  52. package/dist/diagnostics/doctor.js +20 -0
  53. package/dist/governance/card-normalize.d.ts +24 -0
  54. package/dist/governance/card-normalize.js +76 -0
  55. package/dist/governance/copy.d.ts +109 -0
  56. package/dist/governance/copy.js +223 -0
  57. package/dist/governance/engine/aftermath.d.ts +49 -0
  58. package/dist/governance/engine/aftermath.js +134 -0
  59. package/dist/governance/engine/artifact-persist.d.ts +35 -0
  60. package/dist/governance/engine/artifact-persist.js +86 -0
  61. package/dist/governance/engine/budget.d.ts +52 -0
  62. package/dist/governance/engine/budget.js +112 -0
  63. package/dist/governance/engine/cards.d.ts +18 -0
  64. package/dist/governance/engine/cards.js +186 -0
  65. package/dist/governance/engine/digest.d.ts +32 -0
  66. package/dist/governance/engine/digest.js +63 -0
  67. package/dist/governance/engine/engine-core.d.ts +219 -0
  68. package/dist/governance/engine/engine-core.js +562 -0
  69. package/dist/governance/engine/engine.d.ts +68 -0
  70. package/dist/governance/engine/engine.js +314 -0
  71. package/dist/governance/engine/estimate.d.ts +38 -0
  72. package/dist/governance/engine/estimate.js +46 -0
  73. package/dist/governance/engine/inspector.d.ts +16 -0
  74. package/dist/governance/engine/inspector.js +396 -0
  75. package/dist/governance/engine/integration.d.ts +35 -0
  76. package/dist/governance/engine/integration.js +220 -0
  77. package/dist/governance/engine/lifecycle.d.ts +29 -0
  78. package/dist/governance/engine/lifecycle.js +656 -0
  79. package/dist/governance/engine/pause.d.ts +56 -0
  80. package/dist/governance/engine/pause.js +107 -0
  81. package/dist/governance/engine/phase-members.d.ts +15 -0
  82. package/dist/governance/engine/phase-members.js +34 -0
  83. package/dist/governance/engine/phase-planner.d.ts +53 -0
  84. package/dist/governance/engine/phase-planner.js +282 -0
  85. package/dist/governance/engine/rebuild.d.ts +27 -0
  86. package/dist/governance/engine/rebuild.js +57 -0
  87. package/dist/governance/engine/report.d.ts +66 -0
  88. package/dist/governance/engine/report.js +220 -0
  89. package/dist/governance/engine/run-digest.d.ts +20 -0
  90. package/dist/governance/engine/run-digest.js +42 -0
  91. package/dist/governance/engine/spec-builder.d.ts +117 -0
  92. package/dist/governance/engine/spec-builder.js +321 -0
  93. package/dist/governance/engine/stage-runner.d.ts +22 -0
  94. package/dist/governance/engine/stage-runner.js +664 -0
  95. package/dist/governance/engine/start-input.d.ts +19 -0
  96. package/dist/governance/engine/start-input.js +111 -0
  97. package/dist/governance/engine/state-machine.d.ts +101 -0
  98. package/dist/governance/engine/state-machine.js +586 -0
  99. package/dist/governance/engine/submission-queries.d.ts +42 -0
  100. package/dist/governance/engine/submission-queries.js +97 -0
  101. package/dist/governance/engine/submission-render.d.ts +12 -0
  102. package/dist/governance/engine/submission-render.js +92 -0
  103. package/dist/governance/engine/submissions.d.ts +27 -0
  104. package/dist/governance/engine/submissions.js +455 -0
  105. package/dist/governance/engine/visibility.d.ts +104 -0
  106. package/dist/governance/engine/visibility.js +223 -0
  107. package/dist/governance/governed-dispatch.d.ts +19 -0
  108. package/dist/governance/governed-dispatch.js +17 -0
  109. package/dist/governance/limits.d.ts +107 -0
  110. package/dist/governance/limits.js +131 -0
  111. package/dist/governance/posture-transition.d.ts +38 -0
  112. package/dist/governance/posture-transition.js +44 -0
  113. package/dist/governance/posture.d.ts +35 -0
  114. package/dist/governance/posture.js +59 -0
  115. package/dist/governance/profiles.d.ts +48 -0
  116. package/dist/governance/profiles.js +140 -0
  117. package/dist/governance/prompts/orchestrator-block.d.ts +40 -0
  118. package/dist/governance/prompts/orchestrator-block.js +128 -0
  119. package/dist/governance/prompts/plan-addendum.d.ts +22 -0
  120. package/dist/governance/prompts/plan-addendum.js +60 -0
  121. package/dist/governance/prompts/refusals.d.ts +48 -0
  122. package/dist/governance/prompts/refusals.js +82 -0
  123. package/dist/governance/prompts/role-blocks.d.ts +40 -0
  124. package/dist/governance/prompts/role-blocks.js +255 -0
  125. package/dist/governance/report-reader.d.ts +31 -0
  126. package/dist/governance/report-reader.js +57 -0
  127. package/dist/governance/roles.d.ts +83 -0
  128. package/dist/governance/roles.js +142 -0
  129. package/dist/governance/rules/claims.d.ts +83 -0
  130. package/dist/governance/rules/claims.js +250 -0
  131. package/dist/governance/rules/elimination.d.ts +66 -0
  132. package/dist/governance/rules/elimination.js +105 -0
  133. package/dist/governance/rules/events-table.d.ts +46 -0
  134. package/dist/governance/rules/events-table.js +94 -0
  135. package/dist/governance/rules/failure-analysis.d.ts +28 -0
  136. package/dist/governance/rules/failure-analysis.js +93 -0
  137. package/dist/governance/rules/ledger-replay.d.ts +53 -0
  138. package/dist/governance/rules/ledger-replay.js +53 -0
  139. package/dist/governance/rules/ledger-state.d.ts +85 -0
  140. package/dist/governance/rules/ledger-state.js +175 -0
  141. package/dist/governance/rules/ledger.d.ts +228 -0
  142. package/dist/governance/rules/ledger.js +560 -0
  143. package/dist/governance/rules/rca.d.ts +68 -0
  144. package/dist/governance/rules/rca.js +220 -0
  145. package/dist/governance/rules/settlement.d.ts +129 -0
  146. package/dist/governance/rules/settlement.js +363 -0
  147. package/dist/governance/rules/suspicion.d.ts +66 -0
  148. package/dist/governance/rules/suspicion.js +169 -0
  149. package/dist/governance/store/artifacts.d.ts +81 -0
  150. package/dist/governance/store/artifacts.js +173 -0
  151. package/dist/governance/store/atomic.d.ts +79 -0
  152. package/dist/governance/store/atomic.js +119 -0
  153. package/dist/governance/store/events-log.d.ts +35 -0
  154. package/dist/governance/store/events-log.js +86 -0
  155. package/dist/governance/store/run-lock.d.ts +51 -0
  156. package/dist/governance/store/run-lock.js +122 -0
  157. package/dist/governance/store/run-store.d.ts +229 -0
  158. package/dist/governance/store/run-store.js +644 -0
  159. package/dist/governance/store/scoreboard-export.d.ts +53 -0
  160. package/dist/governance/store/scoreboard-export.js +65 -0
  161. package/dist/governance/tools/child-tools.d.ts +31 -0
  162. package/dist/governance/tools/child-tools.js +65 -0
  163. package/dist/governance/tools/guard.d.ts +71 -0
  164. package/dist/governance/tools/guard.js +194 -0
  165. package/dist/governance/tools/inspector-tools.d.ts +63 -0
  166. package/dist/governance/tools/inspector-tools.js +424 -0
  167. package/dist/governance/tools/lead-tools.d.ts +42 -0
  168. package/dist/governance/tools/lead-tools.js +226 -0
  169. package/dist/governance/tools/normalize-util.d.ts +101 -0
  170. package/dist/governance/tools/normalize-util.js +230 -0
  171. package/dist/governance/tools/scan-incomplete.d.ts +79 -0
  172. package/dist/governance/tools/scan-incomplete.js +486 -0
  173. package/dist/governance/tools/shared-tools.d.ts +44 -0
  174. package/dist/governance/tools/shared-tools.js +125 -0
  175. package/dist/governance/tools/submit-normalizers.d.ts +28 -0
  176. package/dist/governance/tools/submit-normalizers.js +474 -0
  177. package/dist/governance/tools/submit-tools.d.ts +26 -0
  178. package/dist/governance/tools/submit-tools.js +171 -0
  179. package/dist/governance/types.d.ts +661 -0
  180. package/dist/governance/types.js +27 -0
  181. package/dist/session/persist.js +10 -0
  182. package/dist/session/validate-session.js +36 -3
  183. package/dist/team/forced-mode.d.ts +19 -0
  184. package/dist/team/forced-mode.js +42 -20
  185. package/dist/team/runtime.d.ts +8 -0
  186. package/dist/team/runtime.js +9 -0
  187. package/dist/team/subagent.d.ts +23 -0
  188. package/dist/team/subagent.js +14 -3
  189. package/dist/team/types.d.ts +6 -0
  190. package/dist/tools/index.d.ts +30 -7
  191. package/dist/tools/index.js +14 -0
  192. package/dist/tools/plan-tools.d.ts +14 -2
  193. package/dist/tools/plan-tools.js +5 -2
  194. package/dist/ui/App.js +170 -31
  195. package/dist/ui/BottomStatusRow.js +1 -1
  196. package/dist/ui/Composer.d.ts +7 -1
  197. package/dist/ui/Composer.js +9 -6
  198. package/dist/ui/GovernancePanel.d.ts +50 -0
  199. package/dist/ui/GovernancePanel.js +77 -0
  200. package/dist/ui/GovernanceRun.d.ts +18 -0
  201. package/dist/ui/GovernanceRun.js +11 -0
  202. package/dist/ui/StatusBar.d.ts +15 -2
  203. package/dist/ui/StatusBar.js +10 -4
  204. package/dist/ui/Transcript.js +7 -1
  205. package/dist/ui/entries/GovernanceCard.d.ts +30 -0
  206. package/dist/ui/entries/GovernanceCard.js +32 -0
  207. package/dist/ui/entries/InvocationCard.d.ts +20 -0
  208. package/dist/ui/entries/InvocationCard.js +32 -0
  209. package/dist/ui/governance-copy.d.ts +69 -0
  210. package/dist/ui/governance-copy.js +104 -0
  211. package/dist/ui/governance-status.d.ts +145 -0
  212. package/dist/ui/governance-status.js +326 -0
  213. package/dist/ui/interaction-copy.d.ts +4 -0
  214. package/dist/ui/interaction-copy.js +5 -0
  215. package/dist/ui/interaction-hints.d.ts +17 -0
  216. package/dist/ui/interaction-hints.js +14 -2
  217. package/dist/ui/layout/governance-board.d.ts +121 -0
  218. package/dist/ui/layout/governance-board.js +382 -0
  219. package/dist/ui/layout/governance-card.d.ts +53 -0
  220. package/dist/ui/layout/governance-card.js +134 -0
  221. package/dist/ui/layout/governance-chip.d.ts +38 -0
  222. package/dist/ui/layout/governance-chip.js +64 -0
  223. package/dist/ui/layout/governance-panel.d.ts +85 -0
  224. package/dist/ui/layout/governance-panel.js +358 -0
  225. package/dist/ui/layout/governance-roster.d.ts +65 -0
  226. package/dist/ui/layout/governance-roster.js +275 -0
  227. package/dist/ui/layout/invocation-card.d.ts +41 -0
  228. package/dist/ui/layout/invocation-card.js +91 -0
  229. package/dist/ui/layout/status-detail-layout.js +3 -3
  230. package/dist/ui/layout/status-layout.d.ts +5 -2
  231. package/dist/ui/layout/status-layout.js +10 -6
  232. package/dist/ui/layout/virtual-window.js +22 -1
  233. package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +41 -0
  234. package/dist/ui/overlays/GovernanceBoardOverlay.js +76 -0
  235. package/dist/ui/overlays/GovernanceSettingsPage.d.ts +48 -0
  236. package/dist/ui/overlays/GovernanceSettingsPage.js +93 -0
  237. package/dist/ui/overlays/HelpOverlay.js +4 -3
  238. package/dist/ui/overlays/QuestionOverlay.d.ts +9 -1
  239. package/dist/ui/overlays/QuestionOverlay.js +4 -2
  240. package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
  241. package/dist/ui/overlays/SettingsScreen.js +21 -1
  242. package/dist/ui/overlays/governance-settings.d.ts +79 -0
  243. package/dist/ui/overlays/governance-settings.js +140 -0
  244. package/dist/ui/overlays/model-profile-state.d.ts +6 -1
  245. package/dist/ui/overlays/model-profile-state.js +10 -0
  246. package/dist/ui/palettes.d.ts +17 -0
  247. package/dist/ui/palettes.js +9 -0
  248. package/dist/ui/scrollback/live-preview.js +2 -0
  249. package/dist/ui/scrollback/settled-boundary.js +3 -0
  250. package/dist/ui/shortcuts.js +2 -2
  251. package/dist/ui/theme.d.ts +7 -0
  252. package/dist/ui/theme.js +1 -0
  253. package/dist/ui/transcript-text.js +13 -1
  254. package/dist/ui/use-wheel-routing.js +3 -1
  255. package/package.json +2 -2
@@ -0,0 +1,470 @@
1
+ /**
2
+ * GovernanceWiring - the host side of governance mode (governance-mode 3.2, 14.2).
3
+ *
4
+ * ASCII ONLY (`agent/` is inside the glyph scanner's scope).
5
+ *
6
+ * `AgentController` is 3000+ lines over the repo's file limit, so every governance decision
7
+ * it needs lives here and the controller only forwards: registration, availability, the
8
+ * Orchestrator and role prompt blocks, the PLAN+GOVERN interview gate, the approved-plan
9
+ * stash, the engine's lifecycle, the child tool hooks. The posture TRANSITION rule is a
10
+ * pure function in `governance/posture-transition.ts` for the same reason.
11
+ *
12
+ * TWO LAYERS OF "CAN I USE THIS" (D-22 / RV-1 / RV-14), and they are not the same thing:
13
+ *
14
+ * - REGISTERED is decided ONCE, in the constructor, from inputs that cannot change
15
+ * afterwards: team tools exist, `governance.enabled`, the env kill switch, and the
16
+ * channel's STATIC `neverPrompts` (NOT the live `canPrompt()`: at construction the App
17
+ * has not attached its human channel yet, so `canPrompt()` is false for every TUI
18
+ * session and keying off it would register nothing, silently, forever). The tool array
19
+ * is immutable after `new Agent(...)`, so this is irreversible for the session.
20
+ * - AVAILABLE is live: registered AND `team.enabled` AND an API key. It is what `/gov`,
21
+ * `doctor` and the key cycle ask.
22
+ */
23
+ import { join } from 'node:path';
24
+ import { clampGovernanceConfig, isGovernanceEnvDisabled, } from '../config/governance-config.js';
25
+ import { getGovernanceDir } from '../config/app-paths.js';
26
+ import { RESTART_REQUIRED_TEXT, unavailableReasonText, } from '../governance/copy.js';
27
+ import { createGovernanceEngine } from '../governance/engine/engine.js';
28
+ import { estimateForRun } from '../governance/engine/estimate.js';
29
+ import { formatDispatchEstimate } from '../governance/copy.js';
30
+ import { GOV_CHILD_TOOL_NAMES, GOV_LEAD_TOOL_NAMES, GOV_LIMITS } from '../governance/limits.js';
31
+ import { composePosture, isGovernancePosture, POSTURES_CLASSIC, POSTURES_FULL, } from '../governance/posture.js';
32
+ import { buildPlanGovernAddendum } from '../governance/prompts/plan-addendum.js';
33
+ import { buildOrchestratorBlock } from '../governance/prompts/orchestrator-block.js';
34
+ import { PLAN_INTERVIEW_TOPICS, govAskRoundsRefusal } from '../governance/prompts/refusals.js';
35
+ import { buildRoleBlock } from '../governance/prompts/role-blocks.js';
36
+ import { readRunReport } from '../governance/report-reader.js';
37
+ import { ArtifactStore } from '../governance/store/artifacts.js';
38
+ import { RunStore } from '../governance/store/run-store.js';
39
+ import { buildChildTools } from '../governance/tools/child-tools.js';
40
+ import { applyInspectorToolPolicy, withGovernanceReadGuard } from '../governance/tools/guard.js';
41
+ import { makeGovDispatchTool, makeGovRunTool } from '../governance/tools/lead-tools.js';
42
+ import { TEAM_LIMITS } from '../team/limits.js';
43
+ /** Keyword probes that map an answered question onto the fixed interview topics. */
44
+ const TOPIC_PROBES = [
45
+ /goal|success|outcome|objective|criteria/i,
46
+ /scope|in scope|out of scope|include|exclude/i,
47
+ /constraint|stack|compat|performance|style|forbid/i,
48
+ /quality|test|security|review|strict/i,
49
+ /team|parallel|budget|cost|work package|\bwp/i,
50
+ /risk|depend|decision/i,
51
+ ];
52
+ function governanceOf(config) {
53
+ return clampGovernanceConfig(config.governance);
54
+ }
55
+ /** Render the approved plan as the run specification (<= `specChars`). */
56
+ export function renderPlanAsSpec(plan) {
57
+ const lines = [`# ${plan.title}`, '', plan.summary, ''];
58
+ plan.steps.forEach((step, i) => {
59
+ lines.push(`${i + 1}. ${step.title}`);
60
+ if (step.detail)
61
+ lines.push(` ${step.detail.replace(/\n/g, '\n ')}`);
62
+ });
63
+ if (plan.filesTouched.length > 0)
64
+ lines.push('', `Files: ${plan.filesTouched.join(', ')}`);
65
+ if (plan.risks.length > 0)
66
+ lines.push('', 'Risks:', ...plan.risks.map((r) => `- ${r}`));
67
+ if (plan.openQuestions.length > 0) {
68
+ lines.push('', 'Open questions:', ...plan.openQuestions.map((q) => `- ${q}`));
69
+ }
70
+ const text = lines.join('\n');
71
+ return text.length > GOV_LIMITS.specChars ? text.slice(0, GOV_LIMITS.specChars) : text;
72
+ }
73
+ const RUNS_LISTED = 10;
74
+ export class GovernanceWiring {
75
+ deps;
76
+ registered;
77
+ enabledAtStart;
78
+ env;
79
+ governOn = false;
80
+ pendingPosture = null;
81
+ engine = null;
82
+ store = null;
83
+ listeners = new Set();
84
+ engineUnsubscribe = null;
85
+ approvedPlan = null;
86
+ answeredRounds = 0;
87
+ coveredTopics = new Set();
88
+ /** A lazy facade: tools are built before the engine exists and the engine needs the runtime. */
89
+ api;
90
+ constructor(deps) {
91
+ this.deps = deps;
92
+ this.env = deps.env ?? process.env;
93
+ const gov = governanceOf(deps.config());
94
+ this.enabledAtStart = gov.enabled;
95
+ // STATIC, evaluated once (D-22). `neverPrompts` is read off the gate object, which is
96
+ // exactly what `createPlanTools` registers on.
97
+ this.registered =
98
+ deps.teamRegistered &&
99
+ gov.enabled &&
100
+ !isGovernanceEnvDisabled(this.env) &&
101
+ deps.gate.neverPrompts !== true;
102
+ this.api = this.buildLazyApi();
103
+ }
104
+ // -------------------------------------------------------------------------
105
+ // Registration and availability
106
+ // -------------------------------------------------------------------------
107
+ /** Whether governance tools were registered at construction. Never changes. */
108
+ isRegistered() {
109
+ return this.registered;
110
+ }
111
+ /** The live answer, with the precise reason when it is no. */
112
+ isAvailable() {
113
+ const config = this.deps.config();
114
+ const gov = governanceOf(config);
115
+ if (!this.registered)
116
+ return { ok: false, reason: this.unregisteredReason(gov) };
117
+ if (isGovernanceEnvDisabled(this.env))
118
+ return { ok: false, reason: 'disabled_env' };
119
+ if (!gov.enabled)
120
+ return { ok: false, reason: 'disabled_config' };
121
+ if (!config.team.enabled)
122
+ return { ok: false, reason: 'team_off' };
123
+ if (!this.deps.hasApiKey())
124
+ return { ok: false, reason: 'no_api_key' };
125
+ return { ok: true };
126
+ }
127
+ unregisteredReason(gov) {
128
+ if (isGovernanceEnvDisabled(this.env))
129
+ return 'disabled_env';
130
+ if (this.deps.gate.neverPrompts === true)
131
+ return 'headless';
132
+ if (!this.deps.teamRegistered)
133
+ return 'no_team';
134
+ // Disabled at start. Now on => the tool array cannot grow: restart. Still off => config.
135
+ return !this.enabledAtStart && gov.enabled ? 'restart_required' : 'disabled_config';
136
+ }
137
+ /** The exact sentence for the current unavailability, or null when available. */
138
+ unavailableText() {
139
+ const a = this.isAvailable();
140
+ if (a.ok)
141
+ return null;
142
+ return a.reason === 'restart_required' ? RESTART_REQUIRED_TEXT : unavailableReasonText(a.reason);
143
+ }
144
+ /** Shift+Tab cycle: four states when usable and `governance.shiftTab`, else the classic two. */
145
+ postureCycle() {
146
+ const gov = governanceOf(this.deps.config());
147
+ return this.isAvailable().ok && gov.shiftTab ? POSTURES_FULL : POSTURES_CLASSIC;
148
+ }
149
+ // -------------------------------------------------------------------------
150
+ // Posture state (the gate mode lives in the controller)
151
+ // -------------------------------------------------------------------------
152
+ isGovernOn() {
153
+ return this.governOn;
154
+ }
155
+ setGovernOn(on) {
156
+ this.governOn = on;
157
+ }
158
+ getPendingPosture() {
159
+ return this.pendingPosture;
160
+ }
161
+ setPendingPosture(pending) {
162
+ this.pendingPosture = pending;
163
+ }
164
+ /** Entering PLAN+GOVERN starts a fresh interview and drops any older plan (9.3). */
165
+ onPostureAdopted(posture, previous) {
166
+ if (posture === 'plan_govern' && previous !== 'plan_govern') {
167
+ this.answeredRounds = 0;
168
+ this.coveredTopics.clear();
169
+ this.approvedPlan = null;
170
+ }
171
+ }
172
+ isStageRunning() {
173
+ return this.engine?.isStageRunning() ?? false;
174
+ }
175
+ // -------------------------------------------------------------------------
176
+ // Prompt blocks
177
+ // -------------------------------------------------------------------------
178
+ /**
179
+ * `''` unless governance is on, registered and available (closed state: byte-equal).
180
+ *
181
+ * `config` is the config the prompt is being composed FOR. A settings save composes the
182
+ * prompt from the candidate BEFORE it becomes live, so reading the live config here would
183
+ * quote the limits from before the save (governance-mode-v1-1 IF-3).
184
+ */
185
+ orchestratorBlock(posture, config) {
186
+ if (!this.registered || !isGovernancePosture(posture) || !this.isAvailable().ok)
187
+ return '';
188
+ const gov = governanceOf(config ?? this.deps.config());
189
+ return buildOrchestratorBlock({
190
+ posture,
191
+ limits: {
192
+ maxWorkPackages: gov.maxWorkPackages,
193
+ maxPhases: gov.maxPhases,
194
+ maxRework: gov.maxRework,
195
+ maxFixCycles: gov.maxFixCycles,
196
+ budgetTokens: gov.budgetTokens,
197
+ },
198
+ });
199
+ }
200
+ /** `''` unless PLAN+GOVERN. */
201
+ planAddendum(posture, config) {
202
+ if (!this.registered || posture !== 'plan_govern' || !this.isAvailable().ok)
203
+ return '';
204
+ return buildPlanGovernAddendum(governanceOf(config ?? this.deps.config()).minAskRounds);
205
+ }
206
+ roleBlockFor(spec) {
207
+ const ext = spec.governance;
208
+ if (ext === undefined)
209
+ return '';
210
+ return buildRoleBlock(ext, { integrationCommands: ext.integrationCommands ?? [] });
211
+ }
212
+ // -------------------------------------------------------------------------
213
+ // PLAN+GOVERN interview and approval
214
+ // -------------------------------------------------------------------------
215
+ /** Called from the controller's `noteAnswered`: one real, answered, non-duplicate round. */
216
+ noteAnsweredRound(questions = []) {
217
+ if (!this.governOn || this.deps.getGateMode() !== 'plan')
218
+ return;
219
+ this.answeredRounds += 1;
220
+ const text = questions.map((q) => `${q.header} ${q.question}`).join(' ');
221
+ TOPIC_PROBES.forEach((probe, index) => {
222
+ if (probe.test(text))
223
+ this.coveredTopics.add(index);
224
+ });
225
+ }
226
+ answeredRoundCount() {
227
+ return this.answeredRounds;
228
+ }
229
+ /** `submit_plan` pre-check: a refusal text, or null to proceed. */
230
+ beforeSubmitPlan() {
231
+ if (!this.governOn || this.deps.getGateMode() !== 'plan')
232
+ return null;
233
+ const need = governanceOf(this.deps.config()).minAskRounds;
234
+ if (need <= 0 || !this.deps.gate.canPrompt())
235
+ return null;
236
+ if (this.answeredRounds >= need)
237
+ return null;
238
+ const uncovered = PLAN_INTERVIEW_TOPICS.filter((_, i) => !this.coveredTopics.has(i));
239
+ return govAskRoundsRefusal(this.answeredRounds, need, uncovered);
240
+ }
241
+ /** Stash the approved plan when governance is on. Returns which way the session goes. */
242
+ handlePlanApproved(plan) {
243
+ if (!this.governOn)
244
+ return 'build';
245
+ this.approvedPlan = renderPlanAsSpec(plan);
246
+ return 'govern';
247
+ }
248
+ /** `gov_run start` consumes the stash exactly once. */
249
+ takeApprovedPlan() {
250
+ const plan = this.approvedPlan;
251
+ this.approvedPlan = null;
252
+ return plan;
253
+ }
254
+ // -------------------------------------------------------------------------
255
+ // Tools
256
+ // -------------------------------------------------------------------------
257
+ /** `[]` when not registered, which is the byte-identical closed path. */
258
+ leadTools() {
259
+ if (!this.registered)
260
+ return [];
261
+ return [makeGovRunTool(this.api), makeGovDispatchTool(this.api)];
262
+ }
263
+ leadPolicyExempt() {
264
+ return GOV_LEAD_TOOL_NAMES;
265
+ }
266
+ childPolicyExempt() {
267
+ return GOV_CHILD_TOOL_NAMES;
268
+ }
269
+ /** Role tools for a governed child; `[]` for anything else. */
270
+ childToolsFor(spec) {
271
+ const ext = spec.governance;
272
+ if (ext === undefined)
273
+ return [];
274
+ const identity = { id: ext.identity, role: ext.role, generation: ext.generation };
275
+ return buildChildTools(ext.role, this.api, identity, { getCwd: () => this.deps.getCwd() });
276
+ }
277
+ /**
278
+ * Narrow a governed child's finished tool array: the inspector loses its write tools and
279
+ * gets the restricted shell, and every read-class tool gets the store tripwire.
280
+ *
281
+ * This runs on the factory's RESULT (see `SubagentDeps.narrowToolsFor`) because it has to
282
+ * see the built-in `bash` to replace it, while the role tools had to go IN through the
283
+ * factory. A non-governed spec comes back untouched.
284
+ */
285
+ narrowToolsFor(spec, tools) {
286
+ const ext = spec.governance;
287
+ if (ext === undefined)
288
+ return tools;
289
+ const root = getGovernanceDir();
290
+ const base = ext.role === 'INSP'
291
+ ? applyInspectorToolPolicy(tools, { integrationCommands: ext.integrationCommands ?? [] })
292
+ : tools;
293
+ return base.map((tool) => withGovernanceReadGuard(tool, root));
294
+ }
295
+ // -------------------------------------------------------------------------
296
+ // Engine lifecycle and events
297
+ // -------------------------------------------------------------------------
298
+ /** The engine is created on first use: a session that never governs never opens a store. */
299
+ ensureEngine() {
300
+ if (this.engine !== null)
301
+ return this.engine;
302
+ const runtime = this.deps.getRuntime();
303
+ if (runtime === null)
304
+ throw new Error('governance: no team runtime in this session');
305
+ const store = new RunStore({ root: getGovernanceDir() });
306
+ this.store = store;
307
+ const engineDeps = {
308
+ runtime,
309
+ store,
310
+ artifacts: new ArtifactStore(store),
311
+ getConfig: () => governanceOf(this.deps.config()),
312
+ getTeamCeiling: () => Math.max(1, Math.min(this.deps.config().team.maxSubagents, TEAM_LIMITS.hardMaxSubagents)),
313
+ getCwd: () => this.deps.getCwd(),
314
+ getSessionId: () => this.deps.getSessionId?.(),
315
+ withPausedWatchdog: (fn) => this.deps.withPausedWatchdog(fn),
316
+ hasApiKey: () => this.deps.hasApiKey(),
317
+ modelCost: () => this.deps.modelCost(),
318
+ takeApprovedPlan: () => this.takeApprovedPlan(),
319
+ confirmCharter: (charter, signal) => this.confirmCharter(charter, signal),
320
+ now: this.deps.now ?? Date.now,
321
+ };
322
+ this.engine = createGovernanceEngine(engineDeps);
323
+ this.engineUnsubscribe = this.engine.subscribe((event) => {
324
+ for (const listener of [...this.listeners])
325
+ listener(event);
326
+ });
327
+ return this.engine;
328
+ }
329
+ /** `confirmCharter` through the EXISTING plan-review overlay; no new overlay kind (RV-10). */
330
+ async confirmCharter(charter, signal) {
331
+ if (!this.deps.gate.canPrompt())
332
+ return { decision: 'approved' };
333
+ const plan = {
334
+ title: charter.title,
335
+ summary: charter.summary,
336
+ steps: charter.steps,
337
+ filesTouched: charter.filesTouched,
338
+ risks: charter.risks,
339
+ openQuestions: [],
340
+ };
341
+ const response = await this.deps.withHumanWait(() => this.deps.gate.request({ kind: 'plan', plan }, signal));
342
+ if (response === null || response.kind !== 'planDecision')
343
+ return null;
344
+ return { decision: response.decision, feedback: response.feedback };
345
+ }
346
+ /** Subscribe to the governance event stream (works before the engine exists). */
347
+ subscribe(listener) {
348
+ this.listeners.add(listener);
349
+ return () => {
350
+ this.listeners.delete(listener);
351
+ };
352
+ }
353
+ snapshot() {
354
+ return this.engine?.snapshot() ?? null;
355
+ }
356
+ /** Run + ledger for the `/gov board` overlay (read-only views of live memory). */
357
+ boardInput() {
358
+ const core = this.engine?.internals;
359
+ if (core === undefined || core.run === null)
360
+ return null;
361
+ return {
362
+ run: core.run,
363
+ ledger: core.ledger,
364
+ boardPath: join(getGovernanceDir(), core.run.runId, 'board.md'),
365
+ };
366
+ }
367
+ abortStage() {
368
+ this.engine?.abortStage();
369
+ }
370
+ /**
371
+ * Recent runs for `/gov runs`, newest first. Opens the store lazily; `[]` when governance
372
+ * is unusable. `RunStore.listRuns()` is ALREADY newest-first: the previous
373
+ * `slice(-10).reverse()` therefore returned the OLDEST ten once there were more than ten.
374
+ */
375
+ async listRuns() {
376
+ if (!this.registered)
377
+ return [];
378
+ const store = this.store ?? new RunStore({ root: getGovernanceDir() });
379
+ const rows = await store.listRuns();
380
+ return rows.slice(0, RUNS_LISTED).map((r) => ({
381
+ runId: r.runId,
382
+ goal: r.goal,
383
+ status: r.status,
384
+ updatedAt: r.updatedAt,
385
+ ...(r.digest !== undefined ? { digest: r.digest } : {}),
386
+ }));
387
+ }
388
+ /** `/gov report [runId]`: a stored report, read-only (governance-mode-v1-1 7.2). */
389
+ async readReport(runId) {
390
+ if (!this.registered)
391
+ return { kind: 'no_run' };
392
+ const store = this.store ?? new RunStore({ root: getGovernanceDir() });
393
+ const wanted = (runId ?? '').trim();
394
+ if (wanted !== '')
395
+ return readRunReport(store, wanted);
396
+ const latest = await store.latestForCwd(this.deps.getCwd());
397
+ return latest === null ? { kind: 'no_run' } : readRunReport(store, latest.runId);
398
+ }
399
+ /** `/gov pause`: the engine's verdict, or `no_run` when no engine was ever created. */
400
+ requestPause() {
401
+ return this.engine?.requestPause() ?? Promise.resolve('no_run');
402
+ }
403
+ cancelPause() {
404
+ return this.engine?.cancelPause() ?? false;
405
+ }
406
+ /** The dispatch-plan line for the live run (`/gov status`), or null without one. */
407
+ dispatchPlanText() {
408
+ const run = this.engine?.internals.run ?? null;
409
+ if (run === null)
410
+ return null;
411
+ const ceiling = Math.max(1, Math.min(this.deps.config().team.maxSubagents, TEAM_LIMITS.hardMaxSubagents));
412
+ return formatDispatchEstimate(estimateForRun(run, ceiling)) || null;
413
+ }
414
+ /**
415
+ * The run `/gov resume [runId]` should hand to the orchestrator: the named one when it
416
+ * exists, else the newest resumable run for this directory. `null` when there is none.
417
+ */
418
+ async findResumableRun(runId) {
419
+ if (!this.registered)
420
+ return null;
421
+ const store = this.store ?? new RunStore({ root: getGovernanceDir() });
422
+ if (runId !== undefined && runId !== '') {
423
+ const known = (await store.listRuns()).find((r) => r.runId === runId);
424
+ return known ? known.runId : null;
425
+ }
426
+ const latest = await store.latestResumable(this.deps.getCwd());
427
+ return latest ? latest.runId : null;
428
+ }
429
+ dispose() {
430
+ this.engineUnsubscribe?.();
431
+ this.engineUnsubscribe = null;
432
+ this.engine?.dispose();
433
+ this.engine = null;
434
+ this.listeners.clear();
435
+ }
436
+ // -------------------------------------------------------------------------
437
+ // Lazy facade
438
+ // -------------------------------------------------------------------------
439
+ buildLazyApi() {
440
+ const e = () => this.ensureEngine();
441
+ return {
442
+ start: (input, signal) => e().start(input, signal),
443
+ status: () => e().status(),
444
+ resume: (runId) => e().resume(runId),
445
+ dispatchStage: (input, signal) => e().dispatchStage(input, signal),
446
+ close: (summary) => e().close(summary),
447
+ abandon: (reason) => e().abandon(reason),
448
+ resolve: (input) => e().resolve(input),
449
+ snapshot: () => this.snapshot(),
450
+ isStageRunning: () => this.isStageRunning(),
451
+ abortStage: () => this.abortStage(),
452
+ subscribe: (listener) => this.subscribe(listener),
453
+ dispose: () => this.dispose(),
454
+ submitWork: (id, input) => e().submitWork(id, input),
455
+ submitReview: (id, input) => e().submitReview(id, input),
456
+ submitTest: (id, input) => e().submitTest(id, input),
457
+ inspectorApply: (input) => e().inspectorApply(input),
458
+ inspectorRule: (input) => e().inspectorRule(input),
459
+ inspectorSettle: (input) => e().inspectorSettle(input),
460
+ standings: () => e().standings(),
461
+ readArtifact: (id, input) => e().readArtifact(id, input),
462
+ requestView: (id, input) => e().requestView(id, input),
463
+ scan: (id, input) => e().scan(id, input),
464
+ };
465
+ }
466
+ /** Posture helper for callers that only hold the gate mode. */
467
+ currentPosture(mode) {
468
+ return composePosture(mode, this.governOn);
469
+ }
470
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Invocation cards (tui-invocation-cards): what a transcript shows INSTEAD of the
3
+ * system prompt a command submitted on the user's behalf.
4
+ *
5
+ * Pure and total: no IO, no React, no Ink, nothing but `import type`. ASCII ONLY -
6
+ * `src/agent/**` new files sit inside the `glyphs.test.ts` scan scope.
7
+ *
8
+ * ONE CONSTRUCTOR (I-3). `buildInvocationCard` is the only function that turns raw
9
+ * command-layer material into a card, `App.submitMessage` is its only caller, and
10
+ * `sanitizeInvocationCard` (the session-load path) shares its normaliser so a card
11
+ * written today and a card read back tomorrow cannot drift apart.
12
+ *
13
+ * ORDER IS REDACT -> NORMALISE -> LIMIT, and it is the load-bearing part (I-6). Cut
14
+ * first and a secret that straddles the cut leaves a prefix shorter than the
15
+ * redaction patterns' minimum length: the half-key sails through into the
16
+ * transcript, the session file and the exit replay.
17
+ */
18
+ export type InvocationVariant = 'skill' | 'team' | 'todo' | 'govern';
19
+ /** Everything one card needs; persisted, queued and rendered in this one shape. */
20
+ export interface InvocationCard {
21
+ readonly variant: InvocationVariant;
22
+ /** Skill name / 'continue' / 'resume'; '' for team. */
23
+ readonly name: string;
24
+ /** What the user typed (or its equivalent), redacted and capped; also `Entry.text`. */
25
+ readonly commandLine: string;
26
+ /** The user's own words (args / brief), redacted and trimmed; may be ''. */
27
+ readonly input: string;
28
+ /** Characters cut from `input`; 0 when nothing was cut. */
29
+ readonly inputHidden: number;
30
+ /** Fact fragments, each at most `factMaxChars`, at most `factsMax` of them. */
31
+ readonly facts: readonly string[];
32
+ /** One disclosure sentence. */
33
+ readonly note?: string;
34
+ }
35
+ /**
36
+ * The renderer, the height estimate and the loader all read THESE numbers (I-4).
37
+ * A card's height is a function of its data and these limits, so it is bounded:
38
+ * header + `inputMaxLines` + hidden tail + note.
39
+ */
40
+ export declare const INVOCATION_LIMITS: {
41
+ readonly commandLineMaxChars: 2000;
42
+ readonly inputMaxChars: 480;
43
+ readonly inputMaxLines: 8;
44
+ readonly nameMaxChars: 64;
45
+ readonly factsMax: 5;
46
+ readonly factMaxChars: 28;
47
+ readonly noteMaxChars: 140;
48
+ };
49
+ /**
50
+ * Raw material from the command layer: NOT redacted, NOT trimmed. Commands must not
51
+ * cut or scrub it themselves - both happen exactly once, in `buildInvocationCard`.
52
+ */
53
+ export interface InvocationCardInput {
54
+ variant: InvocationVariant;
55
+ name?: string;
56
+ commandLine: string;
57
+ input?: string;
58
+ facts?: readonly string[];
59
+ note?: string;
60
+ }
61
+ /**
62
+ * Total, never throws. `redact` is injected (the same closure `Command:` echo uses) so
63
+ * this module depends on nothing; the default is the identity for tests and the loader.
64
+ */
65
+ export declare function buildInvocationCard(input: InvocationCardInput, redact?: (text: string) => string): InvocationCard;
66
+ /**
67
+ * Session-load path: `null` for anything that is not a plausible card, so the caller
68
+ * drops the field and the entry degrades to a plain user message (I-7). Re-derives
69
+ * `inputHidden` from nothing - it trusts the stored count only when it is a sane integer.
70
+ */
71
+ export declare function sanitizeInvocationCard(value: unknown): InvocationCard | null;
72
+ /**
73
+ * Emergency kill switch (`ARAGON_NO_INVOCATION_CARDS=1`): every command and the App fall
74
+ * back to the pre-card behaviour. Read at call time, not import time, so a test (or a
75
+ * wrapper script) that sets the variable after load is honoured.
76
+ */
77
+ export declare function invocationCardsEnabled(env?: NodeJS.ProcessEnv): boolean;