@aragon-agent/cli 0.6.43 → 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.
- package/CHANGELOG.md +44 -0
- package/README.md +69 -2
- package/dist/agent/controller.d.ts +69 -0
- package/dist/agent/controller.js +224 -11
- package/dist/agent/governance-view.d.ts +40 -0
- package/dist/agent/governance-view.js +40 -0
- package/dist/agent/governance-wiring.d.ts +162 -0
- package/dist/agent/governance-wiring.js +470 -0
- package/dist/agent/plan-prompt.d.ts +7 -0
- package/dist/agent/plan-prompt.js +2 -1
- package/dist/agent/reducer.d.ts +33 -1
- package/dist/agent/reducer.js +45 -9
- package/dist/agent/system-prompt.d.ts +19 -0
- package/dist/agent/system-prompt.js +9 -1
- package/dist/cli.js +45 -1
- package/dist/commands/builtins.js +10 -4
- package/dist/commands/governance-command.d.ts +35 -0
- package/dist/commands/governance-command.js +292 -0
- package/dist/commands/governance-runs-table.d.ts +40 -0
- package/dist/commands/governance-runs-table.js +130 -0
- package/dist/commands/registry.d.ts +12 -0
- package/dist/config/app-paths.d.ts +10 -0
- package/dist/config/app-paths.js +12 -0
- package/dist/config/cli-commands.d.ts +18 -0
- package/dist/config/cli-commands.js +47 -1
- package/dist/config/env.d.ts +2 -1
- package/dist/config/env.js +10 -1
- package/dist/config/governance-config.d.ts +125 -0
- package/dist/config/governance-config.js +164 -0
- package/dist/config/governance-launch.d.ts +71 -0
- package/dist/config/governance-launch.js +58 -0
- package/dist/config/load.d.ts +15 -0
- package/dist/config/load.js +26 -1
- package/dist/config/model-profile-store.d.ts +4 -2
- package/dist/config/schema.d.ts +22 -0
- package/dist/config/schema.js +6 -0
- package/dist/config/store.js +14 -1
- package/dist/config/ui-state.d.ts +11 -0
- package/dist/config/ui-state.js +11 -0
- package/dist/diagnostics/doctor.js +20 -0
- package/dist/governance/card-normalize.d.ts +24 -0
- package/dist/governance/card-normalize.js +76 -0
- package/dist/governance/copy.d.ts +109 -0
- package/dist/governance/copy.js +223 -0
- package/dist/governance/engine/aftermath.d.ts +49 -0
- package/dist/governance/engine/aftermath.js +134 -0
- package/dist/governance/engine/artifact-persist.d.ts +35 -0
- package/dist/governance/engine/artifact-persist.js +86 -0
- package/dist/governance/engine/budget.d.ts +52 -0
- package/dist/governance/engine/budget.js +112 -0
- package/dist/governance/engine/cards.d.ts +18 -0
- package/dist/governance/engine/cards.js +186 -0
- package/dist/governance/engine/digest.d.ts +32 -0
- package/dist/governance/engine/digest.js +63 -0
- package/dist/governance/engine/engine-core.d.ts +219 -0
- package/dist/governance/engine/engine-core.js +562 -0
- package/dist/governance/engine/engine.d.ts +68 -0
- package/dist/governance/engine/engine.js +314 -0
- package/dist/governance/engine/estimate.d.ts +38 -0
- package/dist/governance/engine/estimate.js +46 -0
- package/dist/governance/engine/inspector.d.ts +16 -0
- package/dist/governance/engine/inspector.js +396 -0
- package/dist/governance/engine/integration.d.ts +35 -0
- package/dist/governance/engine/integration.js +220 -0
- package/dist/governance/engine/lifecycle.d.ts +29 -0
- package/dist/governance/engine/lifecycle.js +656 -0
- package/dist/governance/engine/pause.d.ts +56 -0
- package/dist/governance/engine/pause.js +107 -0
- package/dist/governance/engine/phase-members.d.ts +15 -0
- package/dist/governance/engine/phase-members.js +34 -0
- package/dist/governance/engine/phase-planner.d.ts +53 -0
- package/dist/governance/engine/phase-planner.js +282 -0
- package/dist/governance/engine/rebuild.d.ts +27 -0
- package/dist/governance/engine/rebuild.js +57 -0
- package/dist/governance/engine/report.d.ts +66 -0
- package/dist/governance/engine/report.js +220 -0
- package/dist/governance/engine/run-digest.d.ts +20 -0
- package/dist/governance/engine/run-digest.js +42 -0
- package/dist/governance/engine/spec-builder.d.ts +117 -0
- package/dist/governance/engine/spec-builder.js +321 -0
- package/dist/governance/engine/stage-runner.d.ts +22 -0
- package/dist/governance/engine/stage-runner.js +664 -0
- package/dist/governance/engine/start-input.d.ts +19 -0
- package/dist/governance/engine/start-input.js +111 -0
- package/dist/governance/engine/state-machine.d.ts +101 -0
- package/dist/governance/engine/state-machine.js +586 -0
- package/dist/governance/engine/submission-queries.d.ts +42 -0
- package/dist/governance/engine/submission-queries.js +97 -0
- package/dist/governance/engine/submission-render.d.ts +12 -0
- package/dist/governance/engine/submission-render.js +92 -0
- package/dist/governance/engine/submissions.d.ts +27 -0
- package/dist/governance/engine/submissions.js +455 -0
- package/dist/governance/engine/visibility.d.ts +104 -0
- package/dist/governance/engine/visibility.js +223 -0
- package/dist/governance/governed-dispatch.d.ts +19 -0
- package/dist/governance/governed-dispatch.js +17 -0
- package/dist/governance/limits.d.ts +107 -0
- package/dist/governance/limits.js +131 -0
- package/dist/governance/posture-transition.d.ts +38 -0
- package/dist/governance/posture-transition.js +44 -0
- package/dist/governance/posture.d.ts +35 -0
- package/dist/governance/posture.js +59 -0
- package/dist/governance/profiles.d.ts +48 -0
- package/dist/governance/profiles.js +140 -0
- package/dist/governance/prompts/orchestrator-block.d.ts +40 -0
- package/dist/governance/prompts/orchestrator-block.js +128 -0
- package/dist/governance/prompts/plan-addendum.d.ts +22 -0
- package/dist/governance/prompts/plan-addendum.js +60 -0
- package/dist/governance/prompts/refusals.d.ts +48 -0
- package/dist/governance/prompts/refusals.js +82 -0
- package/dist/governance/prompts/role-blocks.d.ts +40 -0
- package/dist/governance/prompts/role-blocks.js +255 -0
- package/dist/governance/report-reader.d.ts +31 -0
- package/dist/governance/report-reader.js +57 -0
- package/dist/governance/roles.d.ts +83 -0
- package/dist/governance/roles.js +142 -0
- package/dist/governance/rules/claims.d.ts +83 -0
- package/dist/governance/rules/claims.js +250 -0
- package/dist/governance/rules/elimination.d.ts +66 -0
- package/dist/governance/rules/elimination.js +105 -0
- package/dist/governance/rules/events-table.d.ts +46 -0
- package/dist/governance/rules/events-table.js +94 -0
- package/dist/governance/rules/failure-analysis.d.ts +28 -0
- package/dist/governance/rules/failure-analysis.js +93 -0
- package/dist/governance/rules/ledger-replay.d.ts +53 -0
- package/dist/governance/rules/ledger-replay.js +53 -0
- package/dist/governance/rules/ledger-state.d.ts +85 -0
- package/dist/governance/rules/ledger-state.js +175 -0
- package/dist/governance/rules/ledger.d.ts +228 -0
- package/dist/governance/rules/ledger.js +560 -0
- package/dist/governance/rules/rca.d.ts +68 -0
- package/dist/governance/rules/rca.js +220 -0
- package/dist/governance/rules/settlement.d.ts +129 -0
- package/dist/governance/rules/settlement.js +363 -0
- package/dist/governance/rules/suspicion.d.ts +66 -0
- package/dist/governance/rules/suspicion.js +169 -0
- package/dist/governance/store/artifacts.d.ts +81 -0
- package/dist/governance/store/artifacts.js +173 -0
- package/dist/governance/store/atomic.d.ts +79 -0
- package/dist/governance/store/atomic.js +119 -0
- package/dist/governance/store/events-log.d.ts +35 -0
- package/dist/governance/store/events-log.js +86 -0
- package/dist/governance/store/run-lock.d.ts +51 -0
- package/dist/governance/store/run-lock.js +122 -0
- package/dist/governance/store/run-store.d.ts +229 -0
- package/dist/governance/store/run-store.js +644 -0
- package/dist/governance/store/scoreboard-export.d.ts +53 -0
- package/dist/governance/store/scoreboard-export.js +65 -0
- package/dist/governance/tools/child-tools.d.ts +31 -0
- package/dist/governance/tools/child-tools.js +65 -0
- package/dist/governance/tools/guard.d.ts +71 -0
- package/dist/governance/tools/guard.js +194 -0
- package/dist/governance/tools/inspector-tools.d.ts +63 -0
- package/dist/governance/tools/inspector-tools.js +424 -0
- package/dist/governance/tools/lead-tools.d.ts +42 -0
- package/dist/governance/tools/lead-tools.js +226 -0
- package/dist/governance/tools/normalize-util.d.ts +101 -0
- package/dist/governance/tools/normalize-util.js +230 -0
- package/dist/governance/tools/scan-incomplete.d.ts +79 -0
- package/dist/governance/tools/scan-incomplete.js +486 -0
- package/dist/governance/tools/shared-tools.d.ts +44 -0
- package/dist/governance/tools/shared-tools.js +125 -0
- package/dist/governance/tools/submit-normalizers.d.ts +28 -0
- package/dist/governance/tools/submit-normalizers.js +474 -0
- package/dist/governance/tools/submit-tools.d.ts +26 -0
- package/dist/governance/tools/submit-tools.js +171 -0
- package/dist/governance/types.d.ts +661 -0
- package/dist/governance/types.js +27 -0
- package/dist/proc/kill-tree.d.ts +19 -1
- package/dist/proc/kill-tree.js +32 -5
- package/dist/proc/limits.d.ts +19 -0
- package/dist/proc/limits.js +19 -0
- package/dist/proc/supervisor.js +8 -1
- package/dist/session/validate-session.js +32 -1
- package/dist/team/runtime.d.ts +8 -0
- package/dist/team/runtime.js +9 -0
- package/dist/team/subagent.d.ts +23 -0
- package/dist/team/subagent.js +14 -3
- package/dist/team/types.d.ts +6 -0
- package/dist/tools/bounded-spawn.d.ts +56 -0
- package/dist/tools/bounded-spawn.js +76 -0
- package/dist/tools/index.d.ts +30 -7
- package/dist/tools/index.js +14 -0
- package/dist/tools/plan-tools.d.ts +14 -2
- package/dist/tools/plan-tools.js +5 -2
- package/dist/tools/search-tools.js +52 -44
- package/dist/ui/App.js +145 -25
- package/dist/ui/BottomStatusRow.js +1 -1
- package/dist/ui/Composer.d.ts +7 -1
- package/dist/ui/Composer.js +9 -6
- package/dist/ui/GovernancePanel.d.ts +50 -0
- package/dist/ui/GovernancePanel.js +77 -0
- package/dist/ui/GovernanceRun.d.ts +18 -0
- package/dist/ui/GovernanceRun.js +11 -0
- package/dist/ui/ScrollbackShell.d.ts +35 -7
- package/dist/ui/ScrollbackShell.js +1 -1
- package/dist/ui/StatusBar.d.ts +15 -2
- package/dist/ui/StatusBar.js +10 -4
- package/dist/ui/Transcript.js +3 -0
- package/dist/ui/entries/GovernanceCard.d.ts +30 -0
- package/dist/ui/entries/GovernanceCard.js +32 -0
- package/dist/ui/governance-copy.d.ts +69 -0
- package/dist/ui/governance-copy.js +104 -0
- package/dist/ui/governance-status.d.ts +145 -0
- package/dist/ui/governance-status.js +326 -0
- package/dist/ui/interaction-copy.d.ts +4 -0
- package/dist/ui/interaction-copy.js +5 -0
- package/dist/ui/interaction-hints.d.ts +17 -0
- package/dist/ui/interaction-hints.js +14 -2
- package/dist/ui/layout/governance-board.d.ts +121 -0
- package/dist/ui/layout/governance-board.js +382 -0
- package/dist/ui/layout/governance-card.d.ts +53 -0
- package/dist/ui/layout/governance-card.js +134 -0
- package/dist/ui/layout/governance-chip.d.ts +38 -0
- package/dist/ui/layout/governance-chip.js +64 -0
- package/dist/ui/layout/governance-panel.d.ts +85 -0
- package/dist/ui/layout/governance-panel.js +358 -0
- package/dist/ui/layout/governance-roster.d.ts +65 -0
- package/dist/ui/layout/governance-roster.js +275 -0
- package/dist/ui/layout/status-detail-layout.js +3 -3
- package/dist/ui/layout/status-layout.d.ts +5 -2
- package/dist/ui/layout/status-layout.js +10 -6
- package/dist/ui/layout/virtual-window.js +11 -0
- package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +41 -0
- package/dist/ui/overlays/GovernanceBoardOverlay.js +76 -0
- package/dist/ui/overlays/GovernanceSettingsPage.d.ts +48 -0
- package/dist/ui/overlays/GovernanceSettingsPage.js +93 -0
- package/dist/ui/overlays/HelpOverlay.js +4 -3
- package/dist/ui/overlays/QuestionOverlay.d.ts +9 -1
- package/dist/ui/overlays/QuestionOverlay.js +4 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
- package/dist/ui/overlays/SettingsScreen.js +21 -1
- package/dist/ui/overlays/governance-settings.d.ts +79 -0
- package/dist/ui/overlays/governance-settings.js +140 -0
- package/dist/ui/overlays/model-profile-state.d.ts +6 -1
- package/dist/ui/overlays/model-profile-state.js +10 -0
- package/dist/ui/palettes.d.ts +17 -0
- package/dist/ui/palettes.js +9 -0
- package/dist/ui/scrollback/LiveTail.d.ts +3 -1
- package/dist/ui/scrollback/budget.d.ts +12 -6
- package/dist/ui/scrollback/budget.js +11 -4
- package/dist/ui/scrollback/live-preview.js +2 -0
- package/dist/ui/scrollback/settled-boundary.js +3 -0
- package/dist/ui/scrollback/tail-anchor.d.ts +5 -2
- package/dist/ui/scrollback/tail-anchor.js +3 -1
- package/dist/ui/shortcuts.js +2 -2
- package/dist/ui/theme.d.ts +7 -0
- package/dist/ui/theme.js +1 -0
- package/dist/ui/transcript-text.js +9 -0
- package/dist/ui/use-wheel-routing.js +3 -1
- package/package.json +2 -2
package/dist/proc/kill-tree.d.ts
CHANGED
|
@@ -38,5 +38,23 @@ export declare function killTree(pid: number | undefined, signal?: NodeJS.Signal
|
|
|
38
38
|
* The only kill primitive a signal handler, `process.on('exit')` or
|
|
39
39
|
* `handleFatal` may reach (I-9). No escalation ladder and no logging: a reaper
|
|
40
40
|
* that can block or throw is worse than one that misses a child.
|
|
41
|
+
*
|
|
42
|
+
* IT RETURNS WHETHER THE KILL WAS CONFIRMED, AND THAT IS NOT BOOKKEEPING. The
|
|
43
|
+
* Windows branch is a `taskkill` PROCESS, and `spawnSync`'s own `timeout` kills
|
|
44
|
+
* that process when the budget runs out. Measured on 2026-10-23: one
|
|
45
|
+
* `taskkill /t /f` of a shell plus its node child takes 1027-1058 ms on an IDLE
|
|
46
|
+
* machine (five samples), and a `taskkill` cut off at a 150 ms deadline leaves the
|
|
47
|
+
* tree ALIVE 1.5 s later (`ETIMEDOUT`, `status = null`, `signal = SIGTERM`). This
|
|
48
|
+
* function used to `catch {}` that result and return `void`, so the one caller
|
|
49
|
+
* that promises "no service outlives the CLI" could not tell a kill from a
|
|
50
|
+
* timeout - and a loaded host, where a single tree kill genuinely needs more than
|
|
51
|
+
* the old 2000 ms, silently leaked every child it was asked to reap.
|
|
52
|
+
*
|
|
53
|
+
* The budget therefore lives in `PROC_LIMITS.killTreeSyncTimeoutMs`, sized from
|
|
54
|
+
* that measurement rather than from optimism, and the answer comes back to the
|
|
55
|
+
* caller so `reapSync` can raise `killIncomplete` instead of pretending.
|
|
56
|
+
*
|
|
57
|
+
* POSIX reports `true` when a signal was DELIVERED, which is as much as a signal
|
|
58
|
+
* can ever confirm.
|
|
41
59
|
*/
|
|
42
|
-
export declare function killTreeSync(pid: number | undefined):
|
|
60
|
+
export declare function killTreeSync(pid: number | undefined): boolean;
|
package/dist/proc/kill-tree.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { spawn, spawnSync } from 'node:child_process';
|
|
26
26
|
import process from 'node:process';
|
|
27
|
+
import { PROC_LIMITS } from './limits.js';
|
|
27
28
|
/** Is the pid still there? `signal 0` tests for existence without delivering. */
|
|
28
29
|
export function isAlive(pid) {
|
|
29
30
|
if (pid === undefined || !Number.isFinite(pid) || pid <= 0)
|
|
@@ -88,32 +89,58 @@ export function killTree(pid, signal = 'SIGKILL') {
|
|
|
88
89
|
* The only kill primitive a signal handler, `process.on('exit')` or
|
|
89
90
|
* `handleFatal` may reach (I-9). No escalation ladder and no logging: a reaper
|
|
90
91
|
* that can block or throw is worse than one that misses a child.
|
|
92
|
+
*
|
|
93
|
+
* IT RETURNS WHETHER THE KILL WAS CONFIRMED, AND THAT IS NOT BOOKKEEPING. The
|
|
94
|
+
* Windows branch is a `taskkill` PROCESS, and `spawnSync`'s own `timeout` kills
|
|
95
|
+
* that process when the budget runs out. Measured on 2026-10-23: one
|
|
96
|
+
* `taskkill /t /f` of a shell plus its node child takes 1027-1058 ms on an IDLE
|
|
97
|
+
* machine (five samples), and a `taskkill` cut off at a 150 ms deadline leaves the
|
|
98
|
+
* tree ALIVE 1.5 s later (`ETIMEDOUT`, `status = null`, `signal = SIGTERM`). This
|
|
99
|
+
* function used to `catch {}` that result and return `void`, so the one caller
|
|
100
|
+
* that promises "no service outlives the CLI" could not tell a kill from a
|
|
101
|
+
* timeout - and a loaded host, where a single tree kill genuinely needs more than
|
|
102
|
+
* the old 2000 ms, silently leaked every child it was asked to reap.
|
|
103
|
+
*
|
|
104
|
+
* The budget therefore lives in `PROC_LIMITS.killTreeSyncTimeoutMs`, sized from
|
|
105
|
+
* that measurement rather than from optimism, and the answer comes back to the
|
|
106
|
+
* caller so `reapSync` can raise `killIncomplete` instead of pretending.
|
|
107
|
+
*
|
|
108
|
+
* POSIX reports `true` when a signal was DELIVERED, which is as much as a signal
|
|
109
|
+
* can ever confirm.
|
|
91
110
|
*/
|
|
92
111
|
export function killTreeSync(pid) {
|
|
93
112
|
if (pid === undefined || !Number.isFinite(pid) || pid <= 0)
|
|
94
|
-
return;
|
|
113
|
+
return false;
|
|
95
114
|
if (process.platform === 'win32') {
|
|
115
|
+
let result;
|
|
96
116
|
try {
|
|
97
|
-
spawnSync('taskkill', ['/pid', String(pid), '/t', '/f'], {
|
|
117
|
+
result = spawnSync('taskkill', ['/pid', String(pid), '/t', '/f'], {
|
|
98
118
|
windowsHide: true,
|
|
99
119
|
stdio: 'ignore',
|
|
100
|
-
timeout:
|
|
120
|
+
timeout: PROC_LIMITS.killTreeSyncTimeoutMs,
|
|
101
121
|
});
|
|
102
122
|
}
|
|
103
123
|
catch {
|
|
104
|
-
|
|
124
|
+
return false;
|
|
105
125
|
}
|
|
106
|
-
|
|
126
|
+
// `error` is set for a spawn failure AND for the budget expiring (`ETIMEDOUT`,
|
|
127
|
+
// with `signal = SIGTERM`), and neither is a kill.
|
|
128
|
+
if (result.error)
|
|
129
|
+
return false;
|
|
130
|
+
return result.status === 0;
|
|
107
131
|
}
|
|
108
132
|
try {
|
|
109
133
|
process.kill(-pid, 'SIGKILL');
|
|
134
|
+
return true;
|
|
110
135
|
}
|
|
111
136
|
catch {
|
|
112
137
|
try {
|
|
113
138
|
process.kill(pid, 'SIGKILL');
|
|
139
|
+
return true;
|
|
114
140
|
}
|
|
115
141
|
catch {
|
|
116
142
|
/* already gone */
|
|
143
|
+
return false;
|
|
117
144
|
}
|
|
118
145
|
}
|
|
119
146
|
}
|
package/dist/proc/limits.d.ts
CHANGED
|
@@ -102,6 +102,25 @@ export declare const PROC_LIMITS: {
|
|
|
102
102
|
readonly killVerifyMs: 2000;
|
|
103
103
|
/** Liveness poll interval while waiting for a `SIGKILL` to land. */
|
|
104
104
|
readonly killVerifyPollMs: 50;
|
|
105
|
+
/**
|
|
106
|
+
* How long ONE synchronous tree kill (`killTreeSync`) may take.
|
|
107
|
+
*
|
|
108
|
+
* MEASURED, NOT GUESSED. A `taskkill /pid <shell> /t /f` of a shell plus its
|
|
109
|
+
* node child takes 1027-1058 ms on the reference machine on an IDLE host (five
|
|
110
|
+
* samples, 2026-10-23), so the old 2000 ms was 1.9x the honest cost and the
|
|
111
|
+
* 2026-10-23 release run - where the whole suite took 340 s against the usual
|
|
112
|
+
* 200-240 s - is comfortably inside the range where a single tree kill exceeds
|
|
113
|
+
* it. Crossing it is not a slow kill, it is NO kill: `spawnSync` kills
|
|
114
|
+
* `taskkill` itself at the deadline and the tree keeps running (verified: a
|
|
115
|
+
* 150 ms budget left the child alive 1.5 s later, `ETIMEDOUT` / `status null`).
|
|
116
|
+
*
|
|
117
|
+
* 5000 ms is ~5x the measured cost. It is a CEILING and not a cost: a tree that
|
|
118
|
+
* dies in the usual second still returns in that second. The worst case is
|
|
119
|
+
* `maxJobs` (16) trees at the ceiling on an exit hook, which is why this is not
|
|
120
|
+
* any larger - the reaper prefers a reported miss to an exit that never ends,
|
|
121
|
+
* and the miss is now reported (`ProcRecord.killIncomplete`).
|
|
122
|
+
*/
|
|
123
|
+
readonly killTreeSyncTimeoutMs: 5000;
|
|
105
124
|
/** Below this many terminal columns the status chip degrades to `[N]`. */
|
|
106
125
|
readonly statusCompactCols: 92;
|
|
107
126
|
/**
|
package/dist/proc/limits.js
CHANGED
|
@@ -102,6 +102,25 @@ export const PROC_LIMITS = {
|
|
|
102
102
|
killVerifyMs: 2000,
|
|
103
103
|
/** Liveness poll interval while waiting for a `SIGKILL` to land. */
|
|
104
104
|
killVerifyPollMs: 50,
|
|
105
|
+
/**
|
|
106
|
+
* How long ONE synchronous tree kill (`killTreeSync`) may take.
|
|
107
|
+
*
|
|
108
|
+
* MEASURED, NOT GUESSED. A `taskkill /pid <shell> /t /f` of a shell plus its
|
|
109
|
+
* node child takes 1027-1058 ms on the reference machine on an IDLE host (five
|
|
110
|
+
* samples, 2026-10-23), so the old 2000 ms was 1.9x the honest cost and the
|
|
111
|
+
* 2026-10-23 release run - where the whole suite took 340 s against the usual
|
|
112
|
+
* 200-240 s - is comfortably inside the range where a single tree kill exceeds
|
|
113
|
+
* it. Crossing it is not a slow kill, it is NO kill: `spawnSync` kills
|
|
114
|
+
* `taskkill` itself at the deadline and the tree keeps running (verified: a
|
|
115
|
+
* 150 ms budget left the child alive 1.5 s later, `ETIMEDOUT` / `status null`).
|
|
116
|
+
*
|
|
117
|
+
* 5000 ms is ~5x the measured cost. It is a CEILING and not a cost: a tree that
|
|
118
|
+
* dies in the usual second still returns in that second. The worst case is
|
|
119
|
+
* `maxJobs` (16) trees at the ceiling on an exit hook, which is why this is not
|
|
120
|
+
* any larger - the reaper prefers a reported miss to an exit that never ends,
|
|
121
|
+
* and the miss is now reported (`ProcRecord.killIncomplete`).
|
|
122
|
+
*/
|
|
123
|
+
killTreeSyncTimeoutMs: 5000,
|
|
105
124
|
/** Below this many terminal columns the status chip degrades to `[N]`. */
|
|
106
125
|
statusCompactCols: 92,
|
|
107
126
|
// --- long-task-jobs: bounded waiting, progress, notices, log mirror --------
|
package/dist/proc/supervisor.js
CHANGED
|
@@ -404,9 +404,16 @@ export class ProcSupervisor {
|
|
|
404
404
|
for (const entry of this.services.values()) {
|
|
405
405
|
if (isTerminalStatus(entry.record.status))
|
|
406
406
|
continue;
|
|
407
|
-
|
|
407
|
+
// THE ANSWER IS PART OF THE REAP (AC-42, 2026-10-23 release). A `taskkill`
|
|
408
|
+
// that outlived its budget is killed itself and the tree survives, so an
|
|
409
|
+
// unconfirmed kill has to reach the record the card and `bash_output` read -
|
|
410
|
+
// otherwise "stopped" is a claim nobody made good on. `killIncomplete` is
|
|
411
|
+
// that field (P2-6), and it is the reason the reaper may keep a bound at all.
|
|
412
|
+
const killed = killTreeSync(entry.record.pid);
|
|
408
413
|
entry.record.status = 'stopped';
|
|
409
414
|
entry.record.endedAt = Date.now();
|
|
415
|
+
if (!killed)
|
|
416
|
+
entry.record.killIncomplete = true;
|
|
410
417
|
entry.watcher?.stop();
|
|
411
418
|
if (entry.outputTimer)
|
|
412
419
|
clearTimeout(entry.outputTimer);
|
|
@@ -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
|
}
|
|
@@ -145,6 +146,12 @@ const subagent = shape({
|
|
|
145
146
|
});
|
|
146
147
|
const ENTRY_FIELDS = {
|
|
147
148
|
user: { text: string },
|
|
149
|
+
// A GOVERNANCE CARD IS HISTORY, so it is validated LENIENTLY (governance-mode 10.4):
|
|
150
|
+
// unknown fields are ignored (`shape` only reads the declared ones) and `card` is not
|
|
151
|
+
// shape-checked here at all - `sanitizeGovernanceEntries` below repairs a card it can
|
|
152
|
+
// and degrades one it cannot to a one-line notice, so a corrupt footnote never costs
|
|
153
|
+
// the session. `at` is advisory.
|
|
154
|
+
governance: { at: optional(number), card: () => { } },
|
|
148
155
|
queued: { text: string, queueId: optional(nonempty) },
|
|
149
156
|
notice: { text: string, level: enumeration('info', 'warn', 'error') },
|
|
150
157
|
assistant: {
|
|
@@ -282,9 +289,33 @@ export function validateSession(value) {
|
|
|
282
289
|
if (source.acceptance !== undefined) {
|
|
283
290
|
prepared.acceptance = normalizeAcceptance(source.acceptance, [], { enabled: true }).items;
|
|
284
291
|
}
|
|
285
|
-
prepared.entries = sanitizeEntryAcceptance(prepared.entries);
|
|
292
|
+
prepared.entries = sanitizeGovernanceEntries(sanitizeEntryAcceptance(prepared.entries));
|
|
286
293
|
return prepared;
|
|
287
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Repair every governance card the file carries, or degrade it to a one-line notice.
|
|
297
|
+
*
|
|
298
|
+
* The card is rebuilt from `normalizeGovernanceCard`'s output so only known fields survive
|
|
299
|
+
* (a hand-edited file cannot smuggle a 10 000-row table into the transcript), and a value
|
|
300
|
+
* with nothing renderable becomes a `notice` entry that keeps the entry's id: ids must stay
|
|
301
|
+
* unique and in order, and the transcript should say that something was here.
|
|
302
|
+
*/
|
|
303
|
+
function sanitizeGovernanceEntries(entries) {
|
|
304
|
+
if (!Array.isArray(entries))
|
|
305
|
+
return entries;
|
|
306
|
+
return entries.map((entry) => {
|
|
307
|
+
if (!entry || typeof entry !== 'object')
|
|
308
|
+
return entry;
|
|
309
|
+
const record = entry;
|
|
310
|
+
if (record.kind !== 'governance')
|
|
311
|
+
return entry;
|
|
312
|
+
const card = normalizeGovernanceCard(record.card);
|
|
313
|
+
if (card === null) {
|
|
314
|
+
return { id: record.id, kind: 'notice', level: 'info', text: GOVERNANCE_CARD_FALLBACK_TEXT };
|
|
315
|
+
}
|
|
316
|
+
return { id: record.id, kind: 'governance', at: typeof record.at === 'number' ? record.at : 0, card };
|
|
317
|
+
});
|
|
318
|
+
}
|
|
288
319
|
/**
|
|
289
320
|
* Drop the acceptance rows a persisted card cannot use, keeping every usable one.
|
|
290
321
|
*
|
package/dist/team/runtime.d.ts
CHANGED
|
@@ -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;
|
package/dist/team/runtime.js
CHANGED
|
@@ -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).
|
package/dist/team/subagent.d.ts
CHANGED
|
@@ -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. */
|
package/dist/team/subagent.js
CHANGED
|
@@ -77,7 +77,8 @@ export function buildSubagentTools(spec, deps, getAgent) {
|
|
|
77
77
|
: TEAM_LIMITS.waitMaxSeconds * 1000,
|
|
78
78
|
}),
|
|
79
79
|
];
|
|
80
|
-
const
|
|
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
|
-
|
|
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:
|
|
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
|
package/dist/team/types.d.ts
CHANGED
|
@@ -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. */
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CHILD PROCESS WITH A DEADLINE (bug-diagnosis 0b33323c, release gate 2026-10-23).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. `search-tools.ts` used to await a bare `spawn('rg', ...)` whose
|
|
5
|
+
* promise settled on `'close'` and on nothing else. A child that never closes -
|
|
6
|
+
* a launcher shim blocked by a scanner, a wedged network drive, a filesystem that
|
|
7
|
+
* stopped answering - therefore left the tool pending FOREVER: the model's turn
|
|
8
|
+
* had no result to return, the Ink frame had no answer to draw, and the only thing
|
|
9
|
+
* that ever ended it was the test runner's wall clock
|
|
10
|
+
* (`tools.test.ts > grep finds a seeded pattern`, `Test timed out in 30000ms`,
|
|
11
|
+
* 2026-10-23 release). CLAUDE.md section 5 says exactly what is missing here: at
|
|
12
|
+
* an IO / subprocess boundary use a structured wrapper with a timeout, never a
|
|
13
|
+
* bare call.
|
|
14
|
+
*
|
|
15
|
+
* SETTLES ONCE, ALWAYS. Every way a child can end - exit, spawn failure, and OUR
|
|
16
|
+
* OWN deadline - resolves this promise, and the first of them wins. The deadline
|
|
17
|
+
* is the last resort, not a measuring instrument: a caller that has produced an
|
|
18
|
+
* answer is not interrupted by it (`clearTimeout` happens in `finish`).
|
|
19
|
+
*
|
|
20
|
+
* THE KILL IS A TREE KILL. `rg` on Windows is reached through a launcher
|
|
21
|
+
* (`shims/rg.exe`), and killing the launcher alone leaves the real search running
|
|
22
|
+
* forever - which would keep this fix from fixing anything. `killTree` is the one
|
|
23
|
+
* owner of that knowledge in this package and it never throws.
|
|
24
|
+
*/
|
|
25
|
+
/** What ended the child, which is the only thing the caller can act on. */
|
|
26
|
+
export type DeadlineOutcome =
|
|
27
|
+
/** The child closed on its own; `exitCode` is real. */
|
|
28
|
+
'exit'
|
|
29
|
+
/** It outlived `deadlineMs`; it has been killed and `stdout` is what it managed. */
|
|
30
|
+
| 'deadline'
|
|
31
|
+
/** It never started (`ENOENT` and friends). */
|
|
32
|
+
| 'unavailable';
|
|
33
|
+
export interface DeadlineResult {
|
|
34
|
+
outcome: DeadlineOutcome;
|
|
35
|
+
/** `null` for `'deadline'` and `'unavailable'`: there was no exit status. */
|
|
36
|
+
exitCode: number | null;
|
|
37
|
+
/** Everything the child wrote to stdout before it settled. */
|
|
38
|
+
stdout: string;
|
|
39
|
+
}
|
|
40
|
+
export interface DeadlineOptions {
|
|
41
|
+
cwd: string;
|
|
42
|
+
/** Hard limit on the child's lifetime, in milliseconds. Must be > 0. */
|
|
43
|
+
deadlineMs: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Run `command` and settle within `deadlineMs`, whatever the child does.
|
|
47
|
+
*
|
|
48
|
+
* stderr is DROPPED rather than piped: nothing here reads it, and an undrained
|
|
49
|
+
* pipe is a second way to wedge a child (the buffer fills and it blocks on write).
|
|
50
|
+
* stdin is `ignore` for the same reason in reverse - a child that decides to read
|
|
51
|
+
* stdin must see EOF, not wait for a user who is not there.
|
|
52
|
+
*
|
|
53
|
+
* Never throws, never rejects: a caller that has to explain a failure gets
|
|
54
|
+
* `'unavailable'` or `'deadline'` instead of an exception it did not expect.
|
|
55
|
+
*/
|
|
56
|
+
export declare function runWithDeadline(command: string, args: string[], options: DeadlineOptions): Promise<DeadlineResult>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CHILD PROCESS WITH A DEADLINE (bug-diagnosis 0b33323c, release gate 2026-10-23).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. `search-tools.ts` used to await a bare `spawn('rg', ...)` whose
|
|
5
|
+
* promise settled on `'close'` and on nothing else. A child that never closes -
|
|
6
|
+
* a launcher shim blocked by a scanner, a wedged network drive, a filesystem that
|
|
7
|
+
* stopped answering - therefore left the tool pending FOREVER: the model's turn
|
|
8
|
+
* had no result to return, the Ink frame had no answer to draw, and the only thing
|
|
9
|
+
* that ever ended it was the test runner's wall clock
|
|
10
|
+
* (`tools.test.ts > grep finds a seeded pattern`, `Test timed out in 30000ms`,
|
|
11
|
+
* 2026-10-23 release). CLAUDE.md section 5 says exactly what is missing here: at
|
|
12
|
+
* an IO / subprocess boundary use a structured wrapper with a timeout, never a
|
|
13
|
+
* bare call.
|
|
14
|
+
*
|
|
15
|
+
* SETTLES ONCE, ALWAYS. Every way a child can end - exit, spawn failure, and OUR
|
|
16
|
+
* OWN deadline - resolves this promise, and the first of them wins. The deadline
|
|
17
|
+
* is the last resort, not a measuring instrument: a caller that has produced an
|
|
18
|
+
* answer is not interrupted by it (`clearTimeout` happens in `finish`).
|
|
19
|
+
*
|
|
20
|
+
* THE KILL IS A TREE KILL. `rg` on Windows is reached through a launcher
|
|
21
|
+
* (`shims/rg.exe`), and killing the launcher alone leaves the real search running
|
|
22
|
+
* forever - which would keep this fix from fixing anything. `killTree` is the one
|
|
23
|
+
* owner of that knowledge in this package and it never throws.
|
|
24
|
+
*/
|
|
25
|
+
import { spawn } from 'node:child_process';
|
|
26
|
+
import { killTree } from '../proc/kill-tree.js';
|
|
27
|
+
/**
|
|
28
|
+
* Run `command` and settle within `deadlineMs`, whatever the child does.
|
|
29
|
+
*
|
|
30
|
+
* stderr is DROPPED rather than piped: nothing here reads it, and an undrained
|
|
31
|
+
* pipe is a second way to wedge a child (the buffer fills and it blocks on write).
|
|
32
|
+
* stdin is `ignore` for the same reason in reverse - a child that decides to read
|
|
33
|
+
* stdin must see EOF, not wait for a user who is not there.
|
|
34
|
+
*
|
|
35
|
+
* Never throws, never rejects: a caller that has to explain a failure gets
|
|
36
|
+
* `'unavailable'` or `'deadline'` instead of an exception it did not expect.
|
|
37
|
+
*/
|
|
38
|
+
export function runWithDeadline(command, args, options) {
|
|
39
|
+
return new Promise((settle) => {
|
|
40
|
+
let child;
|
|
41
|
+
try {
|
|
42
|
+
child = spawn(command, args, {
|
|
43
|
+
cwd: options.cwd,
|
|
44
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
45
|
+
windowsHide: true,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
settle({ outcome: 'unavailable', exitCode: null, stdout: '' });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
let stdout = '';
|
|
53
|
+
let settled = false;
|
|
54
|
+
let timer;
|
|
55
|
+
const finish = (result) => {
|
|
56
|
+
if (settled)
|
|
57
|
+
return;
|
|
58
|
+
settled = true;
|
|
59
|
+
if (timer !== undefined)
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
settle(result);
|
|
62
|
+
};
|
|
63
|
+
timer = setTimeout(() => {
|
|
64
|
+
// A pid is all `killTree` needs, and it is the only kill that reaches a
|
|
65
|
+
// grandchild. Partial output is still output: the caller decides whether a
|
|
66
|
+
// truncated answer is worth having.
|
|
67
|
+
killTree(child.pid);
|
|
68
|
+
finish({ outcome: 'deadline', exitCode: null, stdout });
|
|
69
|
+
}, options.deadlineMs);
|
|
70
|
+
child.stdout?.on('data', (chunk) => {
|
|
71
|
+
stdout += chunk.toString();
|
|
72
|
+
});
|
|
73
|
+
child.on('error', () => finish({ outcome: 'unavailable', exitCode: null, stdout }));
|
|
74
|
+
child.on('close', (code) => finish({ outcome: 'exit', exitCode: code, stdout }));
|
|
75
|
+
});
|
|
76
|
+
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -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
|
|
254
|
-
* `team_send` / `team_wait
|
|
255
|
-
* evaluating them against the lead's
|
|
256
|
-
* and dead-end the channel
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
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
|
package/dist/tools/index.js
CHANGED
|
@@ -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
|
-
|
|
68
|
+
beforeSubmit?: () => string | null;
|
|
57
69
|
}
|
|
58
70
|
/**
|
|
59
71
|
* Build the plan tools, or `[]` for a channel that can never reach a human.
|
package/dist/tools/plan-tools.js
CHANGED
|
@@ -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}". ` +
|