@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
@@ -9,6 +9,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
9
9
  import { dirname, isAbsolute, join, resolve } from 'node:path';
10
10
  import { getSessionsDir } from '../config/store.js';
11
11
  import { stripEphemeralRuns } from '../team/settled.js';
12
+ import { sanitizeInvocationCard } from '../agent/invocation-card.js';
12
13
  import { validateSession } from './validate-session.js';
13
14
  import { validateCompactionIdentity, verifyCompactionIdentity, } from '../compaction/memory-identity.js';
14
15
  import { getLogger } from '../logging/logger.js';
@@ -227,6 +228,15 @@ export function normalizeLoadedEntries(entries) {
227
228
  // THE ID IS KEPT: `restoreEntries` splices these positions into the
228
229
  // live transcript, and a fresh id here would collide with the next
229
230
  // `e<n>` the reducer allocates.
231
+ if (entry.kind === 'user' && 'invocation' in entry) {
232
+ // THE EIGHTH CLAUSE (tui-invocation-cards I-7). A session file is user-editable
233
+ // and `shape()` ignores undeclared fields, so a card arrives unchecked. Repair
234
+ // what can be repaired; a card that cannot be is dropped and the entry degrades
235
+ // to a plain user message - a corrupt card must never cost the session.
236
+ const { invocation: stored, ...plain } = entry;
237
+ const card = sanitizeInvocationCard(stored);
238
+ return card ? { ...plain, invocation: card } : plain;
239
+ }
230
240
  if (entry.kind === 'queued') {
231
241
  const downgraded = {
232
242
  id: entry.id,
@@ -4,6 +4,7 @@ import { TODO_LIMITS } from '../todo/limits.js';
4
4
  import { acceptanceSummary, normalizeAcceptance } from '../todo/acceptance.js';
5
5
  import { validateCompactionIdentity } from '../compaction/memory-identity.js';
6
6
  import { MemoryError } from '../compaction/memory.js';
7
+ import { GOVERNANCE_CARD_FALLBACK_TEXT, normalizeGovernanceCard } from '../governance/card-normalize.js';
7
8
  function fail(path, expected) {
8
9
  throw new Error(`Invalid session file: ${path} must be ${expected}.`);
9
10
  }
@@ -144,8 +145,16 @@ const subagent = shape({
144
145
  retry: optional(shape({ attempt: number, maxRetries: number })),
145
146
  });
146
147
  const ENTRY_FIELDS = {
147
- user: { text: string },
148
- queued: { text: string, queueId: optional(nonempty) },
148
+ // `invocation` is declared for self-description only: `shape()` ignores undeclared
149
+ // fields, and `normalizeLoadedEntries` repairs or drops a bad card (tui-invocation-cards).
150
+ user: { text: string, invocation: optional(() => { }) },
151
+ // A GOVERNANCE CARD IS HISTORY, so it is validated LENIENTLY (governance-mode 10.4):
152
+ // unknown fields are ignored (`shape` only reads the declared ones) and `card` is not
153
+ // shape-checked here at all - `sanitizeGovernanceEntries` below repairs a card it can
154
+ // and degrades one it cannot to a one-line notice, so a corrupt footnote never costs
155
+ // the session. `at` is advisory.
156
+ governance: { at: optional(number), card: () => { } },
157
+ queued: { text: string, queueId: optional(nonempty), invocation: optional(() => { }) },
149
158
  notice: { text: string, level: enumeration('info', 'warn', 'error') },
150
159
  assistant: {
151
160
  text: string, thinking: optional(string), thinkingOpen: boolean, streaming: boolean,
@@ -282,9 +291,33 @@ export function validateSession(value) {
282
291
  if (source.acceptance !== undefined) {
283
292
  prepared.acceptance = normalizeAcceptance(source.acceptance, [], { enabled: true }).items;
284
293
  }
285
- prepared.entries = sanitizeEntryAcceptance(prepared.entries);
294
+ prepared.entries = sanitizeGovernanceEntries(sanitizeEntryAcceptance(prepared.entries));
286
295
  return prepared;
287
296
  }
297
+ /**
298
+ * Repair every governance card the file carries, or degrade it to a one-line notice.
299
+ *
300
+ * The card is rebuilt from `normalizeGovernanceCard`'s output so only known fields survive
301
+ * (a hand-edited file cannot smuggle a 10 000-row table into the transcript), and a value
302
+ * with nothing renderable becomes a `notice` entry that keeps the entry's id: ids must stay
303
+ * unique and in order, and the transcript should say that something was here.
304
+ */
305
+ function sanitizeGovernanceEntries(entries) {
306
+ if (!Array.isArray(entries))
307
+ return entries;
308
+ return entries.map((entry) => {
309
+ if (!entry || typeof entry !== 'object')
310
+ return entry;
311
+ const record = entry;
312
+ if (record.kind !== 'governance')
313
+ return entry;
314
+ const card = normalizeGovernanceCard(record.card);
315
+ if (card === null) {
316
+ return { id: record.id, kind: 'notice', level: 'info', text: GOVERNANCE_CARD_FALLBACK_TEXT };
317
+ }
318
+ return { id: record.id, kind: 'governance', at: typeof record.at === 'number' ? record.at : 0, card };
319
+ });
320
+ }
288
321
  /**
289
322
  * Drop the acceptance rows a persisted card cannot use, keeping every usable one.
290
323
  *
@@ -106,6 +106,19 @@ export interface ForcedPlan {
106
106
  * can apply, because only it can read the live config.
107
107
  */
108
108
  export declare function planForced(args: SubagentCommandArgs, team: TeamConfig): ForcedPlan;
109
+ /**
110
+ * The parenthesised disclosure sentence of the plan notice - why the fan-out is not what
111
+ * the user typed - or `null` when nothing was narrowed.
112
+ *
113
+ * SHARED BY `planNotice` AND `forcedCardNote` (tui-invocation-cards RV-4): the notice and
114
+ * the transcript card must word the same fact the same way, so there is exactly one
115
+ * place that decides it.
116
+ *
117
+ * TWO CEILINGS, TWO SENTENCES. `forcedPartsMax` bounds what one argument may carry and
118
+ * `team.maxSubagents` bounds what may be dispatched; naming the wrong one sends the user
119
+ * to change a number that is not the reason.
120
+ */
121
+ export declare function clampDisclosure(planned: ForcedPlan, team: TeamConfig): string | null;
109
122
  /**
110
123
  * The one line that says what the NEXT turn will do - a plan, not a result.
111
124
  *
@@ -114,3 +127,9 @@ export declare function planForced(args: SubagentCommandArgs, team: TeamConfig):
114
127
  * said "4 subagents started" would be a promise the command cannot keep.
115
128
  */
116
129
  export declare function planNotice(planned: ForcedPlan, team: TeamConfig): string;
130
+ /**
131
+ * The card's `note`: the same disclosure, as a sentence. `undefined` when nothing was
132
+ * narrowed - the "Esc stops the turn" follow-up is an operating hint the status bar
133
+ * carries, not a fact about this invocation.
134
+ */
135
+ export declare function forcedCardNote(planned: ForcedPlan, team: TeamConfig): string | undefined;
@@ -123,34 +123,56 @@ export function planForced(args, team) {
123
123
  };
124
124
  }
125
125
  /**
126
- * The one line that says what the NEXT turn will do - a plan, not a result.
126
+ * The parenthesised disclosure sentence of the plan notice - why the fan-out is not what
127
+ * the user typed - or `null` when nothing was narrowed.
127
128
  *
128
- * IT IS PRINTED BEFORE THE TURN IS SUBMITTED and it never claims children
129
- * started: forcing is instruction-level for the first turn, and a notice that
130
- * said "4 subagents started" would be a promise the command cannot keep.
129
+ * SHARED BY `planNotice` AND `forcedCardNote` (tui-invocation-cards RV-4): the notice and
130
+ * the transcript card must word the same fact the same way, so there is exactly one
131
+ * place that decides it.
132
+ *
133
+ * TWO CEILINGS, TWO SENTENCES. `forcedPartsMax` bounds what one argument may carry and
134
+ * `team.maxSubagents` bounds what may be dispatched; naming the wrong one sends the user
135
+ * to change a number that is not the reason.
131
136
  */
132
- export function planNotice(planned, team) {
133
- const noun = planned.count === 1 ? 'subagent' : 'subagents';
134
- const head = `Forcing a fan-out of ${planned.count} ${noun}`;
135
- const followUp = `${head} (max ${team.maxSubagents}) - the next turn dispatches them. `
136
- + 'Esc stops the turn.';
137
- // TWO CEILINGS, TWO SENTENCES. `forcedPartsMax` bounds what one argument may
138
- // carry and `team.maxSubagents` bounds what may be dispatched; naming the wrong
139
- // one sends the user to change a number that is not the reason.
137
+ export function clampDisclosure(planned, team) {
140
138
  if (planned.parts.length > 0) {
141
139
  if (planned.parts.length < planned.partsAdmitted) {
142
- return `${head} (asked for ${planned.partsRequested} parts; `
143
- + `team.maxSubagents is ${team.maxSubagents}).`;
140
+ return `asked for ${planned.partsRequested} parts; team.maxSubagents is ${team.maxSubagents}`;
144
141
  }
145
142
  if (planned.partsAdmitted < planned.partsRequested) {
146
- return `${head} (asked for ${planned.partsRequested} parts, `
147
- + `the first ${planned.parts.length} are used).`;
143
+ return `asked for ${planned.partsRequested} parts, the first ${planned.parts.length} are used`;
148
144
  }
149
- return followUp;
145
+ return null;
150
146
  }
151
147
  if (planned.requested > planned.count) {
152
- return `${head} (asked for ${planned.requested}; `
153
- + `team.maxSubagents is ${team.maxSubagents}).`;
148
+ return `asked for ${planned.requested}; team.maxSubagents is ${team.maxSubagents}`;
154
149
  }
155
- return followUp;
150
+ return null;
151
+ }
152
+ /**
153
+ * The one line that says what the NEXT turn will do - a plan, not a result.
154
+ *
155
+ * IT IS PRINTED BEFORE THE TURN IS SUBMITTED and it never claims children
156
+ * started: forcing is instruction-level for the first turn, and a notice that
157
+ * said "4 subagents started" would be a promise the command cannot keep.
158
+ */
159
+ export function planNotice(planned, team) {
160
+ const noun = planned.count === 1 ? 'subagent' : 'subagents';
161
+ const head = `Forcing a fan-out of ${planned.count} ${noun}`;
162
+ const disclosure = clampDisclosure(planned, team);
163
+ if (disclosure !== null)
164
+ return `${head} (${disclosure}).`;
165
+ return `${head} (max ${team.maxSubagents}) - the next turn dispatches them. `
166
+ + 'Esc stops the turn.';
167
+ }
168
+ /**
169
+ * The card's `note`: the same disclosure, as a sentence. `undefined` when nothing was
170
+ * narrowed - the "Esc stops the turn" follow-up is an operating hint the status bar
171
+ * carries, not a fact about this invocation.
172
+ */
173
+ export function forcedCardNote(planned, team) {
174
+ const disclosure = clampDisclosure(planned, team);
175
+ if (disclosure === null)
176
+ return undefined;
177
+ return `${disclosure.charAt(0).toUpperCase()}${disclosure.slice(1)}.`;
156
178
  }
@@ -98,6 +98,14 @@ export interface TeamRuntimeDeps {
98
98
  * `TeamOverseer`), so a mid-dispatch `/fast off` stops the next look.
99
99
  */
100
100
  overseer?: OverseerProvider;
101
+ /**
102
+ * Governance hooks, passed straight through to `SubagentDeps` (governance-mode 13.2).
103
+ * ABSENT outside a governed session, which leaves every child built as before.
104
+ */
105
+ extraToolsFor?: SubagentDeps['extraToolsFor'];
106
+ narrowToolsFor?: SubagentDeps['narrowToolsFor'];
107
+ governanceRoleBlockFor?: SubagentDeps['governanceRoleBlockFor'];
108
+ extraPolicyExempt?: SubagentDeps['extraPolicyExempt'];
101
109
  }
102
110
  export declare class TeamRuntime {
103
111
  private readonly deps;
@@ -512,6 +512,15 @@ export class TeamRuntime {
512
512
  ...(this.deps.contextManagerFor
513
513
  ? { contextManagerFor: this.deps.contextManagerFor }
514
514
  : {}),
515
+ // Site two of two for the four governance hooks, with the same silent failure as
516
+ // the lines above: declared on `TeamRuntimeDeps` and not forwarded here, a governed
517
+ // child would run with no role tools and the dispatch would simply never submit.
518
+ ...(this.deps.extraToolsFor ? { extraToolsFor: this.deps.extraToolsFor } : {}),
519
+ ...(this.deps.narrowToolsFor ? { narrowToolsFor: this.deps.narrowToolsFor } : {}),
520
+ ...(this.deps.governanceRoleBlockFor
521
+ ? { governanceRoleBlockFor: this.deps.governanceRoleBlockFor }
522
+ : {}),
523
+ ...(this.deps.extraPolicyExempt ? { extraPolicyExempt: this.deps.extraPolicyExempt } : {}),
515
524
  // Same conditional-spread rule: a dispatch without a supervisor builds
516
525
  // the exact pre-feature `SubagentDeps`, and the child's idle watchdog
517
526
  // keeps its un-lengthened ceiling (team-overseer §3.6).
@@ -138,6 +138,29 @@ export interface SubagentDeps {
138
138
  toolGateFor?: (label: string) => ToolGate;
139
139
  /** Added to each gated tool's executor timeout; see `gatedToolTimeouts`. */
140
140
  decisionBudgetMs?: number;
141
+ /**
142
+ * Governance hooks (governance-mode 13.2 / 14.3). ALL FOUR ABSENT outside a governed
143
+ * dispatch, and `buildSubagentTools` then produces exactly the array it always did.
144
+ *
145
+ * `extraToolsFor` returns the role tools this child gets (`gov_submit_*`, `ledger_*`,
146
+ * ...). They travel through the `teamTools` slot of the factory, next to the comm
147
+ * tools, so the plan gate and the ceiling treat them like `team_send`.
148
+ *
149
+ * `narrowToolsFor` runs on the factory's RESULT: it is where the inspector loses its
150
+ * write tools and gets the restricted `bash`, and where the governance-store tripwire is
151
+ * wrapped around the read-class tools. It is a separate hook (not folded into
152
+ * `extraToolsFor`) because it must see the finished built-ins, while the role tools must
153
+ * enter the factory. Both are closures supplied by the host, which keeps the dependency
154
+ * direction `governance/ -> team/` (this module never imports governance code).
155
+ *
156
+ * `extraPolicyExempt` is merged with `TEAM_SUBAGENT_TOOL_NAMES` for the skills ceiling
157
+ * (7.1 rule 6). Absent, `policyExempt` stays the SAME Set object as before, so the
158
+ * closed state is provable by identity.
159
+ */
160
+ extraToolsFor?: (spec: SubagentSpec) => AgentTool[];
161
+ narrowToolsFor?: (spec: SubagentSpec, tools: AgentTool[]) => AgentTool[];
162
+ governanceRoleBlockFor?: (spec: SubagentSpec) => string;
163
+ extraPolicyExempt?: ReadonlySet<string>;
141
164
  }
142
165
  export interface SubagentHooks {
143
166
  /** Coalesced at the source (§5.2); phase transitions always pass. */
@@ -77,7 +77,8 @@ export function buildSubagentTools(spec, deps, getAgent) {
77
77
  : TEAM_LIMITS.waitMaxSeconds * 1000,
78
78
  }),
79
79
  ];
80
- const tools = createBuiltinTools({
80
+ const extras = deps.extraToolsFor?.(spec) ?? [];
81
+ const built = createBuiltinTools({
81
82
  getCwd: deps.getCwd,
82
83
  confirmTools: deps.confirmTools,
83
84
  // THE CHILD ASKS THE CALLER TOO (section 2.3.7). Without this the approval
@@ -145,7 +146,9 @@ export function buildSubagentTools(spec, deps, getAgent) {
145
146
  // yields an empty array: a silent default is not a decision anyone can find
146
147
  // later.
147
148
  todoTools: [],
148
- teamTools: commTools,
149
+ // Role tools ride the same slot as the comm tools (governance-mode 14.3). A spread
150
+ // only when a governed dispatch supplied some, so every other child is unchanged.
151
+ teamTools: extras.length > 0 ? [...commTools, ...extras] : commTools,
149
152
  // `spec.readOnly` forces the plan gate on for this child even in a BUILD
150
153
  // session. TIGHTENING ONLY (D-13): there is no field anywhere that gives a
151
154
  // child more permission than the session it was spawned from. Read LIVE, so
@@ -168,10 +171,13 @@ export function buildSubagentTools(spec, deps, getAgent) {
168
171
  onToolPolicyEvent: (e) => deps.onChildToolPolicyEvent?.({ ...e, label: spec.label }),
169
172
  // The two comm tools are not in the LEAD's registered tool list, so
170
173
  // the ceiling would refuse them and dead-end the channel.
171
- policyExempt: TEAM_SUBAGENT_TOOL_NAMES,
174
+ policyExempt: deps.extraPolicyExempt
175
+ ? new Set([...TEAM_SUBAGENT_TOOL_NAMES, ...deps.extraPolicyExempt])
176
+ : TEAM_SUBAGENT_TOOL_NAMES,
172
177
  }
173
178
  : {}),
174
179
  });
180
+ const tools = deps.narrowToolsFor ? deps.narrowToolsFor(spec, built) : built;
175
181
  // Mail rides on the NEXT tool result of whatever the child does next (D-4), so
176
182
  // every tool it can call has to carry the tail — the comm tools included, or a
177
183
  // child that only ever messages would never receive.
@@ -256,6 +262,11 @@ export function createSubagent(spec, deps, hooks) {
256
262
  peers: deps.bus.peersOf(spec.label),
257
263
  }),
258
264
  ...(fastBlock ? { fastBlock } : {}),
265
+ // The governed role block (governance-mode 6.1), after the subagent block. Spread
266
+ // only when this child is a governed one, so every other child's prompt is unchanged.
267
+ ...(spec.governance && deps.governanceRoleBlockFor
268
+ ? { governanceRoleBlock: deps.governanceRoleBlockFor(spec) }
269
+ : {}),
259
270
  });
260
271
  // LAZY ACCESSORS OVER `agentRef`, WHICH IS ASSIGNED AFTER THIS RETURNS. A
261
272
  // non-lazy read here is `undefined` at construction and the child's estimate
@@ -13,6 +13,7 @@
13
13
  import type { TokenUsage } from '@aragon-agent/core';
14
14
  import type { ActivityArgs } from './activity.js';
15
15
  import type { TimelineEntry } from './timeline.js';
16
+ import type { GovernanceSpecExt } from '../governance/types.js';
16
17
  /** One normalized delegation request. Produced only by `normalizeSubagentSpecs`. */
17
18
  export interface SubagentSpec {
18
19
  /** Slug, unique within the dispatch. Shown in the panel, the report and logs. */
@@ -27,6 +28,11 @@ export interface SubagentSpec {
27
28
  * permission than the session it was spawned from.
28
29
  */
29
30
  readOnly: boolean;
31
+ /**
32
+ * Set only by the governance engine (governance-mode section 8.3). Its presence is what
33
+ * `isGovernedDispatch` reads to keep a governed dispatch out of the transcript.
34
+ */
35
+ governance?: GovernanceSpecExt;
30
36
  }
31
37
  export type SubagentPhase = 'queued' | 'starting' | 'thinking' | 'tool' | 'waiting' | 'done' | 'failed' | 'aborted';
32
38
  /** Live, mutable record of one child. Projected into the UI and the report. */
@@ -250,15 +250,38 @@ export interface BuiltinToolsOptions {
250
250
  * Tool names that skip the CEILING wrapper only (never the plan gate, never
251
251
  * confirmation).
252
252
  *
253
- * It exists for a subagent's two comm tools and for nothing else (§3.11).
254
- * `team_send` / `team_wait` are not in the LEAD's registered tool list, so
255
- * evaluating them against the lead's `allowed-tools` ceiling would refuse them
256
- * and dead-end the channel — the same dead end `SKILL_TOOL_FLOOR` exists to
257
- * prevent one level up. Widening this set to anything that can write to disk
258
- * would turn delegation into a ceiling bypass, which is exactly what §3.11
259
- * re-applies the lead's policy to children to avoid.
253
+ * It has TWO users, and only two (§3.11, governance-mode 7.1 rule 6 / D-22b).
254
+ * First, a subagent's comm tools `team_send` / `team_wait`, which are not in the
255
+ * LEAD's registered tool list, so evaluating them against the lead's
256
+ * `allowed-tools` ceiling would refuse them and dead-end the channel. Second, the
257
+ * governance tools: the Orchestrator's `gov_run` / `gov_dispatch` and a governed
258
+ * child's role tools. Those are exempt for the same reason (a ceiling written
259
+ * before they existed names none of them) and because they never touch the user's
260
+ * workspace: the lead's two only write the host's own governance store, and the
261
+ * agents they dispatch each pass the full ceiling and plan gate for what they do.
262
+ *
263
+ * Widening this set to anything that can write to the user's workspace would turn
264
+ * delegation into a ceiling bypass, which is exactly what §3.11 re-applies the
265
+ * lead's policy to children to avoid. The governance names deliberately do NOT
266
+ * live in `HOST_TOOL_NAMES` / `SKILL_TOOL_FLOOR`: those feed `exec --allow-tool`
267
+ * validation and the `tools` diagnostics, and joining them would change both.
260
268
  */
261
269
  policyExempt?: ReadonlySet<string>;
270
+ /**
271
+ * The Orchestrator's two tools (`gov_run`, `gov_dispatch`), appended AFTER
272
+ * `teamTools` (governance-mode 14.3). Spread conditionally by the controller, so a
273
+ * session without governance builds the identical array.
274
+ */
275
+ governTools?: AgentTool[];
276
+ /**
277
+ * Live read of `{ governOn, mode }` for the governance gate. Never cached: Shift+Tab
278
+ * and `/gov off` flip it mid-run. Omitting it applies no gate, which is what keeps the
279
+ * closed state provable by object identity.
280
+ */
281
+ governGate?: () => {
282
+ governOn: boolean;
283
+ mode: AgentMode;
284
+ };
262
285
  /**
263
286
  * The non-interactive tool policy of `aragon exec`
264
287
  * (cli-integration-surface §4.2). A denied tool is NOT REGISTERED AT ALL, so
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { errorResult, evaluateToolCall, } from '@aragon-agent/core';
12
12
  import { planRefusal, submitPlanRefusal } from '../agent/agent-mode.js';
13
+ import { withGovernanceGate } from '../governance/tools/guard.js';
13
14
  import { makeReadFile, makeWriteFile, makeEditFile, makeListDir } from './fs-tools.js';
14
15
  import { makeGlob, makeGrep } from './search-tools.js';
15
16
  import { makeBash } from './bash-tool.js';
@@ -382,6 +383,12 @@ export function createBuiltinTools(options) {
382
383
  if (options.teamTools && options.teamTools.length > 0) {
383
384
  tools = [...tools, ...options.teamTools];
384
385
  }
386
+ // The Orchestrator's tools, after `task` (governance-mode 14.3). Inside the factory for
387
+ // the reason `teamTools` records: appending later would let the ceiling and the plan
388
+ // gate see a different array than the one `HOST_TOOL_NAMES` is checked against.
389
+ if (options.governTools && options.governTools.length > 0) {
390
+ tools = [...tools, ...options.governTools];
391
+ }
385
392
  // --- The caller's per-call approval gate (headless-host-bridge §2.3.3). ---
386
393
  //
387
394
  // AFTER THE LAST APPEND AND BEFORE THE PERMISSION FILTER, for the two structural
@@ -495,6 +502,13 @@ export function createBuiltinTools(options) {
495
502
  return list;
496
503
  return list.map((tool) => withToolPostHooks(tool, hookRunner));
497
504
  };
505
+ // The governance gate is the INNER wrapper and the plan gate the outer one
506
+ // (governance-mode 13.2): `withPlanModeGate(withGovernanceGate(t))`, because the plan
507
+ // refusal is the more specific message and speaks first.
508
+ if (options.governGate) {
509
+ const getState = options.governGate;
510
+ tools = tools.map((t) => withGovernanceGate(t, getState));
511
+ }
498
512
  if (!options.agentMode)
499
513
  return withHooksPost(withJobNotices(tools));
500
514
  const getMode = options.agentMode;
@@ -17,7 +17,7 @@
17
17
  * selected by whether an optional install step succeeded.
18
18
  */
19
19
  import { type AgentTool } from '@aragon-agent/core';
20
- import { type HumanInputGate, type NormalizedQuestion } from './human-input.js';
20
+ import { type HumanInputGate, type NormalizedPlan, type NormalizedQuestion } from './human-input.js';
21
21
  export interface PlanToolsOptions {
22
22
  gate: HumanInputGate;
23
23
  /**
@@ -52,8 +52,20 @@ export interface PlanToolsOptions {
52
52
  * This is the one place a `plan -> build` transition is not deferred to
53
53
  * `agent_end`, and the asymmetry is the whole point: the user has just read
54
54
  * the plan and authorized exactly this work. A stray `Shift+Tab` has not.
55
+ *
56
+ * Receives the approved plan (governance-mode 9.2): PLAN+GOVERN stashes it as the
57
+ * run specification. Returning a string REPLACES the tool result, which is how that
58
+ * path tells the model it is now the Orchestrator; returning nothing keeps
59
+ * `APPROVED_RESULT`, so every pre-existing caller is unchanged.
60
+ */
61
+ onPlanApproved: (plan: NormalizedPlan) => string | void;
62
+ /**
63
+ * Pre-submit check (governance-mode 9.2). Called after `normalizePlan` and BEFORE
64
+ * the review overlay opens; a non-null return is a refusal text, delivered as a
65
+ * NON-error result (an error invites an identical retry). Absent everywhere but
66
+ * PLAN+GOVERN, so plain plan mode is byte-identical.
55
67
  */
56
- onPlanApproved: () => void;
68
+ beforeSubmit?: () => string | null;
57
69
  }
58
70
  /**
59
71
  * Build the plan tools, or `[]` for a channel that can never reach a human.
@@ -165,13 +165,16 @@ function makeSubmitPlan(opts) {
165
165
  const plan = normalizePlan(params);
166
166
  if (!plan)
167
167
  return errorResult('A plan needs at least one step with a title.');
168
+ const refusal = opts.beforeSubmit?.() ?? null;
169
+ if (refusal !== null)
170
+ return textResult(refusal);
168
171
  const response = await opts.withHumanWait(() => opts.gate.request({ kind: 'plan', plan }, ctx.signal));
169
172
  if (response === null || response.kind !== 'planDecision') {
170
173
  return textResult(DISMISSED_RESULT);
171
174
  }
172
175
  if (response.decision === 'approved') {
173
- opts.onPlanApproved();
174
- return textResult(APPROVED_RESULT);
176
+ const replacement = opts.onPlanApproved(plan);
177
+ return textResult(typeof replacement === 'string' ? replacement : APPROVED_RESULT);
175
178
  }
176
179
  if (response.decision === 'revise') {
177
180
  return textResult(`Plan rejected. The user's feedback: "${response.feedback}". ` +