harnery 0.29.0 → 0.31.0
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/README.md +4 -4
- package/dist/commander.d.ts +8 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +14 -13
- package/dist/commands/adapter.d.ts +12 -0
- package/dist/commands/adapter.d.ts.map +1 -0
- package/dist/commands/{harness.js → adapter.js} +35 -35
- package/dist/commands/agents.d.ts +3 -2
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +61 -60
- package/dist/commands/artifacts.d.ts +4 -0
- package/dist/commands/artifacts.d.ts.map +1 -0
- package/dist/commands/artifacts.js +143 -0
- package/dist/commands/backup.d.ts +1 -1
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +5 -2
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +310 -7
- package/dist/commands/checkpoint.d.ts +12 -0
- package/dist/commands/checkpoint.d.ts.map +1 -0
- package/dist/commands/checkpoint.js +162 -0
- package/dist/commands/context.d.ts +2 -2
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +3 -151
- package/dist/commands/decision.d.ts.map +1 -1
- package/dist/commands/decision.js +31 -7
- package/dist/commands/deinit.d.ts +2 -2
- package/dist/commands/deinit.js +15 -15
- package/dist/commands/doctor.js +23 -23
- package/dist/commands/governor.d.ts +4 -0
- package/dist/commands/governor.d.ts.map +1 -0
- package/dist/commands/{supervisor.js → governor.js} +133 -108
- package/dist/commands/init.d.ts +12 -12
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +44 -30
- package/dist/commands/{session.d.ts → journal.d.ts} +2 -2
- package/dist/commands/journal.d.ts.map +1 -0
- package/dist/commands/{scratch.js → journal.js} +47 -47
- package/dist/commands/policy.js +1 -1
- package/dist/commands/presence.js +2 -2
- package/dist/commands/sync.d.ts +1 -1
- package/dist/commands/sync.js +3 -3
- package/dist/commands/tunnel.d.ts +16 -0
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +193 -23
- package/dist/commands/web-fetch.js +1 -1
- package/dist/commands/web.d.ts +19 -0
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +51 -0
- package/dist/commands/work.d.ts +2 -1
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +55 -25
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +35 -28
- package/dist/core/{harnesses → adapters}/attest-projection.d.ts +2 -2
- package/dist/core/adapters/attest-projection.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/attest.d.ts +16 -16
- package/dist/core/adapters/attest.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/attest.js +9 -9
- package/dist/core/{harnesses → adapters}/attestation.d.ts +16 -16
- package/dist/core/adapters/attestation.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/attestation.js +20 -20
- package/dist/core/{harnesses → adapters}/bench.d.ts +12 -12
- package/dist/core/adapters/bench.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/bench.js +12 -12
- package/dist/core/adapters/index.d.ts +13 -0
- package/dist/core/adapters/index.d.ts.map +1 -0
- package/dist/core/adapters/index.js +6 -0
- package/dist/core/{harnesses → adapters}/profiles.d.ts +11 -11
- package/dist/core/adapters/profiles.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/profiles.js +11 -11
- package/dist/core/adapters/registry.d.ts +15 -0
- package/dist/core/adapters/registry.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/registry.js +11 -11
- package/dist/core/{harnesses → adapters}/types.d.ts +28 -23
- package/dist/core/adapters/types.d.ts.map +1 -0
- package/dist/core/{harnesses → adapters}/types.js +1 -1
- package/dist/core/agents/canonical-emit.d.ts +4 -4
- package/dist/core/agents/canonical-emit.js +5 -5
- package/dist/core/agents/cli-emit.js +1 -1
- package/dist/core/agents/cli.js +30 -26
- package/dist/core/agents/codex-replay.d.ts +1 -1
- package/dist/core/agents/codex-replay.js +6 -6
- package/dist/core/agents/coord-client.d.ts +21 -2
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +122 -27
- package/dist/core/agents/events/consume.d.ts +1 -1
- package/dist/core/agents/events/emit.d.ts +5 -6
- package/dist/core/agents/events/emit.d.ts.map +1 -1
- package/dist/core/agents/events/emit.js +2 -2
- package/dist/core/agents/render/prompt-context.d.ts +21 -9
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +82 -30
- package/dist/core/agents/render/session-context.d.ts +1 -1
- package/dist/core/agents/render/session-context.js +5 -5
- package/dist/core/agents/rules/claim-conflict.d.ts +2 -6
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +1 -1
- package/dist/core/agents/rules/commit-conflict.js +98 -22
- package/dist/core/agents/rules/stop-hook.d.ts +9 -9
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +23 -5
- package/dist/core/agents/rules/verdict.d.ts +14 -0
- package/dist/core/agents/rules/verdict.d.ts.map +1 -0
- package/dist/core/agents/rules/verdict.js +1 -0
- package/dist/core/agents/session-events.js +3 -3
- package/dist/core/agents/state/council.d.ts +1 -1
- package/dist/core/agents/state/council.js +1 -1
- package/dist/core/agents/state/heartbeat-projector.d.ts +3 -2
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +14 -10
- package/dist/core/agents/state/heartbeat-writer.d.ts +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +12 -12
- package/dist/core/agents/state/{scratch.d.ts → journal.d.ts} +7 -7
- package/dist/core/agents/state/{scratch.d.ts.map → journal.d.ts.map} +1 -1
- package/dist/core/agents/state/{scratch.js → journal.js} +12 -12
- package/dist/core/agents/state/pidmap.d.ts +28 -4
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +130 -15
- package/dist/core/agents/state/proc-start.d.ts +95 -0
- package/dist/core/agents/state/proc-start.d.ts.map +1 -0
- package/dist/core/agents/state/proc-start.js +199 -0
- package/dist/core/agents/state/shell-mutation.d.ts +1 -1
- package/dist/core/agents/state/shell-mutation.js +1 -1
- package/dist/core/agents/state/stale-sweep.js +7 -7
- package/dist/core/artifacts/index.d.ts +86 -0
- package/dist/core/artifacts/index.d.ts.map +1 -0
- package/dist/core/artifacts/index.js +421 -0
- package/dist/core/config.d.ts +9 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +26 -2
- package/dist/core/context/index.d.ts +12 -12
- package/dist/core/context/index.js +10 -10
- package/dist/core/governor/adapter-spread.d.ts +52 -0
- package/dist/core/governor/adapter-spread.d.ts.map +1 -0
- package/dist/core/governor/adapter-spread.js +52 -0
- package/dist/core/governor/index.d.ts +7 -0
- package/dist/core/governor/index.d.ts.map +1 -0
- package/dist/core/governor/index.js +6 -0
- package/dist/core/governor/plan-api.d.ts +38 -0
- package/dist/core/governor/plan-api.d.ts.map +1 -0
- package/dist/core/governor/plan-api.js +83 -0
- package/dist/core/governor/plan-read.d.ts +5 -0
- package/dist/core/governor/plan-read.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/plan-read.js +71 -51
- package/dist/core/governor/plan-types.d.ts +181 -0
- package/dist/core/governor/plan-types.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/plan-types.js +4 -4
- package/dist/core/governor/planning.d.ts +48 -0
- package/dist/core/governor/planning.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/planning.js +143 -94
- package/dist/core/governor/read.d.ts +6 -0
- package/dist/core/governor/read.d.ts.map +1 -0
- package/dist/core/governor/read.js +4 -0
- package/dist/core/governor/runner.d.ts +37 -0
- package/dist/core/governor/runner.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/runner.js +15 -15
- package/dist/core/governor/service-read.d.ts +6 -0
- package/dist/core/governor/service-read.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/service-read.js +12 -12
- package/dist/core/governor/service.d.ts +114 -0
- package/dist/core/governor/service.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/service.js +57 -57
- package/dist/core/governor/state.d.ts +116 -0
- package/dist/core/governor/state.d.ts.map +1 -0
- package/dist/core/{supervisor → governor}/state.js +290 -116
- package/dist/core/hooks/adapter/detect.d.ts +9 -0
- package/dist/core/hooks/{harness → adapter}/detect.d.ts.map +1 -1
- package/dist/core/hooks/{harness → adapter}/detect.js +8 -8
- package/dist/core/hooks/{harness → adapter}/events.d.ts +15 -14
- package/dist/core/hooks/adapter/events.d.ts.map +1 -0
- package/dist/core/hooks/{harness → adapter}/events.js +5 -5
- package/dist/core/hooks/{harness → adapter}/output.d.ts +20 -14
- package/dist/core/hooks/adapter/output.d.ts.map +1 -0
- package/dist/core/hooks/{harness → adapter}/output.js +30 -22
- package/dist/core/hooks/{harness → adapter}/parse.d.ts +5 -5
- package/dist/core/hooks/{harness → adapter}/parse.d.ts.map +1 -1
- package/dist/core/hooks/{harness → adapter}/parse.js +4 -4
- package/dist/core/hooks/{harness → adapter}/wiring.d.ts +15 -15
- package/dist/core/hooks/{harness → adapter}/wiring.d.ts.map +1 -1
- package/dist/core/hooks/{harness → adapter}/wiring.js +13 -13
- package/dist/core/hooks/cli.d.ts +3 -3
- package/dist/core/hooks/cli.js +175 -151
- package/dist/core/hooks/effects/image-capture.d.ts +5 -5
- package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
- package/dist/core/hooks/effects/image-capture.js +3 -3
- package/dist/core/hooks/effects/index.d.ts +12 -12
- package/dist/core/hooks/effects/index.d.ts.map +1 -1
- package/dist/core/hooks/effects/index.js +17 -17
- package/dist/core/hooks/events/emit.d.ts +2 -2
- package/dist/core/hooks/events/emit.d.ts.map +1 -1
- package/dist/core/hooks/events/emit.js +1 -1
- package/dist/core/hooks/events/rotate.js +1 -1
- package/dist/core/hooks/events/schema.d.ts +13 -5
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/guard-path.d.ts +2 -2
- package/dist/core/hooks/guard-path.js +2 -2
- package/dist/core/hooks/index.d.ts +1 -1
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/resolve/anchor.d.ts +14 -5
- package/dist/core/hooks/resolve/anchor.d.ts.map +1 -1
- package/dist/core/hooks/resolve/anchor.js +64 -7
- package/dist/core/hooks/resolve/coord-root.d.ts +3 -3
- package/dist/core/hooks/resolve/coord-root.js +4 -4
- package/dist/core/hooks/resolve/intent.js +1 -1
- package/dist/core/hooks/resolve/owner.d.ts +1 -1
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +11 -5
- package/dist/core/{scratch → journal}/index.d.ts +25 -25
- package/dist/core/{scratch → journal}/index.d.ts.map +1 -1
- package/dist/core/{scratch → journal}/index.js +38 -38
- package/dist/core/policy/evaluate.d.ts +1 -1
- package/dist/core/policy/evaluate.d.ts.map +1 -1
- package/dist/core/policy/evaluate.js +7 -7
- package/dist/core/policy/types.d.ts +3 -3
- package/dist/core/policy/types.d.ts.map +1 -1
- package/dist/core/work/runner.d.ts.map +1 -1
- package/dist/core/work/runner.js +11 -2
- package/dist/core/work/state.d.ts +28 -3
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +148 -16
- package/dist/core/workflow/adapters.d.ts +17 -0
- package/dist/core/workflow/adapters.d.ts.map +1 -0
- package/dist/core/workflow/adapters.js +21 -0
- package/dist/core/workflow/approvals.js +6 -6
- package/dist/core/workflow/billing.d.ts +9 -9
- package/dist/core/workflow/billing.js +9 -9
- package/dist/core/workflow/child-env.d.ts +10 -2
- package/dist/core/workflow/child-env.d.ts.map +1 -1
- package/dist/core/workflow/child-env.js +12 -4
- package/dist/core/workflow/engine.d.ts +15 -5
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +151 -83
- package/dist/core/workflow/evidence-preflight.d.ts +24 -0
- package/dist/core/workflow/evidence-preflight.d.ts.map +1 -0
- package/dist/core/workflow/evidence-preflight.js +134 -0
- package/dist/core/workflow/index.d.ts +3 -2
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +2 -1
- package/dist/core/workflow/proof.d.ts +28 -5
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +77 -34
- package/dist/core/workflow/run-state.d.ts +1 -1
- package/dist/core/workflow/run-state.js +7 -7
- package/dist/core/workflow/sandbox-projection.d.ts +6 -6
- package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
- package/dist/core/workflow/sandbox-projection.js +11 -11
- package/dist/core/workflow/spawn-claude.d.ts +3 -3
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +28 -8
- package/dist/core/workflow/spawn-codex.d.ts +4 -4
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +26 -8
- package/dist/core/workflow/spawn-cursor.d.ts +4 -4
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +27 -8
- package/dist/core/workflow/spawn-failure.d.ts +15 -1
- package/dist/core/workflow/spawn-failure.d.ts.map +1 -1
- package/dist/core/workflow/spawn-failure.js +35 -1
- package/dist/core/workflow/specialists.js +2 -2
- package/dist/core/workflow/{journal.d.ts → transcript.d.ts} +8 -8
- package/dist/core/workflow/transcript.d.ts.map +1 -0
- package/dist/core/workflow/{journal.js → transcript.js} +14 -14
- package/dist/core/workflow/types.d.ts +119 -46
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/types.js +11 -1
- package/dist/core/workflow/validate.d.ts +1 -1
- package/dist/core/workflow/validate.js +1 -1
- package/dist/core/workflow/workspaces/cancellation.js +2 -2
- package/dist/core/workflow/workspaces/cleanup.js +2 -2
- package/dist/core/workflow/workspaces/execution.js +6 -6
- package/dist/core/workflow/workspaces/inspect.js +9 -9
- package/dist/core/workflow/workspaces/integration.js +14 -14
- package/dist/core/workflow/workspaces/lifecycle.d.ts +1 -1
- package/dist/core/workflow/workspaces/lifecycle.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/lifecycle.js +9 -9
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +39 -5
- package/dist/core/workflow/workspaces/recovery.js +3 -3
- package/dist/core/workflow/workspaces/state.d.ts +1 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +3 -3
- package/dist/core/workflow/workspaces/types.d.ts +1 -1
- package/dist/core/workflow/workspaces/types.d.ts.map +1 -1
- package/dist/lib/browser/asserts.d.ts +28 -0
- package/dist/lib/browser/asserts.d.ts.map +1 -0
- package/dist/lib/browser/asserts.js +152 -0
- package/dist/lib/browser/client.d.ts +49 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +155 -14
- package/dist/lib/browser/content-checks.d.ts +108 -0
- package/dist/lib/browser/content-checks.d.ts.map +1 -0
- package/dist/lib/browser/content-checks.js +474 -0
- package/dist/lib/browser/critique.d.ts +91 -0
- package/dist/lib/browser/critique.d.ts.map +1 -0
- package/dist/lib/browser/critique.js +167 -0
- package/dist/lib/browser/geometry.d.ts +32 -0
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +244 -1
- package/dist/lib/browser/index.d.ts +4 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -0
- package/dist/lib/claude-desktop.d.ts.map +1 -1
- package/dist/lib/claude-desktop.js +4 -1
- package/dist/lib/council/index.d.ts +6 -6
- package/dist/lib/council/index.d.ts.map +1 -1
- package/dist/lib/council/index.js +8 -8
- package/dist/lib/exec.d.ts +7 -0
- package/dist/lib/exec.d.ts.map +1 -1
- package/dist/lib/exec.js +6 -3
- package/dist/lib/identities/assume.d.ts +2 -2
- package/dist/lib/identities/assume.js +7 -7
- package/dist/lib/instructions/apply.d.ts +3 -3
- package/dist/lib/instructions/apply.d.ts.map +1 -1
- package/dist/lib/instructions/apply.js +75 -27
- package/dist/lib/instructions/host-addendum.d.ts +54 -0
- package/dist/lib/instructions/host-addendum.d.ts.map +1 -0
- package/dist/lib/instructions/host-addendum.js +107 -0
- package/dist/lib/instructions/templates.d.ts +10 -3
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +41 -12
- package/dist/lib/machine.d.ts +2 -2
- package/dist/lib/machine.js +2 -2
- package/package.json +26 -21
- package/schemas/config.schema.json +26 -1
- package/src/commander.ts +22 -13
- package/src/commands/{harness.ts → adapter.ts} +46 -46
- package/src/commands/agents.ts +71 -64
- package/src/commands/artifacts.ts +168 -0
- package/src/commands/backup.ts +47 -33
- package/src/commands/browse.ts +438 -2
- package/src/commands/checkpoint.ts +196 -0
- package/src/commands/context.ts +3 -176
- package/src/commands/decision.ts +41 -7
- package/src/commands/deinit.ts +16 -16
- package/src/commands/doctor.ts +28 -28
- package/src/commands/{supervisor.ts → governor.ts} +184 -154
- package/src/commands/init.ts +50 -34
- package/src/commands/{scratch.ts → journal.ts} +60 -60
- package/src/commands/policy.ts +1 -1
- package/src/commands/presence.ts +2 -2
- package/src/commands/sync.ts +3 -3
- package/src/commands/tunnel.ts +237 -34
- package/src/commands/web-fetch.ts +1 -1
- package/src/commands/web.ts +56 -1
- package/src/commands/work.ts +72 -38
- package/src/commands/workflow.ts +43 -34
- package/src/core/{harnesses → adapters}/attest-projection.ts +2 -2
- package/src/core/{harnesses → adapters}/attest.ts +27 -27
- package/src/core/{harnesses → adapters}/attestation.ts +41 -41
- package/src/core/{harnesses → adapters}/bench.ts +34 -43
- package/src/core/adapters/index.ts +56 -0
- package/src/core/{harnesses → adapters}/profiles.ts +16 -16
- package/src/core/{harnesses → adapters}/registry.ts +23 -23
- package/src/core/{harnesses → adapters}/types.ts +28 -23
- package/src/core/agents/canonical-emit.ts +6 -6
- package/src/core/agents/cli-emit.ts +1 -1
- package/src/core/agents/cli.ts +30 -28
- package/src/core/agents/codex-replay.ts +6 -6
- package/src/core/agents/coord-client.ts +119 -28
- package/src/core/agents/events/consume.ts +1 -1
- package/src/core/agents/events/emit.ts +7 -6
- package/src/core/agents/render/prompt-context.ts +107 -31
- package/src/core/agents/render/session-context.ts +6 -6
- package/src/core/agents/rules/claim-conflict.ts +4 -7
- package/src/core/agents/rules/commit-conflict.ts +103 -22
- package/src/core/agents/rules/stop-hook.ts +33 -17
- package/src/core/agents/rules/verdict.ts +13 -0
- package/src/core/agents/session-events.ts +4 -4
- package/src/core/agents/state/council.ts +1 -1
- package/src/core/agents/state/heartbeat-projector.ts +16 -12
- package/src/core/agents/state/heartbeat-writer.ts +12 -12
- package/src/core/agents/state/{scratch.ts → journal.ts} +12 -12
- package/src/core/agents/state/pidmap.ts +130 -15
- package/src/core/agents/state/proc-start.ts +208 -0
- package/src/core/agents/state/shell-mutation.ts +1 -1
- package/src/core/agents/state/stale-sweep.ts +7 -7
- package/src/core/artifacts/index.ts +557 -0
- package/src/core/config.ts +30 -2
- package/src/core/context/index.ts +15 -15
- package/src/core/governor/adapter-spread.ts +82 -0
- package/src/core/governor/index.ts +91 -0
- package/src/core/governor/plan-api.ts +115 -0
- package/src/core/{supervisor → governor}/plan-read.ts +120 -96
- package/src/core/{supervisor → governor}/plan-types.ts +64 -50
- package/src/core/{supervisor → governor}/planning.ts +240 -188
- package/src/core/governor/read.ts +59 -0
- package/src/core/{supervisor → governor}/runner.ts +39 -39
- package/src/core/{supervisor → governor}/service-read.ts +22 -22
- package/src/core/{supervisor → governor}/service.ts +117 -119
- package/src/core/{supervisor → governor}/state.ts +389 -174
- package/src/core/hooks/adapter/detect.ts +30 -0
- package/src/core/hooks/{harness → adapter}/events.ts +16 -14
- package/src/core/hooks/{harness → adapter}/output.ts +31 -23
- package/src/core/hooks/{harness → adapter}/parse.ts +5 -5
- package/src/core/hooks/{harness → adapter}/wiring.ts +21 -21
- package/src/core/hooks/cli.ts +185 -162
- package/src/core/hooks/effects/image-capture.ts +6 -6
- package/src/core/hooks/effects/index.ts +17 -17
- package/src/core/hooks/events/emit.ts +3 -3
- package/src/core/hooks/events/rotate.ts +1 -1
- package/src/core/hooks/events/schema.ts +15 -7
- package/src/core/hooks/guard-path.ts +2 -2
- package/src/core/hooks/index.ts +1 -1
- package/src/core/hooks/resolve/anchor.ts +63 -8
- package/src/core/hooks/resolve/coord-root.ts +4 -4
- package/src/core/hooks/resolve/intent.ts +1 -1
- package/src/core/hooks/resolve/owner.ts +11 -5
- package/src/core/{scratch → journal}/index.ts +53 -53
- package/src/core/policy/evaluate.ts +8 -8
- package/src/core/policy/types.ts +3 -3
- package/src/core/work/runner.ts +11 -2
- package/src/core/work/state.ts +185 -21
- package/src/core/workflow/adapters.ts +33 -0
- package/src/core/workflow/approvals.ts +7 -7
- package/src/core/workflow/billing.ts +12 -12
- package/src/core/workflow/child-env.ts +13 -4
- package/src/core/workflow/engine.ts +166 -90
- package/src/core/workflow/evidence-preflight.ts +165 -0
- package/src/core/workflow/index.ts +6 -3
- package/src/core/workflow/proof.ts +94 -48
- package/src/core/workflow/run-state.ts +7 -7
- package/src/core/workflow/sandbox-projection.ts +16 -16
- package/src/core/workflow/spawn-claude.ts +34 -12
- package/src/core/workflow/spawn-codex.ts +30 -12
- package/src/core/workflow/spawn-cursor.ts +31 -12
- package/src/core/workflow/spawn-failure.ts +41 -1
- package/src/core/workflow/specialists.ts +2 -2
- package/src/core/workflow/{journal.ts → transcript.ts} +16 -16
- package/src/core/workflow/types.ts +130 -46
- package/src/core/workflow/validate.ts +1 -1
- package/src/core/workflow/workspaces/cancellation.ts +2 -2
- package/src/core/workflow/workspaces/cleanup.ts +2 -2
- package/src/core/workflow/workspaces/execution.ts +6 -6
- package/src/core/workflow/workspaces/inspect.ts +14 -11
- package/src/core/workflow/workspaces/integration.ts +18 -14
- package/src/core/workflow/workspaces/lifecycle.ts +10 -10
- package/src/core/workflow/workspaces/local-git.ts +47 -5
- package/src/core/workflow/workspaces/recovery.ts +3 -3
- package/src/core/workflow/workspaces/state.ts +4 -3
- package/src/core/workflow/workspaces/types.ts +1 -1
- package/src/lib/browser/asserts.ts +177 -0
- package/src/lib/browser/client.ts +189 -14
- package/src/lib/browser/content-checks.ts +571 -0
- package/src/lib/browser/critique.ts +231 -0
- package/src/lib/browser/geometry.ts +297 -1
- package/src/lib/browser/index.ts +34 -0
- package/src/lib/claude-desktop.ts +4 -1
- package/src/lib/council/index.ts +9 -9
- package/src/lib/exec.ts +13 -3
- package/src/lib/identities/assume.ts +7 -7
- package/src/lib/instructions/apply.ts +87 -25
- package/src/lib/instructions/host-addendum.ts +126 -0
- package/src/lib/instructions/templates.ts +44 -15
- package/src/lib/machine.ts +2 -2
- package/dist/commands/harness.d.ts +0 -12
- package/dist/commands/harness.d.ts.map +0 -1
- package/dist/commands/scratch.d.ts +0 -4
- package/dist/commands/scratch.d.ts.map +0 -1
- package/dist/commands/session.d.ts.map +0 -1
- package/dist/commands/session.js +0 -162
- package/dist/commands/supervisor.d.ts +0 -4
- package/dist/commands/supervisor.d.ts.map +0 -1
- package/dist/core/harnesses/attest-projection.d.ts.map +0 -1
- package/dist/core/harnesses/attest.d.ts.map +0 -1
- package/dist/core/harnesses/attestation.d.ts.map +0 -1
- package/dist/core/harnesses/bench.d.ts.map +0 -1
- package/dist/core/harnesses/index.d.ts +0 -13
- package/dist/core/harnesses/index.d.ts.map +0 -1
- package/dist/core/harnesses/index.js +0 -6
- package/dist/core/harnesses/profiles.d.ts.map +0 -1
- package/dist/core/harnesses/registry.d.ts +0 -15
- package/dist/core/harnesses/registry.d.ts.map +0 -1
- package/dist/core/harnesses/types.d.ts.map +0 -1
- package/dist/core/hooks/harness/detect.d.ts +0 -9
- package/dist/core/hooks/harness/events.d.ts.map +0 -1
- package/dist/core/hooks/harness/output.d.ts.map +0 -1
- package/dist/core/supervisor/index.d.ts +0 -7
- package/dist/core/supervisor/index.d.ts.map +0 -1
- package/dist/core/supervisor/index.js +0 -6
- package/dist/core/supervisor/plan-api.d.ts +0 -23
- package/dist/core/supervisor/plan-api.d.ts.map +0 -1
- package/dist/core/supervisor/plan-api.js +0 -50
- package/dist/core/supervisor/plan-read.d.ts +0 -5
- package/dist/core/supervisor/plan-read.d.ts.map +0 -1
- package/dist/core/supervisor/plan-types.d.ts +0 -170
- package/dist/core/supervisor/plan-types.d.ts.map +0 -1
- package/dist/core/supervisor/planning.d.ts +0 -33
- package/dist/core/supervisor/planning.d.ts.map +0 -1
- package/dist/core/supervisor/read.d.ts +0 -6
- package/dist/core/supervisor/read.d.ts.map +0 -1
- package/dist/core/supervisor/read.js +0 -4
- package/dist/core/supervisor/runner.d.ts +0 -37
- package/dist/core/supervisor/runner.d.ts.map +0 -1
- package/dist/core/supervisor/service-read.d.ts +0 -6
- package/dist/core/supervisor/service-read.d.ts.map +0 -1
- package/dist/core/supervisor/service.d.ts +0 -114
- package/dist/core/supervisor/service.d.ts.map +0 -1
- package/dist/core/supervisor/state.d.ts +0 -95
- package/dist/core/supervisor/state.d.ts.map +0 -1
- package/dist/core/workflow/harnesses.d.ts +0 -17
- package/dist/core/workflow/harnesses.d.ts.map +0 -1
- package/dist/core/workflow/harnesses.js +0 -21
- package/dist/core/workflow/journal.d.ts.map +0 -1
- package/src/commands/session.ts +0 -187
- package/src/core/harnesses/index.ts +0 -56
- package/src/core/hooks/harness/detect.ts +0 -30
- package/src/core/supervisor/index.ts +0 -89
- package/src/core/supervisor/plan-api.ts +0 -73
- package/src/core/supervisor/read.ts +0 -59
- package/src/core/workflow/harnesses.ts +0 -33
- /package/dist/core/{harnesses → adapters}/attest-projection.js +0 -0
|
@@ -17,18 +17,18 @@ import { assertWorkId, readWorkItem, type WorkRecord } from "../work/read.ts";
|
|
|
17
17
|
import { workflowScriptDigest } from "../workflow/run-state.ts";
|
|
18
18
|
import { normalizeWorkflowSpecialists } from "../workflow/specialists.ts";
|
|
19
19
|
import type { WorkflowSpecialistProfile } from "../workflow/types.ts";
|
|
20
|
-
import {
|
|
20
|
+
import { readGovernorPlans } from "./plan-read.ts";
|
|
21
21
|
import {
|
|
22
|
-
type
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
type
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
type CreateGovernorReplanningInput,
|
|
23
|
+
type GovernorPlanHistory,
|
|
24
|
+
type GovernorPlanRecord,
|
|
25
|
+
type GovernorReplanningPolicy,
|
|
26
|
+
governorGraphFingerprint,
|
|
27
|
+
MAX_GOVERNOR_PLAN_REVIEWERS,
|
|
28
|
+
MAX_GOVERNOR_PLAN_REVISION_ROUNDS,
|
|
29
29
|
} from "./plan-types.ts";
|
|
30
30
|
|
|
31
|
-
export const
|
|
31
|
+
export const GOVERNOR_INTENT_SCHEMA_VERSION = 1 as const;
|
|
32
32
|
|
|
33
33
|
const GOAL_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
34
34
|
const MAX_TITLE = 200;
|
|
@@ -38,8 +38,15 @@ const MAX_INTENT_BYTES = 256 * 1024;
|
|
|
38
38
|
const MAX_TEMPLATES = 20;
|
|
39
39
|
const TEMPLATE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
40
40
|
const FOREIGN_LEASE_STALE_MS = 24 * 60 * 60 * 1_000;
|
|
41
|
+
/** Ceiling on CONSECUTIVE uncharged (upstream) replans (ADR 0046). Because an
|
|
42
|
+
* uncharged replan does not spend `max_replans`, an unending vendor outage would
|
|
43
|
+
* otherwise replan forever; this bound stops it and names the outside service.
|
|
44
|
+
* Kept low — each replan is a real planner round-trip — and a module constant
|
|
45
|
+
* rather than a policy field to keep the frozen replanning schema unchanged; an
|
|
46
|
+
* environment failure self-bounds at one because it stops immediately. */
|
|
47
|
+
const MAX_UNCHARGED_REPLANS = 3;
|
|
41
48
|
|
|
42
|
-
export interface
|
|
49
|
+
export interface GovernorLimits {
|
|
43
50
|
max_cycles: number;
|
|
44
51
|
max_runtime_ms: number;
|
|
45
52
|
max_parallel_work: number;
|
|
@@ -48,38 +55,38 @@ export interface SupervisorLimits {
|
|
|
48
55
|
agent_concurrency: number;
|
|
49
56
|
}
|
|
50
57
|
|
|
51
|
-
export interface
|
|
58
|
+
export interface GovernorAutomationPolicy {
|
|
52
59
|
accept_passing_proof: boolean;
|
|
53
60
|
resume_approved: boolean;
|
|
54
61
|
retry_blocked: boolean;
|
|
55
62
|
}
|
|
56
63
|
|
|
57
|
-
export interface
|
|
64
|
+
export interface GovernorMission {
|
|
58
65
|
objective: string;
|
|
59
66
|
acceptance: string[];
|
|
60
67
|
max_milestones: number;
|
|
61
68
|
}
|
|
62
69
|
|
|
63
|
-
export interface
|
|
70
|
+
export interface CreateGovernorMissionInput {
|
|
64
71
|
objective: string;
|
|
65
72
|
acceptance: readonly string[];
|
|
66
73
|
maxMilestones?: number;
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
export interface
|
|
70
|
-
schema_version: typeof
|
|
76
|
+
export interface GovernorIntent {
|
|
77
|
+
schema_version: typeof GOVERNOR_INTENT_SCHEMA_VERSION;
|
|
71
78
|
id: string;
|
|
72
79
|
title: string;
|
|
73
80
|
root_work_id: string;
|
|
74
81
|
specialists: Record<string, WorkflowSpecialistProfile>;
|
|
75
|
-
limits:
|
|
76
|
-
automation:
|
|
77
|
-
mission?:
|
|
78
|
-
replanning?:
|
|
82
|
+
limits: GovernorLimits;
|
|
83
|
+
automation: GovernorAutomationPolicy;
|
|
84
|
+
mission?: GovernorMission;
|
|
85
|
+
replanning?: GovernorReplanningPolicy;
|
|
79
86
|
created_at: string;
|
|
80
87
|
}
|
|
81
88
|
|
|
82
|
-
export type
|
|
89
|
+
export type GovernorState =
|
|
83
90
|
| "ready"
|
|
84
91
|
| "running"
|
|
85
92
|
| "awaiting_attention"
|
|
@@ -87,7 +94,7 @@ export type SupervisorState =
|
|
|
87
94
|
| "budget_exhausted"
|
|
88
95
|
| "succeeded";
|
|
89
96
|
|
|
90
|
-
export type
|
|
97
|
+
export type GovernorNextAction =
|
|
91
98
|
| "run"
|
|
92
99
|
| "wait_for_run"
|
|
93
100
|
| "resolve_approval"
|
|
@@ -100,19 +107,23 @@ export type SupervisorNextAction =
|
|
|
100
107
|
| "retry_plan"
|
|
101
108
|
| "none";
|
|
102
109
|
|
|
103
|
-
export interface
|
|
110
|
+
export interface GovernorProjection {
|
|
104
111
|
id: string;
|
|
105
112
|
title: string;
|
|
106
113
|
root_work_id: string;
|
|
107
114
|
root_materialized: boolean;
|
|
108
|
-
state:
|
|
115
|
+
state: GovernorState;
|
|
109
116
|
reason: string;
|
|
110
|
-
next_action:
|
|
117
|
+
next_action: GovernorNextAction;
|
|
111
118
|
work_ids: string[];
|
|
112
119
|
ready_work: string[];
|
|
113
120
|
resumable_work: string[];
|
|
114
121
|
retryable_work: string[];
|
|
115
122
|
attention_work: string[];
|
|
123
|
+
/** Work stopped because a human must rule, with the docket id when the script
|
|
124
|
+
* named one. Empty in the ordinary case. A reader that wants to tell an
|
|
125
|
+
* operator what is actually waiting on them should start here. */
|
|
126
|
+
decision_blocked_work: Array<{ work_id: string; decision_id?: string }>;
|
|
116
127
|
attempts_used: number;
|
|
117
128
|
attempts_remaining: number;
|
|
118
129
|
specialists: string[];
|
|
@@ -123,41 +134,53 @@ export interface SupervisorProjection {
|
|
|
123
134
|
milestones_remaining: number;
|
|
124
135
|
pending_plan_id?: string;
|
|
125
136
|
attention_plan_id?: string;
|
|
126
|
-
latest_plan_status?:
|
|
137
|
+
latest_plan_status?: GovernorPlanRecord["status"];
|
|
138
|
+
/**
|
|
139
|
+
* How consumed (charged) replans that did not advance the graph broke down,
|
|
140
|
+
* present only when at least one replan was spent by a planner run that
|
|
141
|
+
* produced no proposal. `reviewer_rejection` counts replans where a proposal
|
|
142
|
+
* was produced and independent review rejected it (review-round exhaustion,
|
|
143
|
+
* reviewer attention, or review failure); `planner_no_proposal` counts replans
|
|
144
|
+
* where the planner run produced no reviewable proposal at all. Absent when a
|
|
145
|
+
* goal has no planner no-proposal history, so goals that only ever reached
|
|
146
|
+
* review project exactly as before this field existed.
|
|
147
|
+
*/
|
|
148
|
+
replan_consumption?: {
|
|
149
|
+
reviewer_rejection: number;
|
|
150
|
+
planner_no_proposal: number;
|
|
151
|
+
};
|
|
127
152
|
governed_work_ids: string[];
|
|
128
153
|
}
|
|
129
154
|
|
|
130
|
-
export interface
|
|
131
|
-
intent:
|
|
132
|
-
projection:
|
|
155
|
+
export interface GovernorRecord {
|
|
156
|
+
intent: GovernorIntent;
|
|
157
|
+
projection: GovernorProjection;
|
|
133
158
|
work: WorkRecord[];
|
|
134
|
-
plans:
|
|
159
|
+
plans: GovernorPlanRecord[];
|
|
135
160
|
}
|
|
136
161
|
|
|
137
|
-
export interface
|
|
162
|
+
export interface CreateGovernorInput {
|
|
138
163
|
coordRoot: string;
|
|
139
164
|
rootWorkId?: string;
|
|
140
165
|
specialists: Readonly<Record<string, WorkflowSpecialistProfile>>;
|
|
141
166
|
title?: string;
|
|
142
167
|
id?: string;
|
|
143
|
-
limits?: Partial<
|
|
144
|
-
automation?: Partial<
|
|
145
|
-
mission?:
|
|
146
|
-
replanning?:
|
|
168
|
+
limits?: Partial<GovernorLimits>;
|
|
169
|
+
automation?: Partial<GovernorAutomationPolicy>;
|
|
170
|
+
mission?: CreateGovernorMissionInput;
|
|
171
|
+
replanning?: CreateGovernorReplanningInput;
|
|
147
172
|
}
|
|
148
173
|
|
|
149
|
-
export function
|
|
174
|
+
export function createGovernor(input: CreateGovernorInput): GovernorRecord {
|
|
150
175
|
const coordRoot = resolve(input.coordRoot);
|
|
151
|
-
const id = input.id ??
|
|
152
|
-
|
|
176
|
+
const id = input.id ?? newGovernorId();
|
|
177
|
+
assertGovernorId(id);
|
|
153
178
|
const mission = input.mission ? normalizeMission(input.mission) : undefined;
|
|
154
179
|
if (mission && !input.replanning) {
|
|
155
|
-
throw new Error("
|
|
180
|
+
throw new Error("governor mission requires a replanning policy");
|
|
156
181
|
}
|
|
157
182
|
if (!input.rootWorkId && !mission) {
|
|
158
|
-
throw new Error(
|
|
159
|
-
"supervisor creation without root work requires a mission and replanning policy",
|
|
160
|
-
);
|
|
183
|
+
throw new Error("governor creation without root work requires a mission and replanning policy");
|
|
161
184
|
}
|
|
162
185
|
if (input.rootWorkId) assertWorkId(input.rootWorkId);
|
|
163
186
|
const root = input.rootWorkId ? readWorkItem(coordRoot, input.rootWorkId) : undefined;
|
|
@@ -175,12 +198,12 @@ export function createSupervisor(input: CreateSupervisorInput): SupervisorRecord
|
|
|
175
198
|
"objective-first mission max_replans must exceed max_milestones to reserve completion review",
|
|
176
199
|
);
|
|
177
200
|
}
|
|
178
|
-
const intent:
|
|
179
|
-
schema_version:
|
|
201
|
+
const intent: GovernorIntent = {
|
|
202
|
+
schema_version: GOVERNOR_INTENT_SCHEMA_VERSION,
|
|
180
203
|
id,
|
|
181
204
|
title: bounded(
|
|
182
205
|
input.title ?? root?.intent.title ?? missionTitle(mission!),
|
|
183
|
-
"
|
|
206
|
+
"governor title",
|
|
184
207
|
MAX_TITLE,
|
|
185
208
|
),
|
|
186
209
|
root_work_id: input.rootWorkId ?? missionRootId(id),
|
|
@@ -191,42 +214,41 @@ export function createSupervisor(input: CreateSupervisorInput): SupervisorRecord
|
|
|
191
214
|
...(replanning ? { replanning } : {}),
|
|
192
215
|
created_at: new Date().toISOString(),
|
|
193
216
|
};
|
|
194
|
-
const path =
|
|
217
|
+
const path = governorIntentPath(coordRoot, id);
|
|
195
218
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
196
219
|
chmodSync(dirname(path), 0o700);
|
|
197
220
|
writePrivateJson(path, intent);
|
|
198
|
-
return
|
|
221
|
+
return readGovernor(coordRoot, id);
|
|
199
222
|
}
|
|
200
223
|
|
|
201
|
-
export function
|
|
202
|
-
return
|
|
224
|
+
export function readGovernor(coordRoot: string, goalId: string): GovernorRecord {
|
|
225
|
+
return readGovernorInternal(coordRoot, goalId, false);
|
|
203
226
|
}
|
|
204
227
|
|
|
205
|
-
/** @internal Runner seam; ignores the lease held by this
|
|
206
|
-
export function
|
|
207
|
-
return
|
|
228
|
+
/** @internal Runner seam; ignores the lease held by this governor process. */
|
|
229
|
+
export function readGovernorIgnoringLease(coordRoot: string, goalId: string): GovernorRecord {
|
|
230
|
+
return readGovernorInternal(coordRoot, goalId, true);
|
|
208
231
|
}
|
|
209
232
|
|
|
210
|
-
export function
|
|
211
|
-
const base = join(resolve(coordRoot), ".harnery", "
|
|
233
|
+
export function listGovernors(coordRoot: string): GovernorRecord[] {
|
|
234
|
+
const base = join(resolve(coordRoot), ".harnery", "governors");
|
|
212
235
|
if (!existsSync(base)) return [];
|
|
213
|
-
const records:
|
|
236
|
+
const records: GovernorRecord[] = [];
|
|
214
237
|
for (const name of readdirSync(base)) {
|
|
215
|
-
if (!GOAL_ID.test(name) || !existsSync(
|
|
216
|
-
records.push(
|
|
238
|
+
if (!GOAL_ID.test(name) || !existsSync(governorIntentPath(coordRoot, name))) continue;
|
|
239
|
+
records.push(readGovernor(coordRoot, name));
|
|
217
240
|
}
|
|
218
241
|
return records.sort((left, right) =>
|
|
219
242
|
right.intent.created_at.localeCompare(left.intent.created_at),
|
|
220
243
|
);
|
|
221
244
|
}
|
|
222
245
|
|
|
223
|
-
export function
|
|
246
|
+
export function collectGovernorWork(coordRoot: string, rootWorkId: string): WorkRecord[] {
|
|
224
247
|
const records = new Map<string, WorkRecord>();
|
|
225
248
|
const visiting = new Set<string>();
|
|
226
249
|
const visit = (workId: string): void => {
|
|
227
250
|
if (records.has(workId)) return;
|
|
228
|
-
if (visiting.has(workId))
|
|
229
|
-
throw new Error(`supervisor work graph contains a cycle at ${workId}`);
|
|
251
|
+
if (visiting.has(workId)) throw new Error(`governor work graph contains a cycle at ${workId}`);
|
|
230
252
|
visiting.add(workId);
|
|
231
253
|
const record = readWorkItem(coordRoot, workId);
|
|
232
254
|
for (const dependency of record.intent.dependencies) visit(dependency);
|
|
@@ -237,18 +259,18 @@ export function collectSupervisorWork(coordRoot: string, rootWorkId: string): Wo
|
|
|
237
259
|
return Array.from(records.values());
|
|
238
260
|
}
|
|
239
261
|
|
|
240
|
-
export function
|
|
241
|
-
if (!GOAL_ID.test(goalId)) throw new Error(`invalid
|
|
262
|
+
export function assertGovernorId(goalId: string): void {
|
|
263
|
+
if (!GOAL_ID.test(goalId)) throw new Error(`invalid governor id ${JSON.stringify(goalId)}`);
|
|
242
264
|
}
|
|
243
265
|
|
|
244
|
-
export function
|
|
266
|
+
export function newGovernorId(): string {
|
|
245
267
|
return `goal-${new Date().toISOString().replace(/[:.]/g, "-")}-${randomBytes(3).toString("hex")}`;
|
|
246
268
|
}
|
|
247
269
|
|
|
248
|
-
/** @internal Runner seam; one exclusive crash-recoverable
|
|
249
|
-
export function
|
|
250
|
-
|
|
251
|
-
const path = join(
|
|
270
|
+
/** @internal Runner seam; one exclusive crash-recoverable governor lease. */
|
|
271
|
+
export function acquireGovernorLease(coordRoot: string, goalId: string): () => void {
|
|
272
|
+
readGovernorIntent(coordRoot, goalId);
|
|
273
|
+
const path = join(governorDir(coordRoot, goalId), "lease.json");
|
|
252
274
|
const owner = {
|
|
253
275
|
pid: process.pid,
|
|
254
276
|
host: hostname(),
|
|
@@ -274,11 +296,11 @@ export function acquireSupervisorLease(coordRoot: string, goalId: string): () =>
|
|
|
274
296
|
const existing = readLease(path);
|
|
275
297
|
if (existing && leaseIsLive(existing)) {
|
|
276
298
|
throw new Error(
|
|
277
|
-
`
|
|
299
|
+
`governor ${goalId} is already running under pid ${existing.pid} on ${existing.host}`,
|
|
278
300
|
);
|
|
279
301
|
}
|
|
280
302
|
unlinkSync(path);
|
|
281
|
-
if (!acquire()) throw new Error(`
|
|
303
|
+
if (!acquire()) throw new Error(`governor ${goalId} lease raced with another process`);
|
|
282
304
|
}
|
|
283
305
|
return () => {
|
|
284
306
|
try {
|
|
@@ -290,19 +312,19 @@ export function acquireSupervisorLease(coordRoot: string, goalId: string): () =>
|
|
|
290
312
|
};
|
|
291
313
|
}
|
|
292
314
|
|
|
293
|
-
function
|
|
315
|
+
function readGovernorInternal(
|
|
294
316
|
coordRootRaw: string,
|
|
295
317
|
goalId: string,
|
|
296
318
|
ignoreLease: boolean,
|
|
297
|
-
):
|
|
319
|
+
): GovernorRecord {
|
|
298
320
|
const coordRoot = resolve(coordRootRaw);
|
|
299
|
-
const intent =
|
|
300
|
-
const plans =
|
|
321
|
+
const intent = readGovernorIntent(coordRoot, goalId);
|
|
322
|
+
const plans = readGovernorPlans(coordRoot, intent.id, intent.root_work_id);
|
|
301
323
|
const activeRootExists = workIntentExists(coordRoot, plans.active_root_work_id);
|
|
302
324
|
if (!activeRootExists && (!intent.mission || plans.generation > 0)) {
|
|
303
|
-
throw new Error(`
|
|
325
|
+
throw new Error(`governor ${intent.id} root work is missing`);
|
|
304
326
|
}
|
|
305
|
-
const work = activeRootExists ?
|
|
327
|
+
const work = activeRootExists ? collectGovernorWork(coordRoot, plans.active_root_work_id) : [];
|
|
306
328
|
const governed = collectGovernedWork(coordRoot, intent.root_work_id, plans, work);
|
|
307
329
|
return {
|
|
308
330
|
intent,
|
|
@@ -314,14 +336,30 @@ function readSupervisorInternal(
|
|
|
314
336
|
|
|
315
337
|
function deriveProjection(
|
|
316
338
|
coordRoot: string,
|
|
317
|
-
intent:
|
|
339
|
+
intent: GovernorIntent,
|
|
318
340
|
work: WorkRecord[],
|
|
319
341
|
governed: WorkRecord[],
|
|
320
|
-
plans:
|
|
342
|
+
plans: GovernorPlanHistory,
|
|
321
343
|
ignoreLease: boolean,
|
|
322
|
-
):
|
|
344
|
+
): GovernorProjection {
|
|
323
345
|
const root = work.find((record) => record.intent.id === plans.active_root_work_id);
|
|
324
346
|
const attemptsUsed = governed.reduce((sum, record) => sum + record.projection.attempts_used, 0);
|
|
347
|
+
// The goal budget, like the per-item budget, counts only charged attempts
|
|
348
|
+
// (ADR 0046): an uncharged environment/upstream attempt on any child does not
|
|
349
|
+
// spend the goal's total. attempts_used stays the raw count for display.
|
|
350
|
+
const chargedUsed = governed.reduce((sum, record) => sum + record.projection.charged_attempts, 0);
|
|
351
|
+
// Replans, like per-item attempts (ADR 0046), are budgeted by the CHARGED
|
|
352
|
+
// count: an uncharged planner failure (environment/upstream) does not spend
|
|
353
|
+
// max_replans. replans_used stays the raw count for display.
|
|
354
|
+
const unchargedReplans = plans.plans.filter(
|
|
355
|
+
(plan) => plan.status === "failed" && isUnchargedPlanClass(plan.class),
|
|
356
|
+
).length;
|
|
357
|
+
const chargedReplans = plans.plans.length - unchargedReplans;
|
|
358
|
+
// Attribute the consumed replans that did not advance the graph so the
|
|
359
|
+
// projection can distinguish planner no-proposal exhaustion from reviewer
|
|
360
|
+
// rejection through existing plan seams (review receipt presence), rather than
|
|
361
|
+
// reporting both as a single undifferentiated replan exhaustion.
|
|
362
|
+
const replanConsumption = classifyReplanConsumption(plans);
|
|
325
363
|
const readyWork = work
|
|
326
364
|
.filter((record) => record.projection.state === "ready")
|
|
327
365
|
.map((record) => record.intent.id);
|
|
@@ -356,6 +394,15 @@ function deriveProjection(
|
|
|
356
394
|
(record) => record.projection.state === "blocked" && record.projection.next_action === "none",
|
|
357
395
|
)
|
|
358
396
|
.map((record) => record.intent.id);
|
|
397
|
+
// Work stopped on a person, kept apart from the rest of terminalBlocked. Every
|
|
398
|
+
// other terminal block is something an agent could in principle clear; this one
|
|
399
|
+
// can only be cleared by a ruling, so it is the thing worth saying out loud.
|
|
400
|
+
const decisionBlocked = work
|
|
401
|
+
.filter((record) => record.projection.blocked_on_decision !== undefined)
|
|
402
|
+
.map((record) => ({
|
|
403
|
+
work_id: record.intent.id,
|
|
404
|
+
decision_id: record.projection.blocked_on_decision || undefined,
|
|
405
|
+
}));
|
|
359
406
|
const attentionWork = unique([
|
|
360
407
|
...pendingApproval,
|
|
361
408
|
...(intent.automation.accept_passing_proof ? [] : reviews),
|
|
@@ -371,6 +418,12 @@ function deriveProjection(
|
|
|
371
418
|
record.intent.id === intent.root_work_id && record.projection.state === "succeeded",
|
|
372
419
|
);
|
|
373
420
|
const milestonesCompleted = plans.milestones_completed + (initialRootAccepted ? 1 : 0);
|
|
421
|
+
const resumableDispatchable = intent.automation.resume_approved ? resumableWork : [];
|
|
422
|
+
const attemptDispatchable = [
|
|
423
|
+
...readyWork,
|
|
424
|
+
...(intent.automation.retry_blocked ? retryableWork : []),
|
|
425
|
+
];
|
|
426
|
+
const dispatchable = [...resumableDispatchable, ...attemptDispatchable];
|
|
374
427
|
const base = {
|
|
375
428
|
id: intent.id,
|
|
376
429
|
title: intent.title,
|
|
@@ -381,23 +434,25 @@ function deriveProjection(
|
|
|
381
434
|
resumable_work: resumableWork,
|
|
382
435
|
retryable_work: retryableWork,
|
|
383
436
|
attention_work: attentionWork,
|
|
437
|
+
decision_blocked_work: decisionBlocked,
|
|
384
438
|
attempts_used: attemptsUsed,
|
|
385
|
-
attempts_remaining: Math.max(0, intent.limits.max_total_attempts -
|
|
439
|
+
attempts_remaining: Math.max(0, intent.limits.max_total_attempts - chargedUsed),
|
|
386
440
|
specialists: Object.keys(intent.specialists),
|
|
387
441
|
plan_generation: plans.generation,
|
|
388
442
|
replans_used: plans.plans.length,
|
|
389
|
-
replans_remaining: Math.max(0, (intent.replanning?.max_replans ?? 0) -
|
|
443
|
+
replans_remaining: Math.max(0, (intent.replanning?.max_replans ?? 0) - chargedReplans),
|
|
390
444
|
milestones_completed: milestonesCompleted,
|
|
391
445
|
milestones_remaining: Math.max(0, (intent.mission?.max_milestones ?? 0) - milestonesCompleted),
|
|
392
446
|
pending_plan_id: plans.latest?.status === "proposed" ? plans.latest.request.id : undefined,
|
|
393
447
|
latest_plan_status: plans.latest?.status,
|
|
448
|
+
...(replanConsumption.planner_no_proposal > 0 ? { replan_consumption: replanConsumption } : {}),
|
|
394
449
|
governed_work_ids: governed.map((record) => record.intent.id),
|
|
395
450
|
};
|
|
396
|
-
if (!ignoreLease &&
|
|
451
|
+
if (!ignoreLease && governorLeaseIsLive(coordRoot, intent.id)) {
|
|
397
452
|
return {
|
|
398
453
|
...base,
|
|
399
454
|
state: "running",
|
|
400
|
-
reason: "the foreground
|
|
455
|
+
reason: "the foreground governor holds the goal lease",
|
|
401
456
|
next_action: "wait_for_run",
|
|
402
457
|
};
|
|
403
458
|
}
|
|
@@ -409,6 +464,19 @@ function deriveProjection(
|
|
|
409
464
|
next_action: "none",
|
|
410
465
|
};
|
|
411
466
|
}
|
|
467
|
+
// ADR 0050: a mission whose completion was reopened dispatches the reopened work
|
|
468
|
+
// before anything else. Without this the goal falls through to the milestone
|
|
469
|
+
// branch below, which still sees a succeeded root when the reopened item is one
|
|
470
|
+
// of its children, and would spend a replan reassessing a mission whose real
|
|
471
|
+
// remaining work is already sitting in ready_work.
|
|
472
|
+
if (intent.mission && plans.latest?.status === "reopened" && dispatchable.length > 0) {
|
|
473
|
+
return {
|
|
474
|
+
...base,
|
|
475
|
+
state: "ready",
|
|
476
|
+
reason: `mission completion was reopened; ${dispatchable.length} work item${dispatchable.length === 1 ? " is" : "s are"} dispatchable`,
|
|
477
|
+
next_action: "run",
|
|
478
|
+
};
|
|
479
|
+
}
|
|
412
480
|
if (plans.latest?.status === "proposed") {
|
|
413
481
|
return {
|
|
414
482
|
...base,
|
|
@@ -433,7 +501,58 @@ function deriveProjection(
|
|
|
433
501
|
next_action: "replan",
|
|
434
502
|
};
|
|
435
503
|
}
|
|
436
|
-
|
|
504
|
+
// ADR 0046: a planner failure that never touched the plan is handled before
|
|
505
|
+
// the replan path. Otherwise a "failed" plan falls through to canReplan and
|
|
506
|
+
// the runner replans an unchanged environment on every tick — the failure mode
|
|
507
|
+
// that burned the measured 19 "codex not found" replans.
|
|
508
|
+
if (plans.latest?.status === "failed" && plans.latest.class === "environment") {
|
|
509
|
+
// A missing precondition (the planner binary was absent). Retrying an
|
|
510
|
+
// unchanged environment cannot help, so the goal STOPS and names it. A human
|
|
511
|
+
// who installs the binary can re-run; the failure was uncharged, so the
|
|
512
|
+
// replan budget is intact.
|
|
513
|
+
return {
|
|
514
|
+
...base,
|
|
515
|
+
state: "blocked",
|
|
516
|
+
reason: `planning could not start; a required precondition is missing: ${plans.latest.reason ?? "the planner binary was absent"}`,
|
|
517
|
+
next_action: "none",
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
if (plans.latest?.status === "failed" && plans.latest.class === "decision") {
|
|
521
|
+
// The planner concluded that a human must rule before this can be planned.
|
|
522
|
+
// Stop and say so. Replanning would re-ask a question the planner already
|
|
523
|
+
// answered correctly, which is how a goal spins without a person noticing.
|
|
524
|
+
return {
|
|
525
|
+
...base,
|
|
526
|
+
state: "blocked",
|
|
527
|
+
reason: `planning is waiting on a human decision: ${plans.latest.reason ?? "the planner did not say which"}`,
|
|
528
|
+
next_action: "none",
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (plans.latest?.status === "failed" && plans.latest.class === "upstream") {
|
|
532
|
+
// The vendor refused. Uncharged replans do not spend max_replans, so the
|
|
533
|
+
// only brake on an unending outage is this consecutive bound; at the limit
|
|
534
|
+
// the goal stops and names the outside service, distinct from work-blocked.
|
|
535
|
+
let trailingUncharged = 0;
|
|
536
|
+
for (let index = plans.plans.length - 1; index >= 0; index--) {
|
|
537
|
+
const plan = plans.plans[index]!;
|
|
538
|
+
if (plan.status !== "failed" || !isUnchargedPlanClass(plan.class)) {
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
trailingUncharged++;
|
|
542
|
+
}
|
|
543
|
+
if (trailingUncharged >= MAX_UNCHARGED_REPLANS) {
|
|
544
|
+
return {
|
|
545
|
+
...base,
|
|
546
|
+
state: "blocked",
|
|
547
|
+
reason: `planning is blocked waiting on an outside service after ${trailingUncharged} consecutive uncharged attempt(s): ${plans.latest.reason ?? "the vendor refused"}`,
|
|
548
|
+
next_action: "none",
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
// Under the bound: fall through so the goal replans when nothing else is
|
|
552
|
+
// dispatchable — the vendor may have recovered. canReplan reads
|
|
553
|
+
// chargedReplans, so these uncharged attempts do not exhaust max_replans.
|
|
554
|
+
}
|
|
555
|
+
const triggerFingerprint = governorGraphFingerprint({
|
|
437
556
|
rootWorkId: plans.active_root_work_id,
|
|
438
557
|
generation: plans.generation,
|
|
439
558
|
work,
|
|
@@ -443,23 +562,27 @@ function deriveProjection(
|
|
|
443
562
|
plans.latest.request.prior_root_work_id === plans.active_root_work_id &&
|
|
444
563
|
plans.latest.status === "attention";
|
|
445
564
|
if (latestHandledSameGraph && plans.latest) {
|
|
565
|
+
const canRetry =
|
|
566
|
+
intent.replanning !== undefined && chargedReplans < intent.replanning.max_replans;
|
|
446
567
|
return {
|
|
447
568
|
...base,
|
|
448
569
|
state: "awaiting_attention",
|
|
449
|
-
reason:
|
|
570
|
+
reason: canRetry
|
|
571
|
+
? (plans.latest.reason ?? `plan ${plans.latest.request.id} requires attention`)
|
|
572
|
+
: exhaustedAttentionReason(plans.latest, replanConsumption),
|
|
450
573
|
attention_plan_id: plans.latest.request.id,
|
|
451
|
-
next_action:
|
|
452
|
-
intent.replanning && plans.plans.length < intent.replanning.max_replans
|
|
453
|
-
? "retry_plan"
|
|
454
|
-
: "none",
|
|
574
|
+
next_action: canRetry ? "retry_plan" : "none",
|
|
455
575
|
};
|
|
456
576
|
}
|
|
457
577
|
if (!root && intent.mission) {
|
|
458
|
-
if (!intent.replanning ||
|
|
578
|
+
if (!intent.replanning || chargedReplans >= intent.replanning.max_replans) {
|
|
459
579
|
return {
|
|
460
580
|
...base,
|
|
461
581
|
state: "budget_exhausted",
|
|
462
|
-
reason:
|
|
582
|
+
reason: replanBudgetReason(
|
|
583
|
+
"mission exhausted its planning budget before creating an initial milestone",
|
|
584
|
+
replanConsumption,
|
|
585
|
+
),
|
|
463
586
|
next_action: "none",
|
|
464
587
|
};
|
|
465
588
|
}
|
|
@@ -470,13 +593,16 @@ function deriveProjection(
|
|
|
470
593
|
next_action: "plan_initial",
|
|
471
594
|
};
|
|
472
595
|
}
|
|
473
|
-
if (!root) throw new Error(`
|
|
596
|
+
if (!root) throw new Error(`governor ${intent.id} root work is missing`);
|
|
474
597
|
if (root.projection.state === "succeeded" && intent.mission) {
|
|
475
|
-
if (!intent.replanning ||
|
|
598
|
+
if (!intent.replanning || chargedReplans >= intent.replanning.max_replans) {
|
|
476
599
|
return {
|
|
477
600
|
...base,
|
|
478
601
|
state: "budget_exhausted",
|
|
479
|
-
reason:
|
|
602
|
+
reason: replanBudgetReason(
|
|
603
|
+
"mission exhausted its planning budget at a milestone boundary",
|
|
604
|
+
replanConsumption,
|
|
605
|
+
),
|
|
480
606
|
next_action: "none",
|
|
481
607
|
};
|
|
482
608
|
}
|
|
@@ -495,12 +621,6 @@ function deriveProjection(
|
|
|
495
621
|
next_action: "none",
|
|
496
622
|
};
|
|
497
623
|
}
|
|
498
|
-
const resumableDispatchable = intent.automation.resume_approved ? resumableWork : [];
|
|
499
|
-
const attemptDispatchable = [
|
|
500
|
-
...readyWork,
|
|
501
|
-
...(intent.automation.retry_blocked ? retryableWork : []),
|
|
502
|
-
];
|
|
503
|
-
const dispatchable = [...resumableDispatchable, ...attemptDispatchable];
|
|
504
624
|
if (intent.automation.accept_passing_proof && reviews.length > 0) {
|
|
505
625
|
return {
|
|
506
626
|
...base,
|
|
@@ -517,7 +637,7 @@ function deriveProjection(
|
|
|
517
637
|
next_action: "run",
|
|
518
638
|
};
|
|
519
639
|
}
|
|
520
|
-
if (
|
|
640
|
+
if (chargedUsed >= intent.limits.max_total_attempts && attemptDispatchable.length > 0) {
|
|
521
641
|
return {
|
|
522
642
|
...base,
|
|
523
643
|
state: "budget_exhausted",
|
|
@@ -565,7 +685,7 @@ function deriveProjection(
|
|
|
565
685
|
next_action: "retry",
|
|
566
686
|
};
|
|
567
687
|
}
|
|
568
|
-
if (
|
|
688
|
+
if (chargedUsed >= intent.limits.max_total_attempts && cancelled.length === 0) {
|
|
569
689
|
return {
|
|
570
690
|
...base,
|
|
571
691
|
state: "budget_exhausted",
|
|
@@ -575,7 +695,7 @@ function deriveProjection(
|
|
|
575
695
|
}
|
|
576
696
|
const canReplan =
|
|
577
697
|
intent.replanning !== undefined &&
|
|
578
|
-
|
|
698
|
+
chargedReplans < intent.replanning.max_replans &&
|
|
579
699
|
cancelled.length === 0 &&
|
|
580
700
|
!latestHandledSameGraph;
|
|
581
701
|
if (canReplan) {
|
|
@@ -590,54 +710,149 @@ function deriveProjection(
|
|
|
590
710
|
}
|
|
591
711
|
if (
|
|
592
712
|
intent.replanning &&
|
|
593
|
-
|
|
713
|
+
chargedReplans >= intent.replanning.max_replans &&
|
|
594
714
|
cancelled.length === 0
|
|
595
715
|
) {
|
|
596
716
|
return {
|
|
597
717
|
...base,
|
|
598
718
|
state: "budget_exhausted",
|
|
599
|
-
reason:
|
|
719
|
+
reason: replanBudgetReason(
|
|
720
|
+
`goal exhausted its ${intent.replanning.max_replans} replans`,
|
|
721
|
+
replanConsumption,
|
|
722
|
+
),
|
|
600
723
|
next_action: "none",
|
|
601
724
|
};
|
|
602
725
|
}
|
|
603
726
|
return {
|
|
604
727
|
...base,
|
|
605
728
|
state: "blocked",
|
|
729
|
+
// Lead with the decisions when there are any. "3 work items need
|
|
730
|
+
// intervention" reads as a queue an agent will get to; "waiting on you to
|
|
731
|
+
// rule on X" is the only phrasing that reaches the person who can clear it.
|
|
606
732
|
reason:
|
|
607
|
-
|
|
608
|
-
? `${
|
|
609
|
-
:
|
|
733
|
+
decisionBlocked.length > 0
|
|
734
|
+
? `${decisionBlocked.length} work item${decisionBlocked.length === 1 ? " is" : "s are"} waiting on a human decision: ${describeDecisionBlocks(decisionBlocked)}`
|
|
735
|
+
: attentionWork.length > 0
|
|
736
|
+
? `${attentionWork.length} work item${attentionWork.length === 1 ? " needs" : "s need"} intervention`
|
|
737
|
+
: "goal graph has no legal progress action",
|
|
610
738
|
next_action: "none",
|
|
611
739
|
};
|
|
612
740
|
}
|
|
613
741
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
742
|
+
/** Bounded, operator-readable list of what is waiting on a ruling. */
|
|
743
|
+
function describeDecisionBlocks(
|
|
744
|
+
blocks: readonly { work_id: string; decision_id?: string }[],
|
|
745
|
+
): string {
|
|
746
|
+
const shown = blocks
|
|
747
|
+
.slice(0, 3)
|
|
748
|
+
.map((b) => (b.decision_id ? `${b.work_id} (${b.decision_id})` : b.work_id))
|
|
749
|
+
.join(", ");
|
|
750
|
+
return blocks.length > 3 ? `${shown}, +${blocks.length - 3} more` : shown;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
interface ReplanConsumption {
|
|
754
|
+
reviewer_rejection: number;
|
|
755
|
+
planner_no_proposal: number;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Classify each consumed replan that did not advance the graph, using existing
|
|
760
|
+
* plan seams only. A plan carries a review receipt exactly when a proposal was
|
|
761
|
+
* produced and independently reviewed, so its presence separates a reviewer
|
|
762
|
+
* rejection from a planner run that produced no reviewable proposal. Uncharged
|
|
763
|
+
* environment/upstream planner failures (ADR 0046) do not spend the replan
|
|
764
|
+
* budget and are attributed to their outside precondition, not counted here.
|
|
765
|
+
*/
|
|
766
|
+
/** Whether a failed plan told us nothing about the plan, and so must not spend
|
|
767
|
+
* a replan (ADR 0046). All three classes mean "replanning this cannot help":
|
|
768
|
+
* the binary is missing, the vendor is down, or a person has to rule. */
|
|
769
|
+
function isUnchargedPlanClass(value: GovernorPlanRecord["class"]): boolean {
|
|
770
|
+
return value === "environment" || value === "upstream" || value === "decision";
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function classifyReplanConsumption(plans: GovernorPlanHistory): ReplanConsumption {
|
|
774
|
+
let reviewerRejection = 0;
|
|
775
|
+
let plannerNoProposal = 0;
|
|
776
|
+
for (const plan of plans.plans) {
|
|
777
|
+
if (plan.status === "failed" && isUnchargedPlanClass(plan.class)) {
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (plan.review && plan.review.status !== "passed") {
|
|
781
|
+
reviewerRejection++;
|
|
782
|
+
} else if (
|
|
783
|
+
!plan.review &&
|
|
784
|
+
(plan.status === "attention" ||
|
|
785
|
+
plan.status === "interrupted" ||
|
|
786
|
+
plan.status === "failed" ||
|
|
787
|
+
plan.status === "retry_requested")
|
|
788
|
+
) {
|
|
789
|
+
// `retry_requested` is a planner no-proposal attention the operator asked to
|
|
790
|
+
// replan: the original run still produced no reviewable proposal and still
|
|
791
|
+
// spent a charged replan, so it belongs in this bucket even though a later
|
|
792
|
+
// plan superseded it. Omitting it would misattribute a retried no-proposal
|
|
793
|
+
// to nothing and let the reason read as pure review exhaustion.
|
|
794
|
+
plannerNoProposal++;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return { reviewer_rejection: reviewerRejection, planner_no_proposal: plannerNoProposal };
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Reason for a goal held at `awaiting_attention/none` because its latest plan is
|
|
802
|
+
* an unresolved attention and no replan slot remains. The latest plan's own
|
|
803
|
+
* per-plan reason always leads (a reviewed-and-rejected latest reports its
|
|
804
|
+
* review-round exhaustion verbatim), then `replanBudgetReason` names the planner
|
|
805
|
+
* no-proposal share of the consumed budget. This keeps the `projection.reason`
|
|
806
|
+
* field carrying the same truth the operator-visible row does: a goal whose
|
|
807
|
+
* budget was mostly spent by planner no-proposal runs is not attributed wholesale
|
|
808
|
+
* to review just because its final, reviewed plan happened to be rejected.
|
|
809
|
+
*/
|
|
810
|
+
function exhaustedAttentionReason(
|
|
811
|
+
latest: GovernorPlanRecord,
|
|
812
|
+
consumption: ReplanConsumption,
|
|
813
|
+
): string {
|
|
814
|
+
const fallback = latest.reason ?? `plan ${latest.request.id} requires attention`;
|
|
815
|
+
return replanBudgetReason(fallback, consumption);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Append planner no-proposal attribution to a replan-exhaustion reason. Returns
|
|
820
|
+
* the base reason unchanged when no consumed replan was a planner no-proposal
|
|
821
|
+
* outcome, so goals without that history project exactly as before.
|
|
822
|
+
*/
|
|
823
|
+
function replanBudgetReason(base: string, consumption: ReplanConsumption): string {
|
|
824
|
+
if (consumption.planner_no_proposal <= 0) return base;
|
|
825
|
+
const count = consumption.planner_no_proposal;
|
|
826
|
+
return `${base}; ${count} replan${count === 1 ? "" : "s"} ended with the planner producing no proposal`;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function readGovernorIntent(coordRoot: string, goalId: string): GovernorIntent {
|
|
830
|
+
assertGovernorId(goalId);
|
|
831
|
+
const path = governorIntentPath(coordRoot, goalId);
|
|
832
|
+
if (!existsSync(path)) throw new Error(`governor ${goalId} does not exist`);
|
|
618
833
|
const size = statSync(path).size;
|
|
619
834
|
if (size <= 0 || size > MAX_INTENT_BYTES) {
|
|
620
|
-
throw new Error(`
|
|
835
|
+
throw new Error(`governor intent has invalid size ${size}`);
|
|
621
836
|
}
|
|
622
|
-
let intent:
|
|
837
|
+
let intent: GovernorIntent;
|
|
623
838
|
try {
|
|
624
|
-
intent = JSON.parse(readFileSync(path, "utf8")) as
|
|
839
|
+
intent = JSON.parse(readFileSync(path, "utf8")) as GovernorIntent;
|
|
625
840
|
} catch (error) {
|
|
626
|
-
throw new Error(`cannot parse
|
|
841
|
+
throw new Error(`cannot parse governor intent ${goalId}: ${(error as Error).message}`);
|
|
627
842
|
}
|
|
628
843
|
validateIntent(intent, goalId);
|
|
629
844
|
return intent;
|
|
630
845
|
}
|
|
631
846
|
|
|
632
|
-
function validateIntent(intent:
|
|
847
|
+
function validateIntent(intent: GovernorIntent, goalId: string): void {
|
|
633
848
|
if (
|
|
634
|
-
intent.schema_version !==
|
|
849
|
+
intent.schema_version !== GOVERNOR_INTENT_SCHEMA_VERSION ||
|
|
635
850
|
intent.id !== goalId ||
|
|
636
851
|
!validTimestamp(intent.created_at)
|
|
637
852
|
) {
|
|
638
|
-
throw new Error(`
|
|
853
|
+
throw new Error(`governor intent ${goalId} has an unsupported or mismatched schema`);
|
|
639
854
|
}
|
|
640
|
-
bounded(intent.title, "
|
|
855
|
+
bounded(intent.title, "governor title", MAX_TITLE);
|
|
641
856
|
assertWorkId(intent.root_work_id);
|
|
642
857
|
if (intent.mission) {
|
|
643
858
|
const normalizedMission = normalizeMission({
|
|
@@ -646,79 +861,79 @@ function validateIntent(intent: SupervisorIntent, goalId: string): void {
|
|
|
646
861
|
maxMilestones: intent.mission.max_milestones,
|
|
647
862
|
});
|
|
648
863
|
if (JSON.stringify(normalizedMission) !== JSON.stringify(intent.mission)) {
|
|
649
|
-
throw new Error(`
|
|
864
|
+
throw new Error(`governor intent ${goalId} mission is not canonical`);
|
|
650
865
|
}
|
|
651
866
|
if (!intent.replanning) {
|
|
652
|
-
throw new Error(`
|
|
867
|
+
throw new Error(`governor intent ${goalId} mission requires replanning`);
|
|
653
868
|
}
|
|
654
869
|
}
|
|
655
870
|
const specialists = normalizeWorkflowSpecialists(intent.specialists);
|
|
656
871
|
if (JSON.stringify(specialists) !== JSON.stringify(intent.specialists)) {
|
|
657
|
-
throw new Error(`
|
|
872
|
+
throw new Error(`governor intent ${goalId} specialists are not canonical`);
|
|
658
873
|
}
|
|
659
874
|
if (JSON.stringify(normalizeLimits(intent.limits)) !== JSON.stringify(intent.limits)) {
|
|
660
|
-
throw new Error(`
|
|
875
|
+
throw new Error(`governor intent ${goalId} limits are not canonical`);
|
|
661
876
|
}
|
|
662
877
|
if (
|
|
663
878
|
JSON.stringify(normalizeAutomation(intent.automation)) !== JSON.stringify(intent.automation)
|
|
664
879
|
) {
|
|
665
|
-
throw new Error(`
|
|
880
|
+
throw new Error(`governor intent ${goalId} automation policy is not canonical`);
|
|
666
881
|
}
|
|
667
882
|
if (intent.replanning) validateReplanning(intent.replanning, specialists, intent.limits, goalId);
|
|
668
883
|
}
|
|
669
884
|
|
|
670
|
-
function normalizeMission(input:
|
|
885
|
+
function normalizeMission(input: CreateGovernorMissionInput): GovernorMission {
|
|
671
886
|
if (!Array.isArray(input.acceptance)) {
|
|
672
|
-
throw new Error("
|
|
887
|
+
throw new Error("governor mission acceptance must be an array");
|
|
673
888
|
}
|
|
674
889
|
if (input.acceptance.length < 1 || input.acceptance.length > MAX_ACCEPTANCE) {
|
|
675
|
-
throw new Error(`
|
|
890
|
+
throw new Error(`governor mission acceptance must contain 1 to ${MAX_ACCEPTANCE} criteria`);
|
|
676
891
|
}
|
|
677
892
|
const acceptance = input.acceptance.map((criterion, index) =>
|
|
678
|
-
bounded(criterion, `
|
|
893
|
+
bounded(criterion, `governor mission acceptance[${index}]`, 500),
|
|
679
894
|
);
|
|
680
895
|
if (new Set(acceptance).size !== acceptance.length) {
|
|
681
|
-
throw new Error("
|
|
896
|
+
throw new Error("governor mission acceptance criteria must be unique");
|
|
682
897
|
}
|
|
683
898
|
return {
|
|
684
|
-
objective: bounded(input.objective, "
|
|
899
|
+
objective: bounded(input.objective, "governor mission objective", MAX_OBJECTIVE),
|
|
685
900
|
acceptance,
|
|
686
|
-
max_milestones: positive(input.maxMilestones ?? 4, "
|
|
901
|
+
max_milestones: positive(input.maxMilestones ?? 4, "governor mission max_milestones", 20),
|
|
687
902
|
};
|
|
688
903
|
}
|
|
689
904
|
|
|
690
|
-
function missionTitle(mission:
|
|
905
|
+
function missionTitle(mission: GovernorMission): string {
|
|
691
906
|
return mission.objective.length <= MAX_TITLE
|
|
692
907
|
? mission.objective
|
|
693
908
|
: `${mission.objective.slice(0, MAX_TITLE - 1).trimEnd()}…`;
|
|
694
909
|
}
|
|
695
910
|
|
|
696
|
-
function normalizeLimits(input: Partial<
|
|
911
|
+
function normalizeLimits(input: Partial<GovernorLimits> | undefined): GovernorLimits {
|
|
697
912
|
return {
|
|
698
|
-
max_cycles: positive(input?.max_cycles ?? 50, "
|
|
913
|
+
max_cycles: positive(input?.max_cycles ?? 50, "governor max_cycles", 1_000),
|
|
699
914
|
max_runtime_ms: positive(
|
|
700
915
|
input?.max_runtime_ms ?? 4 * 60 * 60 * 1_000,
|
|
701
|
-
"
|
|
916
|
+
"governor max_runtime_ms",
|
|
702
917
|
7 * 24 * 60 * 60 * 1_000,
|
|
703
918
|
),
|
|
704
|
-
max_parallel_work: positive(input?.max_parallel_work ?? 1, "
|
|
919
|
+
max_parallel_work: positive(input?.max_parallel_work ?? 1, "governor max_parallel_work", 20),
|
|
705
920
|
max_total_attempts: positive(
|
|
706
921
|
input?.max_total_attempts ?? 100,
|
|
707
|
-
"
|
|
922
|
+
"governor max_total_attempts",
|
|
708
923
|
10_000,
|
|
709
924
|
),
|
|
710
925
|
max_agents_per_work: positive(
|
|
711
926
|
input?.max_agents_per_work ?? 20,
|
|
712
|
-
"
|
|
927
|
+
"governor max_agents_per_work",
|
|
713
928
|
1_000,
|
|
714
929
|
),
|
|
715
|
-
agent_concurrency: positive(input?.agent_concurrency ?? 4, "
|
|
930
|
+
agent_concurrency: positive(input?.agent_concurrency ?? 4, "governor agent_concurrency", 100),
|
|
716
931
|
};
|
|
717
932
|
}
|
|
718
933
|
|
|
719
934
|
function normalizeAutomation(
|
|
720
|
-
input: Partial<
|
|
721
|
-
):
|
|
935
|
+
input: Partial<GovernorAutomationPolicy> | undefined,
|
|
936
|
+
): GovernorAutomationPolicy {
|
|
722
937
|
return {
|
|
723
938
|
accept_passing_proof: boolean(input?.accept_passing_proof ?? false, "accept_passing_proof"),
|
|
724
939
|
resume_approved: boolean(input?.resume_approved ?? true, "resume_approved"),
|
|
@@ -727,10 +942,10 @@ function normalizeAutomation(
|
|
|
727
942
|
}
|
|
728
943
|
|
|
729
944
|
function normalizeReplanning(
|
|
730
|
-
input:
|
|
945
|
+
input: CreateGovernorReplanningInput,
|
|
731
946
|
specialistsInput: Readonly<Record<string, WorkflowSpecialistProfile>>,
|
|
732
|
-
limits:
|
|
733
|
-
):
|
|
947
|
+
limits: GovernorLimits,
|
|
948
|
+
): GovernorReplanningPolicy {
|
|
734
949
|
const specialists = normalizeWorkflowSpecialists(specialistsInput);
|
|
735
950
|
const plannerSpecialist = bounded(input.plannerSpecialist, "planner specialist", 100);
|
|
736
951
|
if (!specialists[plannerSpecialist]) {
|
|
@@ -794,17 +1009,17 @@ function normalizeReplanning(
|
|
|
794
1009
|
}
|
|
795
1010
|
|
|
796
1011
|
function normalizeReviewPolicy(
|
|
797
|
-
input: NonNullable<
|
|
1012
|
+
input: NonNullable<CreateGovernorReplanningInput["review"]>,
|
|
798
1013
|
specialists: Record<string, WorkflowSpecialistProfile>,
|
|
799
1014
|
plannerSpecialist: string,
|
|
800
|
-
limits:
|
|
801
|
-
): NonNullable<
|
|
1015
|
+
limits: GovernorLimits,
|
|
1016
|
+
): NonNullable<GovernorReplanningPolicy["review"]> {
|
|
802
1017
|
if (!Array.isArray(input.reviewerSpecialists) || input.reviewerSpecialists.length < 1) {
|
|
803
1018
|
throw new Error("replanning review requires at least one reviewer specialist");
|
|
804
1019
|
}
|
|
805
|
-
if (input.reviewerSpecialists.length >
|
|
1020
|
+
if (input.reviewerSpecialists.length > MAX_GOVERNOR_PLAN_REVIEWERS) {
|
|
806
1021
|
throw new Error(
|
|
807
|
-
`replanning review cannot exceed ${
|
|
1022
|
+
`replanning review cannot exceed ${MAX_GOVERNOR_PLAN_REVIEWERS} reviewer specialists`,
|
|
808
1023
|
);
|
|
809
1024
|
}
|
|
810
1025
|
const reviewerSpecialists = input.reviewerSpecialists.map((specialist, index) =>
|
|
@@ -824,7 +1039,7 @@ function normalizeReviewPolicy(
|
|
|
824
1039
|
const maxRevisionRounds = nonNegative(
|
|
825
1040
|
input.maxRevisionRounds,
|
|
826
1041
|
"replanning review max_revision_rounds",
|
|
827
|
-
|
|
1042
|
+
MAX_GOVERNOR_PLAN_REVISION_ROUNDS,
|
|
828
1043
|
);
|
|
829
1044
|
const worstCaseAgents =
|
|
830
1045
|
1 + reviewerSpecialists.length * (maxRevisionRounds + 1) + maxRevisionRounds;
|
|
@@ -840,24 +1055,24 @@ function normalizeReviewPolicy(
|
|
|
840
1055
|
}
|
|
841
1056
|
|
|
842
1057
|
function validateReplanning(
|
|
843
|
-
policy:
|
|
1058
|
+
policy: GovernorReplanningPolicy,
|
|
844
1059
|
specialists: Record<string, WorkflowSpecialistProfile>,
|
|
845
|
-
limits:
|
|
1060
|
+
limits: GovernorLimits,
|
|
846
1061
|
goalId: string,
|
|
847
1062
|
): void {
|
|
848
1063
|
if (!specialists[policy.planner_specialist]) {
|
|
849
|
-
throw new Error(`
|
|
1064
|
+
throw new Error(`governor ${goalId} planner specialist is not in the frozen team`);
|
|
850
1065
|
}
|
|
851
1066
|
boolean(policy.auto_apply, "replanning auto_apply");
|
|
852
1067
|
positive(policy.max_replans, "replanning max_replans", 100);
|
|
853
1068
|
positive(policy.max_work_items_per_plan, "replanning max_work_items_per_plan", 25);
|
|
854
1069
|
positive(policy.max_total_work_items, "replanning max_total_work_items", 100);
|
|
855
1070
|
if (policy.max_work_items_per_plan > policy.max_total_work_items) {
|
|
856
|
-
throw new Error(`
|
|
1071
|
+
throw new Error(`governor ${goalId} replanning work-item bounds are inconsistent`);
|
|
857
1072
|
}
|
|
858
1073
|
const entries = Object.entries(policy.templates ?? {});
|
|
859
1074
|
if (entries.length < 1 || entries.length > MAX_TEMPLATES) {
|
|
860
|
-
throw new Error(`
|
|
1075
|
+
throw new Error(`governor ${goalId} has invalid replanning templates`);
|
|
861
1076
|
}
|
|
862
1077
|
let rootCapable = false;
|
|
863
1078
|
for (const [id, template] of entries) {
|
|
@@ -870,32 +1085,32 @@ function validateReplanning(
|
|
|
870
1085
|
template.max_attempts > 100 ||
|
|
871
1086
|
typeof template.root !== "boolean"
|
|
872
1087
|
) {
|
|
873
|
-
throw new Error(`
|
|
1088
|
+
throw new Error(`governor ${goalId} replanning template ${id} is invalid`);
|
|
874
1089
|
}
|
|
875
1090
|
rootCapable ||= template.root;
|
|
876
1091
|
}
|
|
877
|
-
if (!rootCapable) throw new Error(`
|
|
1092
|
+
if (!rootCapable) throw new Error(`governor ${goalId} has no root-capable replanning template`);
|
|
878
1093
|
if (policy.review !== undefined) {
|
|
879
1094
|
const review = policy.review;
|
|
880
1095
|
if (
|
|
881
1096
|
!Array.isArray(review.reviewer_specialists) ||
|
|
882
1097
|
review.reviewer_specialists.length < 1 ||
|
|
883
|
-
review.reviewer_specialists.length >
|
|
1098
|
+
review.reviewer_specialists.length > MAX_GOVERNOR_PLAN_REVIEWERS ||
|
|
884
1099
|
!Number.isSafeInteger(review.max_revision_rounds) ||
|
|
885
1100
|
review.max_revision_rounds < 0 ||
|
|
886
|
-
review.max_revision_rounds >
|
|
1101
|
+
review.max_revision_rounds > MAX_GOVERNOR_PLAN_REVISION_ROUNDS
|
|
887
1102
|
) {
|
|
888
|
-
throw new Error(`
|
|
1103
|
+
throw new Error(`governor ${goalId} replanning review policy is invalid`);
|
|
889
1104
|
}
|
|
890
1105
|
if (new Set(review.reviewer_specialists).size !== review.reviewer_specialists.length) {
|
|
891
|
-
throw new Error(`
|
|
1106
|
+
throw new Error(`governor ${goalId} replanning reviewers are not unique`);
|
|
892
1107
|
}
|
|
893
1108
|
for (const specialist of review.reviewer_specialists) {
|
|
894
1109
|
if (!specialists[specialist]) {
|
|
895
|
-
throw new Error(`
|
|
1110
|
+
throw new Error(`governor ${goalId} reviewer specialist is not in the frozen team`);
|
|
896
1111
|
}
|
|
897
1112
|
if (specialist === policy.planner_specialist) {
|
|
898
|
-
throw new Error(`
|
|
1113
|
+
throw new Error(`governor ${goalId} reviewer specialist cannot be planner`);
|
|
899
1114
|
}
|
|
900
1115
|
}
|
|
901
1116
|
const worstCaseAgents =
|
|
@@ -903,7 +1118,7 @@ function validateReplanning(
|
|
|
903
1118
|
review.reviewer_specialists.length * (review.max_revision_rounds + 1) +
|
|
904
1119
|
review.max_revision_rounds;
|
|
905
1120
|
if (worstCaseAgents > limits.max_agents_per_work) {
|
|
906
|
-
throw new Error(`
|
|
1121
|
+
throw new Error(`governor ${goalId} review policy exceeds the agent budget`);
|
|
907
1122
|
}
|
|
908
1123
|
}
|
|
909
1124
|
}
|
|
@@ -911,12 +1126,12 @@ function validateReplanning(
|
|
|
911
1126
|
function collectGovernedWork(
|
|
912
1127
|
coordRoot: string,
|
|
913
1128
|
originalRootWorkId: string,
|
|
914
|
-
plans:
|
|
1129
|
+
plans: GovernorPlanHistory,
|
|
915
1130
|
active: WorkRecord[],
|
|
916
1131
|
): WorkRecord[] {
|
|
917
1132
|
const governed = new Map<string, WorkRecord>();
|
|
918
1133
|
if (workIntentExists(coordRoot, originalRootWorkId)) {
|
|
919
|
-
for (const record of
|
|
1134
|
+
for (const record of collectGovernorWork(coordRoot, originalRootWorkId)) {
|
|
920
1135
|
governed.set(record.intent.id, record);
|
|
921
1136
|
}
|
|
922
1137
|
}
|
|
@@ -934,17 +1149,17 @@ function missionRootId(goalId: string): string {
|
|
|
934
1149
|
return `${goalId.slice(0, 87)}-mission-root`;
|
|
935
1150
|
}
|
|
936
1151
|
|
|
937
|
-
function
|
|
938
|
-
|
|
939
|
-
return join(resolve(coordRoot), ".harnery", "
|
|
1152
|
+
function governorDir(coordRoot: string, goalId: string): string {
|
|
1153
|
+
assertGovernorId(goalId);
|
|
1154
|
+
return join(resolve(coordRoot), ".harnery", "governors", goalId);
|
|
940
1155
|
}
|
|
941
1156
|
|
|
942
|
-
function
|
|
943
|
-
return join(
|
|
1157
|
+
function governorIntentPath(coordRoot: string, goalId: string): string {
|
|
1158
|
+
return join(governorDir(coordRoot, goalId), "intent.json");
|
|
944
1159
|
}
|
|
945
1160
|
|
|
946
|
-
function
|
|
947
|
-
const lease = readLease(join(
|
|
1161
|
+
function governorLeaseIsLive(coordRoot: string, goalId: string): boolean {
|
|
1162
|
+
const lease = readLease(join(governorDir(coordRoot, goalId), "lease.json"));
|
|
948
1163
|
return lease ? leaseIsLive(lease) : false;
|
|
949
1164
|
}
|
|
950
1165
|
|
|
@@ -986,7 +1201,7 @@ function leaseIsLive(lease: { pid: number; host: string; created_at: string }):
|
|
|
986
1201
|
function writePrivateJson(path: string, value: unknown): void {
|
|
987
1202
|
const body = `${JSON.stringify(value, null, 2)}\n`;
|
|
988
1203
|
if (Buffer.byteLength(body) > MAX_INTENT_BYTES) {
|
|
989
|
-
throw new Error(`
|
|
1204
|
+
throw new Error(`governor intent exceeds ${MAX_INTENT_BYTES} bytes`);
|
|
990
1205
|
}
|
|
991
1206
|
writeFileSync(path, body, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
992
1207
|
chmodSync(path, 0o600);
|
|
@@ -1015,7 +1230,7 @@ function nonNegative(value: unknown, field: string, max: number): number {
|
|
|
1015
1230
|
}
|
|
1016
1231
|
|
|
1017
1232
|
function boolean(value: unknown, field: string): boolean {
|
|
1018
|
-
if (typeof value !== "boolean") throw new Error(`
|
|
1233
|
+
if (typeof value !== "boolean") throw new Error(`governor ${field} must be boolean`);
|
|
1019
1234
|
return value;
|
|
1020
1235
|
}
|
|
1021
1236
|
|