@aragon-agent/cli 0.6.44 → 0.6.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +69 -2
  3. package/dist/agent/controller.d.ts +69 -0
  4. package/dist/agent/controller.js +224 -11
  5. package/dist/agent/governance-view.d.ts +40 -0
  6. package/dist/agent/governance-view.js +40 -0
  7. package/dist/agent/governance-wiring.d.ts +162 -0
  8. package/dist/agent/governance-wiring.js +470 -0
  9. package/dist/agent/plan-prompt.d.ts +7 -0
  10. package/dist/agent/plan-prompt.js +2 -1
  11. package/dist/agent/reducer.d.ts +33 -1
  12. package/dist/agent/reducer.js +45 -9
  13. package/dist/agent/system-prompt.d.ts +19 -0
  14. package/dist/agent/system-prompt.js +9 -1
  15. package/dist/cli.js +45 -1
  16. package/dist/commands/builtins.js +10 -4
  17. package/dist/commands/governance-command.d.ts +35 -0
  18. package/dist/commands/governance-command.js +292 -0
  19. package/dist/commands/governance-runs-table.d.ts +40 -0
  20. package/dist/commands/governance-runs-table.js +130 -0
  21. package/dist/commands/registry.d.ts +12 -0
  22. package/dist/config/app-paths.d.ts +10 -0
  23. package/dist/config/app-paths.js +12 -0
  24. package/dist/config/cli-commands.d.ts +18 -0
  25. package/dist/config/cli-commands.js +47 -1
  26. package/dist/config/env.d.ts +2 -1
  27. package/dist/config/env.js +10 -1
  28. package/dist/config/governance-config.d.ts +125 -0
  29. package/dist/config/governance-config.js +164 -0
  30. package/dist/config/governance-launch.d.ts +71 -0
  31. package/dist/config/governance-launch.js +58 -0
  32. package/dist/config/load.d.ts +15 -0
  33. package/dist/config/load.js +26 -1
  34. package/dist/config/model-profile-store.d.ts +4 -2
  35. package/dist/config/schema.d.ts +22 -0
  36. package/dist/config/schema.js +6 -0
  37. package/dist/config/store.js +14 -1
  38. package/dist/config/ui-state.d.ts +11 -0
  39. package/dist/config/ui-state.js +11 -0
  40. package/dist/diagnostics/doctor.js +20 -0
  41. package/dist/governance/card-normalize.d.ts +24 -0
  42. package/dist/governance/card-normalize.js +76 -0
  43. package/dist/governance/copy.d.ts +109 -0
  44. package/dist/governance/copy.js +223 -0
  45. package/dist/governance/engine/aftermath.d.ts +49 -0
  46. package/dist/governance/engine/aftermath.js +134 -0
  47. package/dist/governance/engine/artifact-persist.d.ts +35 -0
  48. package/dist/governance/engine/artifact-persist.js +86 -0
  49. package/dist/governance/engine/budget.d.ts +52 -0
  50. package/dist/governance/engine/budget.js +112 -0
  51. package/dist/governance/engine/cards.d.ts +18 -0
  52. package/dist/governance/engine/cards.js +186 -0
  53. package/dist/governance/engine/digest.d.ts +32 -0
  54. package/dist/governance/engine/digest.js +63 -0
  55. package/dist/governance/engine/engine-core.d.ts +219 -0
  56. package/dist/governance/engine/engine-core.js +562 -0
  57. package/dist/governance/engine/engine.d.ts +68 -0
  58. package/dist/governance/engine/engine.js +314 -0
  59. package/dist/governance/engine/estimate.d.ts +38 -0
  60. package/dist/governance/engine/estimate.js +46 -0
  61. package/dist/governance/engine/inspector.d.ts +16 -0
  62. package/dist/governance/engine/inspector.js +396 -0
  63. package/dist/governance/engine/integration.d.ts +35 -0
  64. package/dist/governance/engine/integration.js +220 -0
  65. package/dist/governance/engine/lifecycle.d.ts +29 -0
  66. package/dist/governance/engine/lifecycle.js +656 -0
  67. package/dist/governance/engine/pause.d.ts +56 -0
  68. package/dist/governance/engine/pause.js +107 -0
  69. package/dist/governance/engine/phase-members.d.ts +15 -0
  70. package/dist/governance/engine/phase-members.js +34 -0
  71. package/dist/governance/engine/phase-planner.d.ts +53 -0
  72. package/dist/governance/engine/phase-planner.js +282 -0
  73. package/dist/governance/engine/rebuild.d.ts +27 -0
  74. package/dist/governance/engine/rebuild.js +57 -0
  75. package/dist/governance/engine/report.d.ts +66 -0
  76. package/dist/governance/engine/report.js +220 -0
  77. package/dist/governance/engine/run-digest.d.ts +20 -0
  78. package/dist/governance/engine/run-digest.js +42 -0
  79. package/dist/governance/engine/spec-builder.d.ts +117 -0
  80. package/dist/governance/engine/spec-builder.js +321 -0
  81. package/dist/governance/engine/stage-runner.d.ts +22 -0
  82. package/dist/governance/engine/stage-runner.js +664 -0
  83. package/dist/governance/engine/start-input.d.ts +19 -0
  84. package/dist/governance/engine/start-input.js +111 -0
  85. package/dist/governance/engine/state-machine.d.ts +101 -0
  86. package/dist/governance/engine/state-machine.js +586 -0
  87. package/dist/governance/engine/submission-queries.d.ts +42 -0
  88. package/dist/governance/engine/submission-queries.js +97 -0
  89. package/dist/governance/engine/submission-render.d.ts +12 -0
  90. package/dist/governance/engine/submission-render.js +92 -0
  91. package/dist/governance/engine/submissions.d.ts +27 -0
  92. package/dist/governance/engine/submissions.js +455 -0
  93. package/dist/governance/engine/visibility.d.ts +104 -0
  94. package/dist/governance/engine/visibility.js +223 -0
  95. package/dist/governance/governed-dispatch.d.ts +19 -0
  96. package/dist/governance/governed-dispatch.js +17 -0
  97. package/dist/governance/limits.d.ts +107 -0
  98. package/dist/governance/limits.js +131 -0
  99. package/dist/governance/posture-transition.d.ts +38 -0
  100. package/dist/governance/posture-transition.js +44 -0
  101. package/dist/governance/posture.d.ts +35 -0
  102. package/dist/governance/posture.js +59 -0
  103. package/dist/governance/profiles.d.ts +48 -0
  104. package/dist/governance/profiles.js +140 -0
  105. package/dist/governance/prompts/orchestrator-block.d.ts +40 -0
  106. package/dist/governance/prompts/orchestrator-block.js +128 -0
  107. package/dist/governance/prompts/plan-addendum.d.ts +22 -0
  108. package/dist/governance/prompts/plan-addendum.js +60 -0
  109. package/dist/governance/prompts/refusals.d.ts +48 -0
  110. package/dist/governance/prompts/refusals.js +82 -0
  111. package/dist/governance/prompts/role-blocks.d.ts +40 -0
  112. package/dist/governance/prompts/role-blocks.js +255 -0
  113. package/dist/governance/report-reader.d.ts +31 -0
  114. package/dist/governance/report-reader.js +57 -0
  115. package/dist/governance/roles.d.ts +83 -0
  116. package/dist/governance/roles.js +142 -0
  117. package/dist/governance/rules/claims.d.ts +83 -0
  118. package/dist/governance/rules/claims.js +250 -0
  119. package/dist/governance/rules/elimination.d.ts +66 -0
  120. package/dist/governance/rules/elimination.js +105 -0
  121. package/dist/governance/rules/events-table.d.ts +46 -0
  122. package/dist/governance/rules/events-table.js +94 -0
  123. package/dist/governance/rules/failure-analysis.d.ts +28 -0
  124. package/dist/governance/rules/failure-analysis.js +93 -0
  125. package/dist/governance/rules/ledger-replay.d.ts +53 -0
  126. package/dist/governance/rules/ledger-replay.js +53 -0
  127. package/dist/governance/rules/ledger-state.d.ts +85 -0
  128. package/dist/governance/rules/ledger-state.js +175 -0
  129. package/dist/governance/rules/ledger.d.ts +228 -0
  130. package/dist/governance/rules/ledger.js +560 -0
  131. package/dist/governance/rules/rca.d.ts +68 -0
  132. package/dist/governance/rules/rca.js +220 -0
  133. package/dist/governance/rules/settlement.d.ts +129 -0
  134. package/dist/governance/rules/settlement.js +363 -0
  135. package/dist/governance/rules/suspicion.d.ts +66 -0
  136. package/dist/governance/rules/suspicion.js +169 -0
  137. package/dist/governance/store/artifacts.d.ts +81 -0
  138. package/dist/governance/store/artifacts.js +173 -0
  139. package/dist/governance/store/atomic.d.ts +79 -0
  140. package/dist/governance/store/atomic.js +119 -0
  141. package/dist/governance/store/events-log.d.ts +35 -0
  142. package/dist/governance/store/events-log.js +86 -0
  143. package/dist/governance/store/run-lock.d.ts +51 -0
  144. package/dist/governance/store/run-lock.js +122 -0
  145. package/dist/governance/store/run-store.d.ts +229 -0
  146. package/dist/governance/store/run-store.js +644 -0
  147. package/dist/governance/store/scoreboard-export.d.ts +53 -0
  148. package/dist/governance/store/scoreboard-export.js +65 -0
  149. package/dist/governance/tools/child-tools.d.ts +31 -0
  150. package/dist/governance/tools/child-tools.js +65 -0
  151. package/dist/governance/tools/guard.d.ts +71 -0
  152. package/dist/governance/tools/guard.js +194 -0
  153. package/dist/governance/tools/inspector-tools.d.ts +63 -0
  154. package/dist/governance/tools/inspector-tools.js +424 -0
  155. package/dist/governance/tools/lead-tools.d.ts +42 -0
  156. package/dist/governance/tools/lead-tools.js +226 -0
  157. package/dist/governance/tools/normalize-util.d.ts +101 -0
  158. package/dist/governance/tools/normalize-util.js +230 -0
  159. package/dist/governance/tools/scan-incomplete.d.ts +79 -0
  160. package/dist/governance/tools/scan-incomplete.js +486 -0
  161. package/dist/governance/tools/shared-tools.d.ts +44 -0
  162. package/dist/governance/tools/shared-tools.js +125 -0
  163. package/dist/governance/tools/submit-normalizers.d.ts +28 -0
  164. package/dist/governance/tools/submit-normalizers.js +474 -0
  165. package/dist/governance/tools/submit-tools.d.ts +26 -0
  166. package/dist/governance/tools/submit-tools.js +171 -0
  167. package/dist/governance/types.d.ts +661 -0
  168. package/dist/governance/types.js +27 -0
  169. package/dist/session/validate-session.js +32 -1
  170. package/dist/team/runtime.d.ts +8 -0
  171. package/dist/team/runtime.js +9 -0
  172. package/dist/team/subagent.d.ts +23 -0
  173. package/dist/team/subagent.js +14 -3
  174. package/dist/team/types.d.ts +6 -0
  175. package/dist/tools/index.d.ts +30 -7
  176. package/dist/tools/index.js +14 -0
  177. package/dist/tools/plan-tools.d.ts +14 -2
  178. package/dist/tools/plan-tools.js +5 -2
  179. package/dist/ui/App.js +145 -25
  180. package/dist/ui/BottomStatusRow.js +1 -1
  181. package/dist/ui/Composer.d.ts +7 -1
  182. package/dist/ui/Composer.js +9 -6
  183. package/dist/ui/GovernancePanel.d.ts +50 -0
  184. package/dist/ui/GovernancePanel.js +77 -0
  185. package/dist/ui/GovernanceRun.d.ts +18 -0
  186. package/dist/ui/GovernanceRun.js +11 -0
  187. package/dist/ui/StatusBar.d.ts +15 -2
  188. package/dist/ui/StatusBar.js +10 -4
  189. package/dist/ui/Transcript.js +3 -0
  190. package/dist/ui/entries/GovernanceCard.d.ts +30 -0
  191. package/dist/ui/entries/GovernanceCard.js +32 -0
  192. package/dist/ui/governance-copy.d.ts +69 -0
  193. package/dist/ui/governance-copy.js +104 -0
  194. package/dist/ui/governance-status.d.ts +145 -0
  195. package/dist/ui/governance-status.js +326 -0
  196. package/dist/ui/interaction-copy.d.ts +4 -0
  197. package/dist/ui/interaction-copy.js +5 -0
  198. package/dist/ui/interaction-hints.d.ts +17 -0
  199. package/dist/ui/interaction-hints.js +14 -2
  200. package/dist/ui/layout/governance-board.d.ts +121 -0
  201. package/dist/ui/layout/governance-board.js +382 -0
  202. package/dist/ui/layout/governance-card.d.ts +53 -0
  203. package/dist/ui/layout/governance-card.js +134 -0
  204. package/dist/ui/layout/governance-chip.d.ts +38 -0
  205. package/dist/ui/layout/governance-chip.js +64 -0
  206. package/dist/ui/layout/governance-panel.d.ts +85 -0
  207. package/dist/ui/layout/governance-panel.js +358 -0
  208. package/dist/ui/layout/governance-roster.d.ts +65 -0
  209. package/dist/ui/layout/governance-roster.js +275 -0
  210. package/dist/ui/layout/status-detail-layout.js +3 -3
  211. package/dist/ui/layout/status-layout.d.ts +5 -2
  212. package/dist/ui/layout/status-layout.js +10 -6
  213. package/dist/ui/layout/virtual-window.js +11 -0
  214. package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +41 -0
  215. package/dist/ui/overlays/GovernanceBoardOverlay.js +76 -0
  216. package/dist/ui/overlays/GovernanceSettingsPage.d.ts +48 -0
  217. package/dist/ui/overlays/GovernanceSettingsPage.js +93 -0
  218. package/dist/ui/overlays/HelpOverlay.js +4 -3
  219. package/dist/ui/overlays/QuestionOverlay.d.ts +9 -1
  220. package/dist/ui/overlays/QuestionOverlay.js +4 -2
  221. package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
  222. package/dist/ui/overlays/SettingsScreen.js +21 -1
  223. package/dist/ui/overlays/governance-settings.d.ts +79 -0
  224. package/dist/ui/overlays/governance-settings.js +140 -0
  225. package/dist/ui/overlays/model-profile-state.d.ts +6 -1
  226. package/dist/ui/overlays/model-profile-state.js +10 -0
  227. package/dist/ui/palettes.d.ts +17 -0
  228. package/dist/ui/palettes.js +9 -0
  229. package/dist/ui/scrollback/live-preview.js +2 -0
  230. package/dist/ui/scrollback/settled-boundary.js +3 -0
  231. package/dist/ui/shortcuts.js +2 -2
  232. package/dist/ui/theme.d.ts +7 -0
  233. package/dist/ui/theme.js +1 -0
  234. package/dist/ui/transcript-text.js +9 -0
  235. package/dist/ui/use-wheel-routing.js +3 -1
  236. package/package.json +2 -2
@@ -0,0 +1,562 @@
1
+ /**
2
+ * EngineCore - the state, persistence and event plumbing every engine module shares
3
+ * (governance-mode 8.6, 12.1, 14.1).
4
+ *
5
+ * ASCII ONLY (`governance/` is glyph-scanned).
6
+ *
7
+ * WHY MEMORY IS AUTHORITATIVE AND DISK IS A QUEUE. The child tools (`gov_submit_*`,
8
+ * `ledger_*`) are synchronous: a model's tool call returns text, and the executor does not
9
+ * wait for a disk round trip. The run store is asynchronous (atomic rename, fsync, a FIFO
10
+ * mutex). So every mutation lands in memory synchronously and is appended to ONE promise
11
+ * chain that preserves order; `flush()` is awaited at every point where the orchestrator
12
+ * is waiting anyway (end of a stage, start, close). The event log stays the truth: the
13
+ * engine owns the sequence counter and stamps each event BEFORE the write resolves, which
14
+ * is also what lets a ledger entry carry the same `seq` as the event that produced it
15
+ * (replay equivalence, I-G2).
16
+ *
17
+ * LEDGER MUTATIONS GO THROUGH `ledgerOp` AND NOWHERE ELSE (I-G1 / I-G7): one function that
18
+ * logs the op, applies it with the pure ledger, and reports the entries it booked. Nothing
19
+ * else in the tree touches `core.ledger`.
20
+ */
21
+ import { GOV_LIMITS, GOV_SCHEMA_VERSION } from '../limits.js';
22
+ import { applyOp, replay } from '../rules/ledger-replay.js';
23
+ import { createLedger, labelFor, standings, teamHealth } from '../rules/ledger-state.js';
24
+ import { INSPECTOR_ID } from '../rules/ledger-state.js';
25
+ import { renderLedgerLine } from './report.js';
26
+ import { buildRunDigest } from './run-digest.js';
27
+ const EMIT_MIN_INTERVAL_MS = 250;
28
+ /** Give an op the stored-event seq so replay stamps exactly what the live path stamped. */
29
+ export function stampOp(op, seq) {
30
+ switch (op.op) {
31
+ case 'add_agent':
32
+ return op;
33
+ case 'event':
34
+ return { op: 'event', event: { ...op.event, seq } };
35
+ case 'collective':
36
+ return { op: 'collective', spec: { ...op.spec, seq } };
37
+ case 'incident':
38
+ return {
39
+ op: 'incident',
40
+ events: op.events.map((e) => ({ ...e, seq })),
41
+ ...(op.clawbacks ? { clawbacks: op.clawbacks.map((c) => ({ ...c, seq })) } : {}),
42
+ };
43
+ case 'confirm_escrow':
44
+ return { ...op, seq };
45
+ case 'clawback':
46
+ return { op: 'clawback', request: { ...op.request, seq } };
47
+ case 'reverse':
48
+ return { op: 'reverse', request: { ...op.request, seq } };
49
+ case 'eliminate':
50
+ return { op: 'eliminate', request: { ...op.request, seq } };
51
+ case 'respawn':
52
+ return { op: 'respawn', request: { ...op.request, seq } };
53
+ case 'tail_check':
54
+ return { ...op, seq };
55
+ }
56
+ }
57
+ export class EngineCore {
58
+ deps;
59
+ run = null;
60
+ ledger = createLedger([]);
61
+ /** Monotone event counter; the engine owns it (see file header). */
62
+ seq = 0;
63
+ /** True once the user left the run in a state a new `start` may replace. */
64
+ readOnly = false;
65
+ // --- Per-stage scratch, owned by the stage runner, written by the adjudication half ---
66
+ /** WP ids (or `insp`) that received a valid submission during the CURRENT stage run. */
67
+ stageSubmitted = new Set();
68
+ /** `${identity}|${wpId}` -> consecutive validation failures (>= maxValidationFailures => absent). */
69
+ validationFailures = new Map();
70
+ /** One line per result produced this stage (digest body). */
71
+ stageResults = [];
72
+ /** One line per ledger booking this stage (digest ledger section). */
73
+ stageLedgerLines = [];
74
+ /** Warnings to print under the digest (caps, escalations, tripwires). */
75
+ stageWarnings = [];
76
+ /** Set by `ledger_settle`; consumed (and cleared) by the stage runner. */
77
+ inspectorOutcome = null;
78
+ /**
79
+ * Accepted submissions and inspector reports by artifact id (adjudication half). The
80
+ * store is async but a child's tool call is not, so reads during a run come from here;
81
+ * the resume path repopulates it from the artifact store.
82
+ */
83
+ submissionCache = new Map();
84
+ /** Set when an artifact write was refused for the 20 MB budget (spec 12.1). */
85
+ storageExhausted = false;
86
+ /** Mechanical suspicion flags per 0-based phase (ownership violations), for the inspector. */
87
+ phaseFlags = new Map();
88
+ /** `gov_view` requests used per identity (D-9: at most one each). */
89
+ viewRequests = new Map();
90
+ /** Files each identity has written so far, from the team stream (best effort, 6.4). */
91
+ touchedFiles = new Map();
92
+ /** `gov_view` grants / denials per 0-based phase, shown to the inspector at settle. */
93
+ viewLog = new Map();
94
+ /** Set by whoever emitted this stage's `stage_ended` (exactly one per stage). */
95
+ stageEndedEmitted = false;
96
+ /** Set once the lazy retention prune ran for this engine instance. */
97
+ pruned = false;
98
+ /**
99
+ * `/gov pause` is waiting for the running stage to finish (governance-mode-v1-1 V-2).
100
+ * MEMORY ONLY: a request is never persisted, so a restart cannot surprise anyone with a
101
+ * run that "paused itself". Cleared when it is redeemed, cancelled, or the stage ends.
102
+ */
103
+ pauseRequested = false;
104
+ /**
105
+ * The current `paused` status came from `/gov pause`. Memory only, like the request; it
106
+ * lets `gov_dispatch` answer a user pause with the explicit "stop and wait" text while an
107
+ * Esc pause keeps its original refusal. Cleared by resume, a new run and close.
108
+ */
109
+ userPaused = false;
110
+ chain = Promise.resolve();
111
+ listeners = new Set();
112
+ rev = 0;
113
+ lastEmitAt = 0;
114
+ emitTimer = null;
115
+ agentStates = new Map();
116
+ activity = new Map();
117
+ lastDelta = new Map();
118
+ stageAbort = null;
119
+ teamUnsubscribe = null;
120
+ persistError = null;
121
+ constructor(deps) {
122
+ this.deps = deps;
123
+ }
124
+ now() {
125
+ return this.deps.now();
126
+ }
127
+ config() {
128
+ return this.deps.getConfig();
129
+ }
130
+ requireRun() {
131
+ if (this.run === null)
132
+ throw new Error('governance: no active run');
133
+ return this.run;
134
+ }
135
+ wp(id) {
136
+ return this.run?.workPackages[id];
137
+ }
138
+ // -------------------------------------------------------------------------
139
+ // Persistence
140
+ // -------------------------------------------------------------------------
141
+ /** Public queue entry for modules that persist their own files in order with the rest. */
142
+ enqueueTask(job) {
143
+ this.enqueue(job);
144
+ }
145
+ enqueue(job) {
146
+ this.chain = this.chain.then(job).then(() => undefined, (error) => {
147
+ // A write failure must not poison the chain: later writes still run, and the first
148
+ // failure is surfaced to the orchestrator once (never swallowed silently).
149
+ this.persistError ??= error instanceof Error ? error.message : String(error);
150
+ });
151
+ }
152
+ /** Append one event to the truth log. Returns the seq it was stamped with. */
153
+ log(type, actor, payload) {
154
+ this.seq += 1;
155
+ const seq = this.seq;
156
+ const runId = this.run?.runId;
157
+ if (runId === undefined)
158
+ return seq;
159
+ const at = this.now();
160
+ this.enqueue(() => this.deps.store.appendEvent(runId, { type, actor, payload, at, seq }));
161
+ if (this.run)
162
+ this.run.seq = seq;
163
+ return seq;
164
+ }
165
+ /** Queue checkpoint writes for the current run and ledger. */
166
+ checkpoint() {
167
+ const run = this.run;
168
+ if (run === null)
169
+ return;
170
+ run.updatedAt = this.now();
171
+ run.seq = this.seq;
172
+ const runCopy = structuredClone(run);
173
+ // Stamped with the TAIL seq (not the last ledger op's), or the store would report
174
+ // `needsReplay` forever after any non-ledger event.
175
+ const ledgerCopy = structuredClone({ ...this.ledger, seq: this.seq });
176
+ // The `/gov runs` digest rides this write, from the same copies (governance-mode-v1-1
177
+ // V-6): no extra disk read, and it cannot disagree with the checkpoint it travels with.
178
+ const digest = buildRunDigest(runCopy, ledgerCopy);
179
+ this.enqueue(async () => {
180
+ await this.deps.store.writeRun(runCopy, digest);
181
+ await this.deps.store.writeLedger(runCopy.runId, ledgerCopy);
182
+ });
183
+ }
184
+ /** Queue a projection file write (`board.md`, reports, scoreboard). */
185
+ writeFile(rel, text) {
186
+ const runId = this.run?.runId;
187
+ if (runId === undefined)
188
+ return;
189
+ this.enqueue(() => this.deps.store.writeRunFile(runId, rel, text));
190
+ }
191
+ exportScoreboard() {
192
+ const runId = this.run?.runId;
193
+ if (runId === undefined)
194
+ return;
195
+ const ledger = structuredClone(this.ledger);
196
+ this.enqueue(() => this.deps.store.exportScoreboard(runId, ledger));
197
+ }
198
+ /** Wait for every queued write. Returns a note when a write failed (and clears it). */
199
+ async flush() {
200
+ await this.chain;
201
+ const note = this.persistError;
202
+ this.persistError = null;
203
+ return note === null ? null : `WARNING: some governance records failed to save (${note}).`;
204
+ }
205
+ // -------------------------------------------------------------------------
206
+ // Ledger (the ONLY mutation path)
207
+ // -------------------------------------------------------------------------
208
+ ledgerOp(op, actor = 'engine') {
209
+ const seq = this.log('ledger', actor, { op: stampOp(op, this.seq + 1) });
210
+ const stamped = stampOp(op, seq);
211
+ this.ledger = applyOp(this.ledger, stamped);
212
+ const booked = this.entriesAt(seq);
213
+ for (const b of booked)
214
+ this.noteDelta(b);
215
+ return { seq, booked };
216
+ }
217
+ /** Append ledger lines for a digest (call after `ledgerOp`). */
218
+ noteBooked(booked) {
219
+ for (const b of booked) {
220
+ if (typeof b.entry.code !== 'string' || b.entry.delta === 0)
221
+ continue;
222
+ this.stageLedgerLines.push(renderLedgerLine(b.entry, b.agentId));
223
+ }
224
+ }
225
+ resetStageScratch() {
226
+ this.stageSubmitted = new Set();
227
+ this.stageResults = [];
228
+ this.stageLedgerLines = [];
229
+ this.stageWarnings = [];
230
+ this.inspectorOutcome = null;
231
+ this.stageEndedEmitted = false;
232
+ this.touchedFiles.clear();
233
+ }
234
+ entriesAt(seq) {
235
+ const out = [];
236
+ for (const agent of Object.values(this.ledger.agents)) {
237
+ for (const entry of agent.history) {
238
+ if (entry.seq === seq)
239
+ out.push({ agentId: agent.id, entry });
240
+ }
241
+ }
242
+ return out;
243
+ }
244
+ noteDelta(b) {
245
+ if (typeof b.entry.code !== 'string' || b.entry.delta === 0)
246
+ return;
247
+ this.lastDelta.set(b.agentId, {
248
+ delta: b.entry.delta,
249
+ code: b.entry.code,
250
+ at: b.entry.at,
251
+ provisional: b.entry.settlement === 'provisional',
252
+ });
253
+ }
254
+ standingRows() {
255
+ return standings(this.ledger);
256
+ }
257
+ health() {
258
+ return teamHealth(this.ledger);
259
+ }
260
+ /** Rebuild the ledger from an op log (used by `RunStore.load` replay). */
261
+ static replayLedger(events, tailSeq) {
262
+ const ops = events
263
+ .filter((e) => e.type === 'ledger' && typeof e.payload.op === 'object' && e.payload.op)
264
+ .sort((a, b) => a.seq - b.seq)
265
+ .map((e) => e.payload.op);
266
+ return { ...replay(createLedger([]), ops), seq: tailSeq };
267
+ }
268
+ // -------------------------------------------------------------------------
269
+ // Agent state, activity, team stream
270
+ // -------------------------------------------------------------------------
271
+ setAgentState(id, state) {
272
+ this.agentStates.set(id, state);
273
+ }
274
+ agentState(id) {
275
+ return this.agentStates.get(id);
276
+ }
277
+ /** Subscribe to the team stream for the duration of the engine (activity lines). */
278
+ attachTeam() {
279
+ this.teamUnsubscribe?.();
280
+ this.teamUnsubscribe = this.deps.runtime.subscribe((event) => {
281
+ if (event.type !== 'agent_update')
282
+ return;
283
+ const run = event.run;
284
+ if (this.run === null)
285
+ return;
286
+ this.activity.set(run.label, run.activity ?? run.lastTool ?? '');
287
+ if (run.filesTouched.length > 0)
288
+ this.touchedFiles.set(run.label, [...run.filesTouched]);
289
+ const phase = run.phase;
290
+ if (phase === 'queued')
291
+ this.setAgentState(run.label, 'queued');
292
+ else if (phase === 'done') {
293
+ if (this.agentStates.get(run.label) !== 'submitted')
294
+ this.setAgentState(run.label, 'idle');
295
+ }
296
+ else if (phase === 'failed' || phase === 'aborted')
297
+ this.setAgentState(run.label, 'failed');
298
+ else
299
+ this.setAgentState(run.label, 'working');
300
+ this.scheduleEmit();
301
+ });
302
+ }
303
+ detachTeam() {
304
+ this.teamUnsubscribe?.();
305
+ this.teamUnsubscribe = null;
306
+ }
307
+ clearActivity() {
308
+ this.activity.clear();
309
+ }
310
+ // -------------------------------------------------------------------------
311
+ // Stage abort
312
+ // -------------------------------------------------------------------------
313
+ beginStage(external) {
314
+ const controller = new AbortController();
315
+ if (external) {
316
+ if (external.aborted)
317
+ controller.abort();
318
+ else
319
+ external.addEventListener('abort', () => controller.abort(), { once: true });
320
+ }
321
+ this.stageAbort = controller;
322
+ return controller;
323
+ }
324
+ endStage() {
325
+ this.stageAbort = null;
326
+ // A request survives only while the run can still be paused by it: after an Esc abort
327
+ // (`paused`) it would otherwise fire the moment the user resumes.
328
+ if (this.run?.status !== 'executing')
329
+ this.pauseRequested = false;
330
+ }
331
+ isStageRunning() {
332
+ return this.run?.stage?.status === 'running' && this.stageAbort !== null;
333
+ }
334
+ /**
335
+ * A `gov_dispatch` call is in flight from `beginStage` until `endStage`: wider than
336
+ * `isStageRunning`, which is false while the stage is being set up and again while it
337
+ * writes its last checkpoint. A pause requested in either gap must wait, not fire.
338
+ */
339
+ isStageActive() {
340
+ return this.stageAbort !== null;
341
+ }
342
+ abortStage() {
343
+ this.stageAbort?.abort();
344
+ this.deps.runtime.abortAll();
345
+ }
346
+ // -------------------------------------------------------------------------
347
+ // Events and snapshot
348
+ // -------------------------------------------------------------------------
349
+ subscribe(listener) {
350
+ this.listeners.add(listener);
351
+ return () => {
352
+ this.listeners.delete(listener);
353
+ };
354
+ }
355
+ emit(build) {
356
+ const snapshot = this.snapshot();
357
+ if (snapshot === null)
358
+ return;
359
+ this.cancelPendingEmit();
360
+ this.lastEmitAt = this.now();
361
+ const event = build(snapshot);
362
+ for (const listener of [...this.listeners]) {
363
+ try {
364
+ listener(event);
365
+ }
366
+ catch {
367
+ // A UI listener must never be able to break a governed run.
368
+ }
369
+ }
370
+ }
371
+ /** Coalesce chatty updates (activity lines) to one snapshot per interval (8.6). */
372
+ scheduleEmit() {
373
+ if (this.emitTimer !== null)
374
+ return;
375
+ const wait = Math.max(0, EMIT_MIN_INTERVAL_MS - (this.now() - this.lastEmitAt));
376
+ this.emitTimer = setTimeout(() => {
377
+ this.emitTimer = null;
378
+ this.emit((snapshot) => ({ type: 'agent_state', snapshot }));
379
+ }, wait);
380
+ this.emitTimer.unref?.();
381
+ }
382
+ cancelPendingEmit() {
383
+ if (this.emitTimer === null)
384
+ return;
385
+ clearTimeout(this.emitTimer);
386
+ this.emitTimer = null;
387
+ }
388
+ dispose() {
389
+ this.cancelPendingEmit();
390
+ this.detachTeam();
391
+ this.listeners.clear();
392
+ }
393
+ snapshot() {
394
+ const run = this.run;
395
+ if (run === null)
396
+ return null;
397
+ this.rev += 1;
398
+ const rows = this.standingRows();
399
+ const reworkWp = run.wpOrder
400
+ .map((id) => run.workPackages[id])
401
+ .find((w) => w !== undefined && w.reworkCount > 0);
402
+ const phaseIndex = currentDisplayPhase(run);
403
+ return {
404
+ runId: run.runId,
405
+ goal: run.goal,
406
+ status: run.status,
407
+ phase: phaseIndex + 1,
408
+ phaseCount: run.phases.length,
409
+ stage: run.stage?.kind ?? null,
410
+ stageRunning: run.stage?.status === 'running',
411
+ startedAt: run.createdAt,
412
+ pipeline: buildPipeline(run, phaseIndex),
413
+ roster: this.rosterRows(rows),
414
+ health: this.health(),
415
+ recent: this.recent(),
416
+ usage: { ...run.usage },
417
+ budgetTokens: run.limits.budgetTokens,
418
+ alerts: this.alerts(run),
419
+ reworkNote: reworkWp
420
+ ? `rework ${reworkWp.reworkCount}/${run.limits.maxRework} (${reworkWp.id})`
421
+ : null,
422
+ // Present only when true, so a snapshot without a pending pause is byte-identical
423
+ // to what it was before the field existed.
424
+ ...(this.pauseRequested ? { pauseRequested: true } : {}),
425
+ rev: this.rev,
426
+ };
427
+ }
428
+ rosterRows(rows) {
429
+ const out = rows.map((r) => ({
430
+ id: r.id,
431
+ role: r.role,
432
+ generation: r.generation,
433
+ state: r.status === 'eliminated' ? 'eliminated' : (this.agentStates.get(r.id) ?? 'idle'),
434
+ wpId: this.wpOf(r.id),
435
+ activity: r.status === 'eliminated' ? '' : (this.activity.get(r.id) ?? ''),
436
+ confirmed: r.confirmed,
437
+ provisional: r.provisional,
438
+ label: labelFor(r.confirmed),
439
+ lastDelta: this.lastDelta.get(r.id) ?? null,
440
+ replacedBy: r.replacedBy,
441
+ }));
442
+ out.push({
443
+ id: INSPECTOR_ID,
444
+ role: 'INSP',
445
+ generation: 0,
446
+ state: this.agentStates.get(INSPECTOR_ID) ?? 'idle',
447
+ wpId: null,
448
+ activity: this.activity.get(INSPECTOR_ID) ?? '',
449
+ confirmed: null,
450
+ provisional: 0,
451
+ label: 'NOT_SCORED',
452
+ lastDelta: null,
453
+ replacedBy: null,
454
+ });
455
+ return out;
456
+ }
457
+ wpOf(agentId) {
458
+ const run = this.run;
459
+ if (run === null)
460
+ return null;
461
+ for (const id of run.wpOrder) {
462
+ const wp = run.workPackages[id];
463
+ if (wp && (wp.assignee === agentId || wp.reviewer === agentId || wp.tester === agentId)) {
464
+ if (wp.status !== 'integrated' && wp.status !== 'dropped')
465
+ return wp.id;
466
+ }
467
+ }
468
+ return null;
469
+ }
470
+ recent() {
471
+ const all = [];
472
+ for (const agent of Object.values(this.ledger.agents)) {
473
+ for (const e of agent.history.slice(-6)) {
474
+ if (typeof e.code !== 'string' || e.delta === 0)
475
+ continue;
476
+ all.push({
477
+ seq: e.seq,
478
+ at: e.at,
479
+ agentId: agent.id,
480
+ code: e.code,
481
+ delta: e.delta,
482
+ provisional: e.settlement === 'provisional',
483
+ reason: e.reason,
484
+ });
485
+ }
486
+ }
487
+ return all
488
+ .sort((a, b) => a.seq - b.seq)
489
+ .slice(-6)
490
+ .map(({ seq: _seq, ...row }) => row);
491
+ }
492
+ alerts(run) {
493
+ const out = [];
494
+ const open = run.escalations.filter((e) => e.status === 'open');
495
+ if (open.length > 0)
496
+ out.push(`${open.length} escalation(s) need the user`);
497
+ if (run.status === 'paused')
498
+ out.push('run paused - gov_run resume');
499
+ const budget = run.limits.budgetTokens;
500
+ if (budget > 0) {
501
+ const used = run.usage.inputTokens + run.usage.outputTokens;
502
+ if (used >= budget * GOV_LIMITS.budgetWarnFraction)
503
+ out.push('team token budget nearly used');
504
+ }
505
+ const health = this.health();
506
+ if (health !== null && health < GOV_LIMITS.healthWarnThreshold)
507
+ out.push('team health low');
508
+ return out;
509
+ }
510
+ static schemaVersion() {
511
+ return GOV_SCHEMA_VERSION;
512
+ }
513
+ }
514
+ /** The phase to display: the first not-integrated one, else the last. */
515
+ export function currentDisplayPhase(run) {
516
+ if (run.phases.length === 0)
517
+ return 0;
518
+ const open = run.phases.find((p) => !p.integrated);
519
+ return open ? open.index : run.phases.length - 1;
520
+ }
521
+ const PIPELINE_ORDER = ['dev', 'review', 'test', 'settle', 'integrate'];
522
+ function stageDone(run, phaseIndex, kind) {
523
+ const phase = run.phases[phaseIndex];
524
+ if (phase === undefined)
525
+ return false;
526
+ if (phase.integrated)
527
+ return true;
528
+ const wps = phase.wpIds
529
+ .map((id) => run.workPackages[id])
530
+ .filter((w) => w !== undefined && w.status !== 'dropped');
531
+ const none = (...statuses) => wps.every((w) => !statuses.includes(w.status));
532
+ switch (kind) {
533
+ case 'dev':
534
+ return none('pending', 'dev_running', 'rework');
535
+ case 'review':
536
+ return stageDone(run, phaseIndex, 'dev') && none('awaiting_review', 'review_running');
537
+ case 'test':
538
+ return stageDone(run, phaseIndex, 'review') && none('awaiting_test', 'test_running');
539
+ case 'settle':
540
+ return phase.settled;
541
+ case 'integrate':
542
+ return phase.integrated;
543
+ default:
544
+ return false;
545
+ }
546
+ }
547
+ export function buildPipeline(run, phaseIndex) {
548
+ const steps = [{ key: 'intake', state: 'done' }];
549
+ const running = run.stage?.status === 'running' ? run.stage.kind : null;
550
+ let activeAssigned = false;
551
+ for (const kind of PIPELINE_ORDER) {
552
+ const done = stageDone(run, phaseIndex, kind);
553
+ let state = done ? 'done' : 'pending';
554
+ const isRunning = running === kind || (kind === 'integrate' && running === 'fix');
555
+ if (!done && !activeAssigned && (isRunning || running === null)) {
556
+ state = 'active';
557
+ activeAssigned = true;
558
+ }
559
+ steps.push({ key: kind, state });
560
+ }
561
+ return steps;
562
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * GovernanceEngine - the facade the tools, the wiring and the UI call
3
+ * (governance-mode 14.1).
4
+ *
5
+ * ASCII ONLY (`governance/` is glyph-scanned).
6
+ *
7
+ * Thin on purpose: the work lives in `lifecycle.ts` (run state), `stage-runner.ts`
8
+ * (dispatch), `submissions.ts` / `inspector.ts` (what children submit) and `engine-core.ts`
9
+ * (state, persistence, events). This class composes them and adds the read-side child
10
+ * entry points (`gov_read`, `gov_view`, `ledger_standings`, scan) that need nothing but the
11
+ * visibility matrix and the submission cache.
12
+ *
13
+ * NOTHING HERE THROWS TO THE MODEL. Every entry point converts an unexpected failure into a
14
+ * `NOT ACCEPTED: internal error` result: a thrown exception inside a tool call becomes an
15
+ * error result that invites an identical retry, and a governed run has no way to explain
16
+ * "the engine crashed" to a child in any other voice.
17
+ */
18
+ import type { GovernanceEngineApi, GovernanceEventListener, GovernanceSnapshot, Identity, ToolText } from '../types.js';
19
+ import { EngineCore, type EngineDeps } from './engine-core.js';
20
+ import { type PauseOutcome } from './pause.js';
21
+ export declare class GovernanceEngine implements GovernanceEngineApi {
22
+ private readonly core;
23
+ constructor(deps: EngineDeps);
24
+ /** Exposed for tests and the wiring's persistence helpers; not part of the tool surface. */
25
+ get internals(): EngineCore;
26
+ start(input: unknown, signal?: AbortSignal): Promise<ToolText>;
27
+ status(): ToolText;
28
+ resume(runId?: string): Promise<ToolText>;
29
+ dispatchStage(input: unknown, signal?: AbortSignal): Promise<ToolText>;
30
+ close(summary?: string): Promise<ToolText>;
31
+ abandon(reason: string): Promise<ToolText>;
32
+ resolve(input: unknown): Promise<ToolText>;
33
+ private guard;
34
+ snapshot(): GovernanceSnapshot | null;
35
+ isStageRunning(): boolean;
36
+ abortStage(): void;
37
+ /**
38
+ * `/gov pause`: stop at the next clean boundary (governance-mode-v1-1 6). A human
39
+ * decision, so it is NOT on `GovernanceEngineApi` and no tool can reach it.
40
+ */
41
+ requestPause(): Promise<PauseOutcome>;
42
+ /** `/gov pause cancel`: true when a pending request was withdrawn. */
43
+ cancelPause(): boolean;
44
+ subscribe(listener: GovernanceEventListener): () => void;
45
+ /**
46
+ * Stop timers and release the run lock WITHOUT closing the run: quitting the CLI leaves
47
+ * the run resumable (D-12), exactly as an Esc does.
48
+ */
49
+ dispose(): void;
50
+ submitWork(identity: Identity, input: unknown): ToolText;
51
+ submitReview(identity: Identity, input: unknown): ToolText;
52
+ submitTest(identity: Identity, input: unknown): ToolText;
53
+ inspectorApply(input: unknown): ToolText;
54
+ inspectorRule(input: unknown): ToolText;
55
+ inspectorSettle(input: unknown): ToolText;
56
+ private child;
57
+ standings(): ToolText;
58
+ private refs;
59
+ private deny;
60
+ readArtifact(identity: Identity, input: unknown): ToolText;
61
+ private doRead;
62
+ requestView(identity: Identity, input: unknown): ToolText;
63
+ private doView;
64
+ private failingFiles;
65
+ private scopedFindings;
66
+ scan(_identity: Identity, input: unknown): ToolText | Promise<ToolText>;
67
+ }
68
+ export declare function createGovernanceEngine(deps: EngineDeps): GovernanceEngine;