@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
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The governance slice of the view model: the small pure folds `reducer.ts` delegates to
|
|
3
|
+
* (governance-mode section 14.4).
|
|
4
|
+
*
|
|
5
|
+
* Pure: no React, no I/O. ASCII ONLY (`agent/` is glyph-scanned).
|
|
6
|
+
*
|
|
7
|
+
* WHY A FILE OF ITS OWN. `reducer.ts` is already past the file-size ceiling and the repo
|
|
8
|
+
* rule is "wiring-style growth only": each governance `case` in the reducer is a line or
|
|
9
|
+
* two that calls a function here, so the logic is unit-testable without a `ViewState`.
|
|
10
|
+
*/
|
|
11
|
+
import { composePosture, isGovernancePosture } from '../governance/posture.js';
|
|
12
|
+
/**
|
|
13
|
+
* The pair of posture fields one `setAgentMode` dispatch writes.
|
|
14
|
+
*
|
|
15
|
+
* "NEVER DISPATCH THE REQUESTED VALUE, ONLY THE ADOPTED ONE" (spec 4.3): the controller
|
|
16
|
+
* reports all four values together and the reducer mirrors them, so the badge and the gate
|
|
17
|
+
* cannot disagree. A dispatch that carries only the gate mode (every pre-governance call
|
|
18
|
+
* site) keeps the governance dimension the view already has and swaps the mode half, which
|
|
19
|
+
* is exactly what such a caller meant.
|
|
20
|
+
*/
|
|
21
|
+
export function adoptPosture(current, action) {
|
|
22
|
+
const governed = isGovernancePosture(current);
|
|
23
|
+
const posture = action.posture ?? composePosture(action.mode, governed);
|
|
24
|
+
const pendingPosture = action.pendingPosture !== undefined
|
|
25
|
+
? action.pendingPosture
|
|
26
|
+
: action.pending === null
|
|
27
|
+
? null
|
|
28
|
+
: composePosture(action.pending, governed);
|
|
29
|
+
return { posture, pendingPosture };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Fold a pushed snapshot into the view: a snapshot OLDER than the one held, for the same
|
|
33
|
+
* run, is dropped (the wiring throttles, and a late flush must not roll the panel back).
|
|
34
|
+
* Returns the held object unchanged when nothing should change.
|
|
35
|
+
*/
|
|
36
|
+
export function foldGovernanceSnapshot(held, incoming) {
|
|
37
|
+
if (held !== null && held.runId === incoming.runId && incoming.rev < held.rev)
|
|
38
|
+
return held;
|
|
39
|
+
return incoming;
|
|
40
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GovernanceWiring - the host side of governance mode (governance-mode 3.2, 14.2).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`agent/` is inside the glyph scanner's scope).
|
|
5
|
+
*
|
|
6
|
+
* `AgentController` is 3000+ lines over the repo's file limit, so every governance decision
|
|
7
|
+
* it needs lives here and the controller only forwards: registration, availability, the
|
|
8
|
+
* Orchestrator and role prompt blocks, the PLAN+GOVERN interview gate, the approved-plan
|
|
9
|
+
* stash, the engine's lifecycle, the child tool hooks. The posture TRANSITION rule is a
|
|
10
|
+
* pure function in `governance/posture-transition.ts` for the same reason.
|
|
11
|
+
*
|
|
12
|
+
* TWO LAYERS OF "CAN I USE THIS" (D-22 / RV-1 / RV-14), and they are not the same thing:
|
|
13
|
+
*
|
|
14
|
+
* - REGISTERED is decided ONCE, in the constructor, from inputs that cannot change
|
|
15
|
+
* afterwards: team tools exist, `governance.enabled`, the env kill switch, and the
|
|
16
|
+
* channel's STATIC `neverPrompts` (NOT the live `canPrompt()`: at construction the App
|
|
17
|
+
* has not attached its human channel yet, so `canPrompt()` is false for every TUI
|
|
18
|
+
* session and keying off it would register nothing, silently, forever). The tool array
|
|
19
|
+
* is immutable after `new Agent(...)`, so this is irreversible for the session.
|
|
20
|
+
* - AVAILABLE is live: registered AND `team.enabled` AND an API key. It is what `/gov`,
|
|
21
|
+
* `doctor` and the key cycle ask.
|
|
22
|
+
*/
|
|
23
|
+
import type { AgentTool, ModelCost } from '@aragon-agent/core';
|
|
24
|
+
import type { CliConfig } from '../config/schema.js';
|
|
25
|
+
import { type GovernanceAvailability } from '../governance/copy.js';
|
|
26
|
+
import type { TeamRuntimeLike } from '../governance/engine/engine-core.js';
|
|
27
|
+
import type { PauseOutcome } from '../governance/engine/pause.js';
|
|
28
|
+
import { type Posture } from '../governance/posture.js';
|
|
29
|
+
import { type RunReportResult } from '../governance/report-reader.js';
|
|
30
|
+
import { type IndexEntry } from '../governance/store/run-store.js';
|
|
31
|
+
import type { GovernanceEventListener, GovernanceSnapshot, LedgerState, RunState } from '../governance/types.js';
|
|
32
|
+
import type { SubagentSpec } from '../team/types.js';
|
|
33
|
+
import type { HumanInputGate, NormalizedPlan, NormalizedQuestion } from '../tools/human-input.js';
|
|
34
|
+
import type { AgentMode } from './agent-mode.js';
|
|
35
|
+
export interface WiringDeps {
|
|
36
|
+
/** `config.team.enabled` at construction: whether `task` (and a TeamRuntime) exist. */
|
|
37
|
+
teamRegistered: boolean;
|
|
38
|
+
config: () => CliConfig;
|
|
39
|
+
gate: HumanInputGate;
|
|
40
|
+
getCwd: () => string;
|
|
41
|
+
/** The gate mode (`build` | `plan`); the controller owns it. */
|
|
42
|
+
getGateMode: () => AgentMode;
|
|
43
|
+
getSessionId?: () => string | undefined;
|
|
44
|
+
withPausedWatchdog: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
45
|
+
withHumanWait: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
46
|
+
hasApiKey: () => boolean;
|
|
47
|
+
modelCost: () => ModelCost | undefined;
|
|
48
|
+
getRuntime: () => TeamRuntimeLike | null;
|
|
49
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
50
|
+
now?: () => number;
|
|
51
|
+
}
|
|
52
|
+
/** Render the approved plan as the run specification (<= `specChars`). */
|
|
53
|
+
export declare function renderPlanAsSpec(plan: NormalizedPlan): string;
|
|
54
|
+
/** What `/gov runs` shows per run: the index row without the cwd / session plumbing. */
|
|
55
|
+
export type GovernanceRunRow = Pick<IndexEntry, 'runId' | 'goal' | 'status' | 'updatedAt' | 'digest'>;
|
|
56
|
+
export declare class GovernanceWiring {
|
|
57
|
+
private readonly deps;
|
|
58
|
+
private readonly registered;
|
|
59
|
+
private readonly enabledAtStart;
|
|
60
|
+
private readonly env;
|
|
61
|
+
private governOn;
|
|
62
|
+
private pendingPosture;
|
|
63
|
+
private engine;
|
|
64
|
+
private store;
|
|
65
|
+
private readonly listeners;
|
|
66
|
+
private engineUnsubscribe;
|
|
67
|
+
private approvedPlan;
|
|
68
|
+
private answeredRounds;
|
|
69
|
+
private readonly coveredTopics;
|
|
70
|
+
/** A lazy facade: tools are built before the engine exists and the engine needs the runtime. */
|
|
71
|
+
private readonly api;
|
|
72
|
+
constructor(deps: WiringDeps);
|
|
73
|
+
/** Whether governance tools were registered at construction. Never changes. */
|
|
74
|
+
isRegistered(): boolean;
|
|
75
|
+
/** The live answer, with the precise reason when it is no. */
|
|
76
|
+
isAvailable(): GovernanceAvailability;
|
|
77
|
+
private unregisteredReason;
|
|
78
|
+
/** The exact sentence for the current unavailability, or null when available. */
|
|
79
|
+
unavailableText(): string | null;
|
|
80
|
+
/** Shift+Tab cycle: four states when usable and `governance.shiftTab`, else the classic two. */
|
|
81
|
+
postureCycle(): readonly Posture[];
|
|
82
|
+
isGovernOn(): boolean;
|
|
83
|
+
setGovernOn(on: boolean): void;
|
|
84
|
+
getPendingPosture(): Posture | null;
|
|
85
|
+
setPendingPosture(pending: Posture | null): void;
|
|
86
|
+
/** Entering PLAN+GOVERN starts a fresh interview and drops any older plan (9.3). */
|
|
87
|
+
onPostureAdopted(posture: Posture, previous: Posture): void;
|
|
88
|
+
isStageRunning(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* `''` unless governance is on, registered and available (closed state: byte-equal).
|
|
91
|
+
*
|
|
92
|
+
* `config` is the config the prompt is being composed FOR. A settings save composes the
|
|
93
|
+
* prompt from the candidate BEFORE it becomes live, so reading the live config here would
|
|
94
|
+
* quote the limits from before the save (governance-mode-v1-1 IF-3).
|
|
95
|
+
*/
|
|
96
|
+
orchestratorBlock(posture: Posture, config?: CliConfig): string;
|
|
97
|
+
/** `''` unless PLAN+GOVERN. */
|
|
98
|
+
planAddendum(posture: Posture, config?: CliConfig): string;
|
|
99
|
+
roleBlockFor(spec: SubagentSpec): string;
|
|
100
|
+
/** Called from the controller's `noteAnswered`: one real, answered, non-duplicate round. */
|
|
101
|
+
noteAnsweredRound(questions?: readonly NormalizedQuestion[]): void;
|
|
102
|
+
answeredRoundCount(): number;
|
|
103
|
+
/** `submit_plan` pre-check: a refusal text, or null to proceed. */
|
|
104
|
+
beforeSubmitPlan(): string | null;
|
|
105
|
+
/** Stash the approved plan when governance is on. Returns which way the session goes. */
|
|
106
|
+
handlePlanApproved(plan: NormalizedPlan): 'govern' | 'build';
|
|
107
|
+
/** `gov_run start` consumes the stash exactly once. */
|
|
108
|
+
takeApprovedPlan(): string | null;
|
|
109
|
+
/** `[]` when not registered, which is the byte-identical closed path. */
|
|
110
|
+
leadTools(): AgentTool[];
|
|
111
|
+
leadPolicyExempt(): ReadonlySet<string>;
|
|
112
|
+
childPolicyExempt(): ReadonlySet<string>;
|
|
113
|
+
/** Role tools for a governed child; `[]` for anything else. */
|
|
114
|
+
childToolsFor(spec: SubagentSpec): AgentTool[];
|
|
115
|
+
/**
|
|
116
|
+
* Narrow a governed child's finished tool array: the inspector loses its write tools and
|
|
117
|
+
* gets the restricted shell, and every read-class tool gets the store tripwire.
|
|
118
|
+
*
|
|
119
|
+
* This runs on the factory's RESULT (see `SubagentDeps.narrowToolsFor`) because it has to
|
|
120
|
+
* see the built-in `bash` to replace it, while the role tools had to go IN through the
|
|
121
|
+
* factory. A non-governed spec comes back untouched.
|
|
122
|
+
*/
|
|
123
|
+
narrowToolsFor(spec: SubagentSpec, tools: AgentTool[]): AgentTool[];
|
|
124
|
+
/** The engine is created on first use: a session that never governs never opens a store. */
|
|
125
|
+
private ensureEngine;
|
|
126
|
+
/** `confirmCharter` through the EXISTING plan-review overlay; no new overlay kind (RV-10). */
|
|
127
|
+
private confirmCharter;
|
|
128
|
+
/** Subscribe to the governance event stream (works before the engine exists). */
|
|
129
|
+
subscribe(listener: GovernanceEventListener): () => void;
|
|
130
|
+
snapshot(): GovernanceSnapshot | null;
|
|
131
|
+
/** Run + ledger for the `/gov board` overlay (read-only views of live memory). */
|
|
132
|
+
boardInput(): {
|
|
133
|
+
run: RunState;
|
|
134
|
+
ledger: LedgerState;
|
|
135
|
+
boardPath: string;
|
|
136
|
+
} | null;
|
|
137
|
+
abortStage(): void;
|
|
138
|
+
/**
|
|
139
|
+
* Recent runs for `/gov runs`, newest first. Opens the store lazily; `[]` when governance
|
|
140
|
+
* is unusable. `RunStore.listRuns()` is ALREADY newest-first: the previous
|
|
141
|
+
* `slice(-10).reverse()` therefore returned the OLDEST ten once there were more than ten.
|
|
142
|
+
*/
|
|
143
|
+
listRuns(): Promise<GovernanceRunRow[]>;
|
|
144
|
+
/** `/gov report [runId]`: a stored report, read-only (governance-mode-v1-1 7.2). */
|
|
145
|
+
readReport(runId?: string): Promise<RunReportResult | {
|
|
146
|
+
kind: 'no_run';
|
|
147
|
+
}>;
|
|
148
|
+
/** `/gov pause`: the engine's verdict, or `no_run` when no engine was ever created. */
|
|
149
|
+
requestPause(): Promise<PauseOutcome>;
|
|
150
|
+
cancelPause(): boolean;
|
|
151
|
+
/** The dispatch-plan line for the live run (`/gov status`), or null without one. */
|
|
152
|
+
dispatchPlanText(): string | null;
|
|
153
|
+
/**
|
|
154
|
+
* The run `/gov resume [runId]` should hand to the orchestrator: the named one when it
|
|
155
|
+
* exists, else the newest resumable run for this directory. `null` when there is none.
|
|
156
|
+
*/
|
|
157
|
+
findResumableRun(runId?: string): Promise<string | null>;
|
|
158
|
+
dispose(): void;
|
|
159
|
+
private buildLazyApi;
|
|
160
|
+
/** Posture helper for callers that only hold the gate mode. */
|
|
161
|
+
currentPosture(mode: AgentMode): Posture;
|
|
162
|
+
}
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GovernanceWiring - the host side of governance mode (governance-mode 3.2, 14.2).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`agent/` is inside the glyph scanner's scope).
|
|
5
|
+
*
|
|
6
|
+
* `AgentController` is 3000+ lines over the repo's file limit, so every governance decision
|
|
7
|
+
* it needs lives here and the controller only forwards: registration, availability, the
|
|
8
|
+
* Orchestrator and role prompt blocks, the PLAN+GOVERN interview gate, the approved-plan
|
|
9
|
+
* stash, the engine's lifecycle, the child tool hooks. The posture TRANSITION rule is a
|
|
10
|
+
* pure function in `governance/posture-transition.ts` for the same reason.
|
|
11
|
+
*
|
|
12
|
+
* TWO LAYERS OF "CAN I USE THIS" (D-22 / RV-1 / RV-14), and they are not the same thing:
|
|
13
|
+
*
|
|
14
|
+
* - REGISTERED is decided ONCE, in the constructor, from inputs that cannot change
|
|
15
|
+
* afterwards: team tools exist, `governance.enabled`, the env kill switch, and the
|
|
16
|
+
* channel's STATIC `neverPrompts` (NOT the live `canPrompt()`: at construction the App
|
|
17
|
+
* has not attached its human channel yet, so `canPrompt()` is false for every TUI
|
|
18
|
+
* session and keying off it would register nothing, silently, forever). The tool array
|
|
19
|
+
* is immutable after `new Agent(...)`, so this is irreversible for the session.
|
|
20
|
+
* - AVAILABLE is live: registered AND `team.enabled` AND an API key. It is what `/gov`,
|
|
21
|
+
* `doctor` and the key cycle ask.
|
|
22
|
+
*/
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { clampGovernanceConfig, isGovernanceEnvDisabled, } from '../config/governance-config.js';
|
|
25
|
+
import { getGovernanceDir } from '../config/app-paths.js';
|
|
26
|
+
import { RESTART_REQUIRED_TEXT, unavailableReasonText, } from '../governance/copy.js';
|
|
27
|
+
import { createGovernanceEngine } from '../governance/engine/engine.js';
|
|
28
|
+
import { estimateForRun } from '../governance/engine/estimate.js';
|
|
29
|
+
import { formatDispatchEstimate } from '../governance/copy.js';
|
|
30
|
+
import { GOV_CHILD_TOOL_NAMES, GOV_LEAD_TOOL_NAMES, GOV_LIMITS } from '../governance/limits.js';
|
|
31
|
+
import { composePosture, isGovernancePosture, POSTURES_CLASSIC, POSTURES_FULL, } from '../governance/posture.js';
|
|
32
|
+
import { buildPlanGovernAddendum } from '../governance/prompts/plan-addendum.js';
|
|
33
|
+
import { buildOrchestratorBlock } from '../governance/prompts/orchestrator-block.js';
|
|
34
|
+
import { PLAN_INTERVIEW_TOPICS, govAskRoundsRefusal } from '../governance/prompts/refusals.js';
|
|
35
|
+
import { buildRoleBlock } from '../governance/prompts/role-blocks.js';
|
|
36
|
+
import { readRunReport } from '../governance/report-reader.js';
|
|
37
|
+
import { ArtifactStore } from '../governance/store/artifacts.js';
|
|
38
|
+
import { RunStore } from '../governance/store/run-store.js';
|
|
39
|
+
import { buildChildTools } from '../governance/tools/child-tools.js';
|
|
40
|
+
import { applyInspectorToolPolicy, withGovernanceReadGuard } from '../governance/tools/guard.js';
|
|
41
|
+
import { makeGovDispatchTool, makeGovRunTool } from '../governance/tools/lead-tools.js';
|
|
42
|
+
import { TEAM_LIMITS } from '../team/limits.js';
|
|
43
|
+
/** Keyword probes that map an answered question onto the fixed interview topics. */
|
|
44
|
+
const TOPIC_PROBES = [
|
|
45
|
+
/goal|success|outcome|objective|criteria/i,
|
|
46
|
+
/scope|in scope|out of scope|include|exclude/i,
|
|
47
|
+
/constraint|stack|compat|performance|style|forbid/i,
|
|
48
|
+
/quality|test|security|review|strict/i,
|
|
49
|
+
/team|parallel|budget|cost|work package|\bwp/i,
|
|
50
|
+
/risk|depend|decision/i,
|
|
51
|
+
];
|
|
52
|
+
function governanceOf(config) {
|
|
53
|
+
return clampGovernanceConfig(config.governance);
|
|
54
|
+
}
|
|
55
|
+
/** Render the approved plan as the run specification (<= `specChars`). */
|
|
56
|
+
export function renderPlanAsSpec(plan) {
|
|
57
|
+
const lines = [`# ${plan.title}`, '', plan.summary, ''];
|
|
58
|
+
plan.steps.forEach((step, i) => {
|
|
59
|
+
lines.push(`${i + 1}. ${step.title}`);
|
|
60
|
+
if (step.detail)
|
|
61
|
+
lines.push(` ${step.detail.replace(/\n/g, '\n ')}`);
|
|
62
|
+
});
|
|
63
|
+
if (plan.filesTouched.length > 0)
|
|
64
|
+
lines.push('', `Files: ${plan.filesTouched.join(', ')}`);
|
|
65
|
+
if (plan.risks.length > 0)
|
|
66
|
+
lines.push('', 'Risks:', ...plan.risks.map((r) => `- ${r}`));
|
|
67
|
+
if (plan.openQuestions.length > 0) {
|
|
68
|
+
lines.push('', 'Open questions:', ...plan.openQuestions.map((q) => `- ${q}`));
|
|
69
|
+
}
|
|
70
|
+
const text = lines.join('\n');
|
|
71
|
+
return text.length > GOV_LIMITS.specChars ? text.slice(0, GOV_LIMITS.specChars) : text;
|
|
72
|
+
}
|
|
73
|
+
const RUNS_LISTED = 10;
|
|
74
|
+
export class GovernanceWiring {
|
|
75
|
+
deps;
|
|
76
|
+
registered;
|
|
77
|
+
enabledAtStart;
|
|
78
|
+
env;
|
|
79
|
+
governOn = false;
|
|
80
|
+
pendingPosture = null;
|
|
81
|
+
engine = null;
|
|
82
|
+
store = null;
|
|
83
|
+
listeners = new Set();
|
|
84
|
+
engineUnsubscribe = null;
|
|
85
|
+
approvedPlan = null;
|
|
86
|
+
answeredRounds = 0;
|
|
87
|
+
coveredTopics = new Set();
|
|
88
|
+
/** A lazy facade: tools are built before the engine exists and the engine needs the runtime. */
|
|
89
|
+
api;
|
|
90
|
+
constructor(deps) {
|
|
91
|
+
this.deps = deps;
|
|
92
|
+
this.env = deps.env ?? process.env;
|
|
93
|
+
const gov = governanceOf(deps.config());
|
|
94
|
+
this.enabledAtStart = gov.enabled;
|
|
95
|
+
// STATIC, evaluated once (D-22). `neverPrompts` is read off the gate object, which is
|
|
96
|
+
// exactly what `createPlanTools` registers on.
|
|
97
|
+
this.registered =
|
|
98
|
+
deps.teamRegistered &&
|
|
99
|
+
gov.enabled &&
|
|
100
|
+
!isGovernanceEnvDisabled(this.env) &&
|
|
101
|
+
deps.gate.neverPrompts !== true;
|
|
102
|
+
this.api = this.buildLazyApi();
|
|
103
|
+
}
|
|
104
|
+
// -------------------------------------------------------------------------
|
|
105
|
+
// Registration and availability
|
|
106
|
+
// -------------------------------------------------------------------------
|
|
107
|
+
/** Whether governance tools were registered at construction. Never changes. */
|
|
108
|
+
isRegistered() {
|
|
109
|
+
return this.registered;
|
|
110
|
+
}
|
|
111
|
+
/** The live answer, with the precise reason when it is no. */
|
|
112
|
+
isAvailable() {
|
|
113
|
+
const config = this.deps.config();
|
|
114
|
+
const gov = governanceOf(config);
|
|
115
|
+
if (!this.registered)
|
|
116
|
+
return { ok: false, reason: this.unregisteredReason(gov) };
|
|
117
|
+
if (isGovernanceEnvDisabled(this.env))
|
|
118
|
+
return { ok: false, reason: 'disabled_env' };
|
|
119
|
+
if (!gov.enabled)
|
|
120
|
+
return { ok: false, reason: 'disabled_config' };
|
|
121
|
+
if (!config.team.enabled)
|
|
122
|
+
return { ok: false, reason: 'team_off' };
|
|
123
|
+
if (!this.deps.hasApiKey())
|
|
124
|
+
return { ok: false, reason: 'no_api_key' };
|
|
125
|
+
return { ok: true };
|
|
126
|
+
}
|
|
127
|
+
unregisteredReason(gov) {
|
|
128
|
+
if (isGovernanceEnvDisabled(this.env))
|
|
129
|
+
return 'disabled_env';
|
|
130
|
+
if (this.deps.gate.neverPrompts === true)
|
|
131
|
+
return 'headless';
|
|
132
|
+
if (!this.deps.teamRegistered)
|
|
133
|
+
return 'no_team';
|
|
134
|
+
// Disabled at start. Now on => the tool array cannot grow: restart. Still off => config.
|
|
135
|
+
return !this.enabledAtStart && gov.enabled ? 'restart_required' : 'disabled_config';
|
|
136
|
+
}
|
|
137
|
+
/** The exact sentence for the current unavailability, or null when available. */
|
|
138
|
+
unavailableText() {
|
|
139
|
+
const a = this.isAvailable();
|
|
140
|
+
if (a.ok)
|
|
141
|
+
return null;
|
|
142
|
+
return a.reason === 'restart_required' ? RESTART_REQUIRED_TEXT : unavailableReasonText(a.reason);
|
|
143
|
+
}
|
|
144
|
+
/** Shift+Tab cycle: four states when usable and `governance.shiftTab`, else the classic two. */
|
|
145
|
+
postureCycle() {
|
|
146
|
+
const gov = governanceOf(this.deps.config());
|
|
147
|
+
return this.isAvailable().ok && gov.shiftTab ? POSTURES_FULL : POSTURES_CLASSIC;
|
|
148
|
+
}
|
|
149
|
+
// -------------------------------------------------------------------------
|
|
150
|
+
// Posture state (the gate mode lives in the controller)
|
|
151
|
+
// -------------------------------------------------------------------------
|
|
152
|
+
isGovernOn() {
|
|
153
|
+
return this.governOn;
|
|
154
|
+
}
|
|
155
|
+
setGovernOn(on) {
|
|
156
|
+
this.governOn = on;
|
|
157
|
+
}
|
|
158
|
+
getPendingPosture() {
|
|
159
|
+
return this.pendingPosture;
|
|
160
|
+
}
|
|
161
|
+
setPendingPosture(pending) {
|
|
162
|
+
this.pendingPosture = pending;
|
|
163
|
+
}
|
|
164
|
+
/** Entering PLAN+GOVERN starts a fresh interview and drops any older plan (9.3). */
|
|
165
|
+
onPostureAdopted(posture, previous) {
|
|
166
|
+
if (posture === 'plan_govern' && previous !== 'plan_govern') {
|
|
167
|
+
this.answeredRounds = 0;
|
|
168
|
+
this.coveredTopics.clear();
|
|
169
|
+
this.approvedPlan = null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
isStageRunning() {
|
|
173
|
+
return this.engine?.isStageRunning() ?? false;
|
|
174
|
+
}
|
|
175
|
+
// -------------------------------------------------------------------------
|
|
176
|
+
// Prompt blocks
|
|
177
|
+
// -------------------------------------------------------------------------
|
|
178
|
+
/**
|
|
179
|
+
* `''` unless governance is on, registered and available (closed state: byte-equal).
|
|
180
|
+
*
|
|
181
|
+
* `config` is the config the prompt is being composed FOR. A settings save composes the
|
|
182
|
+
* prompt from the candidate BEFORE it becomes live, so reading the live config here would
|
|
183
|
+
* quote the limits from before the save (governance-mode-v1-1 IF-3).
|
|
184
|
+
*/
|
|
185
|
+
orchestratorBlock(posture, config) {
|
|
186
|
+
if (!this.registered || !isGovernancePosture(posture) || !this.isAvailable().ok)
|
|
187
|
+
return '';
|
|
188
|
+
const gov = governanceOf(config ?? this.deps.config());
|
|
189
|
+
return buildOrchestratorBlock({
|
|
190
|
+
posture,
|
|
191
|
+
limits: {
|
|
192
|
+
maxWorkPackages: gov.maxWorkPackages,
|
|
193
|
+
maxPhases: gov.maxPhases,
|
|
194
|
+
maxRework: gov.maxRework,
|
|
195
|
+
maxFixCycles: gov.maxFixCycles,
|
|
196
|
+
budgetTokens: gov.budgetTokens,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/** `''` unless PLAN+GOVERN. */
|
|
201
|
+
planAddendum(posture, config) {
|
|
202
|
+
if (!this.registered || posture !== 'plan_govern' || !this.isAvailable().ok)
|
|
203
|
+
return '';
|
|
204
|
+
return buildPlanGovernAddendum(governanceOf(config ?? this.deps.config()).minAskRounds);
|
|
205
|
+
}
|
|
206
|
+
roleBlockFor(spec) {
|
|
207
|
+
const ext = spec.governance;
|
|
208
|
+
if (ext === undefined)
|
|
209
|
+
return '';
|
|
210
|
+
return buildRoleBlock(ext, { integrationCommands: ext.integrationCommands ?? [] });
|
|
211
|
+
}
|
|
212
|
+
// -------------------------------------------------------------------------
|
|
213
|
+
// PLAN+GOVERN interview and approval
|
|
214
|
+
// -------------------------------------------------------------------------
|
|
215
|
+
/** Called from the controller's `noteAnswered`: one real, answered, non-duplicate round. */
|
|
216
|
+
noteAnsweredRound(questions = []) {
|
|
217
|
+
if (!this.governOn || this.deps.getGateMode() !== 'plan')
|
|
218
|
+
return;
|
|
219
|
+
this.answeredRounds += 1;
|
|
220
|
+
const text = questions.map((q) => `${q.header} ${q.question}`).join(' ');
|
|
221
|
+
TOPIC_PROBES.forEach((probe, index) => {
|
|
222
|
+
if (probe.test(text))
|
|
223
|
+
this.coveredTopics.add(index);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
answeredRoundCount() {
|
|
227
|
+
return this.answeredRounds;
|
|
228
|
+
}
|
|
229
|
+
/** `submit_plan` pre-check: a refusal text, or null to proceed. */
|
|
230
|
+
beforeSubmitPlan() {
|
|
231
|
+
if (!this.governOn || this.deps.getGateMode() !== 'plan')
|
|
232
|
+
return null;
|
|
233
|
+
const need = governanceOf(this.deps.config()).minAskRounds;
|
|
234
|
+
if (need <= 0 || !this.deps.gate.canPrompt())
|
|
235
|
+
return null;
|
|
236
|
+
if (this.answeredRounds >= need)
|
|
237
|
+
return null;
|
|
238
|
+
const uncovered = PLAN_INTERVIEW_TOPICS.filter((_, i) => !this.coveredTopics.has(i));
|
|
239
|
+
return govAskRoundsRefusal(this.answeredRounds, need, uncovered);
|
|
240
|
+
}
|
|
241
|
+
/** Stash the approved plan when governance is on. Returns which way the session goes. */
|
|
242
|
+
handlePlanApproved(plan) {
|
|
243
|
+
if (!this.governOn)
|
|
244
|
+
return 'build';
|
|
245
|
+
this.approvedPlan = renderPlanAsSpec(plan);
|
|
246
|
+
return 'govern';
|
|
247
|
+
}
|
|
248
|
+
/** `gov_run start` consumes the stash exactly once. */
|
|
249
|
+
takeApprovedPlan() {
|
|
250
|
+
const plan = this.approvedPlan;
|
|
251
|
+
this.approvedPlan = null;
|
|
252
|
+
return plan;
|
|
253
|
+
}
|
|
254
|
+
// -------------------------------------------------------------------------
|
|
255
|
+
// Tools
|
|
256
|
+
// -------------------------------------------------------------------------
|
|
257
|
+
/** `[]` when not registered, which is the byte-identical closed path. */
|
|
258
|
+
leadTools() {
|
|
259
|
+
if (!this.registered)
|
|
260
|
+
return [];
|
|
261
|
+
return [makeGovRunTool(this.api), makeGovDispatchTool(this.api)];
|
|
262
|
+
}
|
|
263
|
+
leadPolicyExempt() {
|
|
264
|
+
return GOV_LEAD_TOOL_NAMES;
|
|
265
|
+
}
|
|
266
|
+
childPolicyExempt() {
|
|
267
|
+
return GOV_CHILD_TOOL_NAMES;
|
|
268
|
+
}
|
|
269
|
+
/** Role tools for a governed child; `[]` for anything else. */
|
|
270
|
+
childToolsFor(spec) {
|
|
271
|
+
const ext = spec.governance;
|
|
272
|
+
if (ext === undefined)
|
|
273
|
+
return [];
|
|
274
|
+
const identity = { id: ext.identity, role: ext.role, generation: ext.generation };
|
|
275
|
+
return buildChildTools(ext.role, this.api, identity, { getCwd: () => this.deps.getCwd() });
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Narrow a governed child's finished tool array: the inspector loses its write tools and
|
|
279
|
+
* gets the restricted shell, and every read-class tool gets the store tripwire.
|
|
280
|
+
*
|
|
281
|
+
* This runs on the factory's RESULT (see `SubagentDeps.narrowToolsFor`) because it has to
|
|
282
|
+
* see the built-in `bash` to replace it, while the role tools had to go IN through the
|
|
283
|
+
* factory. A non-governed spec comes back untouched.
|
|
284
|
+
*/
|
|
285
|
+
narrowToolsFor(spec, tools) {
|
|
286
|
+
const ext = spec.governance;
|
|
287
|
+
if (ext === undefined)
|
|
288
|
+
return tools;
|
|
289
|
+
const root = getGovernanceDir();
|
|
290
|
+
const base = ext.role === 'INSP'
|
|
291
|
+
? applyInspectorToolPolicy(tools, { integrationCommands: ext.integrationCommands ?? [] })
|
|
292
|
+
: tools;
|
|
293
|
+
return base.map((tool) => withGovernanceReadGuard(tool, root));
|
|
294
|
+
}
|
|
295
|
+
// -------------------------------------------------------------------------
|
|
296
|
+
// Engine lifecycle and events
|
|
297
|
+
// -------------------------------------------------------------------------
|
|
298
|
+
/** The engine is created on first use: a session that never governs never opens a store. */
|
|
299
|
+
ensureEngine() {
|
|
300
|
+
if (this.engine !== null)
|
|
301
|
+
return this.engine;
|
|
302
|
+
const runtime = this.deps.getRuntime();
|
|
303
|
+
if (runtime === null)
|
|
304
|
+
throw new Error('governance: no team runtime in this session');
|
|
305
|
+
const store = new RunStore({ root: getGovernanceDir() });
|
|
306
|
+
this.store = store;
|
|
307
|
+
const engineDeps = {
|
|
308
|
+
runtime,
|
|
309
|
+
store,
|
|
310
|
+
artifacts: new ArtifactStore(store),
|
|
311
|
+
getConfig: () => governanceOf(this.deps.config()),
|
|
312
|
+
getTeamCeiling: () => Math.max(1, Math.min(this.deps.config().team.maxSubagents, TEAM_LIMITS.hardMaxSubagents)),
|
|
313
|
+
getCwd: () => this.deps.getCwd(),
|
|
314
|
+
getSessionId: () => this.deps.getSessionId?.(),
|
|
315
|
+
withPausedWatchdog: (fn) => this.deps.withPausedWatchdog(fn),
|
|
316
|
+
hasApiKey: () => this.deps.hasApiKey(),
|
|
317
|
+
modelCost: () => this.deps.modelCost(),
|
|
318
|
+
takeApprovedPlan: () => this.takeApprovedPlan(),
|
|
319
|
+
confirmCharter: (charter, signal) => this.confirmCharter(charter, signal),
|
|
320
|
+
now: this.deps.now ?? Date.now,
|
|
321
|
+
};
|
|
322
|
+
this.engine = createGovernanceEngine(engineDeps);
|
|
323
|
+
this.engineUnsubscribe = this.engine.subscribe((event) => {
|
|
324
|
+
for (const listener of [...this.listeners])
|
|
325
|
+
listener(event);
|
|
326
|
+
});
|
|
327
|
+
return this.engine;
|
|
328
|
+
}
|
|
329
|
+
/** `confirmCharter` through the EXISTING plan-review overlay; no new overlay kind (RV-10). */
|
|
330
|
+
async confirmCharter(charter, signal) {
|
|
331
|
+
if (!this.deps.gate.canPrompt())
|
|
332
|
+
return { decision: 'approved' };
|
|
333
|
+
const plan = {
|
|
334
|
+
title: charter.title,
|
|
335
|
+
summary: charter.summary,
|
|
336
|
+
steps: charter.steps,
|
|
337
|
+
filesTouched: charter.filesTouched,
|
|
338
|
+
risks: charter.risks,
|
|
339
|
+
openQuestions: [],
|
|
340
|
+
};
|
|
341
|
+
const response = await this.deps.withHumanWait(() => this.deps.gate.request({ kind: 'plan', plan }, signal));
|
|
342
|
+
if (response === null || response.kind !== 'planDecision')
|
|
343
|
+
return null;
|
|
344
|
+
return { decision: response.decision, feedback: response.feedback };
|
|
345
|
+
}
|
|
346
|
+
/** Subscribe to the governance event stream (works before the engine exists). */
|
|
347
|
+
subscribe(listener) {
|
|
348
|
+
this.listeners.add(listener);
|
|
349
|
+
return () => {
|
|
350
|
+
this.listeners.delete(listener);
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
snapshot() {
|
|
354
|
+
return this.engine?.snapshot() ?? null;
|
|
355
|
+
}
|
|
356
|
+
/** Run + ledger for the `/gov board` overlay (read-only views of live memory). */
|
|
357
|
+
boardInput() {
|
|
358
|
+
const core = this.engine?.internals;
|
|
359
|
+
if (core === undefined || core.run === null)
|
|
360
|
+
return null;
|
|
361
|
+
return {
|
|
362
|
+
run: core.run,
|
|
363
|
+
ledger: core.ledger,
|
|
364
|
+
boardPath: join(getGovernanceDir(), core.run.runId, 'board.md'),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
abortStage() {
|
|
368
|
+
this.engine?.abortStage();
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Recent runs for `/gov runs`, newest first. Opens the store lazily; `[]` when governance
|
|
372
|
+
* is unusable. `RunStore.listRuns()` is ALREADY newest-first: the previous
|
|
373
|
+
* `slice(-10).reverse()` therefore returned the OLDEST ten once there were more than ten.
|
|
374
|
+
*/
|
|
375
|
+
async listRuns() {
|
|
376
|
+
if (!this.registered)
|
|
377
|
+
return [];
|
|
378
|
+
const store = this.store ?? new RunStore({ root: getGovernanceDir() });
|
|
379
|
+
const rows = await store.listRuns();
|
|
380
|
+
return rows.slice(0, RUNS_LISTED).map((r) => ({
|
|
381
|
+
runId: r.runId,
|
|
382
|
+
goal: r.goal,
|
|
383
|
+
status: r.status,
|
|
384
|
+
updatedAt: r.updatedAt,
|
|
385
|
+
...(r.digest !== undefined ? { digest: r.digest } : {}),
|
|
386
|
+
}));
|
|
387
|
+
}
|
|
388
|
+
/** `/gov report [runId]`: a stored report, read-only (governance-mode-v1-1 7.2). */
|
|
389
|
+
async readReport(runId) {
|
|
390
|
+
if (!this.registered)
|
|
391
|
+
return { kind: 'no_run' };
|
|
392
|
+
const store = this.store ?? new RunStore({ root: getGovernanceDir() });
|
|
393
|
+
const wanted = (runId ?? '').trim();
|
|
394
|
+
if (wanted !== '')
|
|
395
|
+
return readRunReport(store, wanted);
|
|
396
|
+
const latest = await store.latestForCwd(this.deps.getCwd());
|
|
397
|
+
return latest === null ? { kind: 'no_run' } : readRunReport(store, latest.runId);
|
|
398
|
+
}
|
|
399
|
+
/** `/gov pause`: the engine's verdict, or `no_run` when no engine was ever created. */
|
|
400
|
+
requestPause() {
|
|
401
|
+
return this.engine?.requestPause() ?? Promise.resolve('no_run');
|
|
402
|
+
}
|
|
403
|
+
cancelPause() {
|
|
404
|
+
return this.engine?.cancelPause() ?? false;
|
|
405
|
+
}
|
|
406
|
+
/** The dispatch-plan line for the live run (`/gov status`), or null without one. */
|
|
407
|
+
dispatchPlanText() {
|
|
408
|
+
const run = this.engine?.internals.run ?? null;
|
|
409
|
+
if (run === null)
|
|
410
|
+
return null;
|
|
411
|
+
const ceiling = Math.max(1, Math.min(this.deps.config().team.maxSubagents, TEAM_LIMITS.hardMaxSubagents));
|
|
412
|
+
return formatDispatchEstimate(estimateForRun(run, ceiling)) || null;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* The run `/gov resume [runId]` should hand to the orchestrator: the named one when it
|
|
416
|
+
* exists, else the newest resumable run for this directory. `null` when there is none.
|
|
417
|
+
*/
|
|
418
|
+
async findResumableRun(runId) {
|
|
419
|
+
if (!this.registered)
|
|
420
|
+
return null;
|
|
421
|
+
const store = this.store ?? new RunStore({ root: getGovernanceDir() });
|
|
422
|
+
if (runId !== undefined && runId !== '') {
|
|
423
|
+
const known = (await store.listRuns()).find((r) => r.runId === runId);
|
|
424
|
+
return known ? known.runId : null;
|
|
425
|
+
}
|
|
426
|
+
const latest = await store.latestResumable(this.deps.getCwd());
|
|
427
|
+
return latest ? latest.runId : null;
|
|
428
|
+
}
|
|
429
|
+
dispose() {
|
|
430
|
+
this.engineUnsubscribe?.();
|
|
431
|
+
this.engineUnsubscribe = null;
|
|
432
|
+
this.engine?.dispose();
|
|
433
|
+
this.engine = null;
|
|
434
|
+
this.listeners.clear();
|
|
435
|
+
}
|
|
436
|
+
// -------------------------------------------------------------------------
|
|
437
|
+
// Lazy facade
|
|
438
|
+
// -------------------------------------------------------------------------
|
|
439
|
+
buildLazyApi() {
|
|
440
|
+
const e = () => this.ensureEngine();
|
|
441
|
+
return {
|
|
442
|
+
start: (input, signal) => e().start(input, signal),
|
|
443
|
+
status: () => e().status(),
|
|
444
|
+
resume: (runId) => e().resume(runId),
|
|
445
|
+
dispatchStage: (input, signal) => e().dispatchStage(input, signal),
|
|
446
|
+
close: (summary) => e().close(summary),
|
|
447
|
+
abandon: (reason) => e().abandon(reason),
|
|
448
|
+
resolve: (input) => e().resolve(input),
|
|
449
|
+
snapshot: () => this.snapshot(),
|
|
450
|
+
isStageRunning: () => this.isStageRunning(),
|
|
451
|
+
abortStage: () => this.abortStage(),
|
|
452
|
+
subscribe: (listener) => this.subscribe(listener),
|
|
453
|
+
dispose: () => this.dispose(),
|
|
454
|
+
submitWork: (id, input) => e().submitWork(id, input),
|
|
455
|
+
submitReview: (id, input) => e().submitReview(id, input),
|
|
456
|
+
submitTest: (id, input) => e().submitTest(id, input),
|
|
457
|
+
inspectorApply: (input) => e().inspectorApply(input),
|
|
458
|
+
inspectorRule: (input) => e().inspectorRule(input),
|
|
459
|
+
inspectorSettle: (input) => e().inspectorSettle(input),
|
|
460
|
+
standings: () => e().standings(),
|
|
461
|
+
readArtifact: (id, input) => e().readArtifact(id, input),
|
|
462
|
+
requestView: (id, input) => e().requestView(id, input),
|
|
463
|
+
scan: (id, input) => e().scan(id, input),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/** Posture helper for callers that only hold the gate mode. */
|
|
467
|
+
currentPosture(mode) {
|
|
468
|
+
return composePosture(mode, this.governOn);
|
|
469
|
+
}
|
|
470
|
+
}
|