harnery 0.30.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.map +1 -1
- package/dist/commands/work.js +38 -22
- 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 +23 -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 +66 -52
- package/dist/core/{supervisor → governor}/plan-types.d.ts +57 -56
- 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 +125 -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 +228 -108
- 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/state.d.ts +12 -4
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +50 -17
- 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 +138 -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 +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/proof.d.ts +11 -5
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +45 -36
- 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 +9 -6
- 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 +9 -6
- 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 +9 -6
- package/dist/core/workflow/spawn-failure.d.ts +1 -1
- package/dist/core/workflow/spawn-failure.js +1 -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 +98 -51
- 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/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 +50 -35
- 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 +23 -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 +112 -97
- package/src/core/{supervisor → governor}/plan-types.ts +59 -56
- package/src/core/{supervisor → governor}/planning.ts +223 -192
- 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 +325 -167
- 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/state.ts +65 -23
- 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 +155 -90
- package/src/core/workflow/evidence-preflight.ts +165 -0
- package/src/core/workflow/index.ts +3 -3
- package/src/core/workflow/proof.ts +60 -50
- 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 +13 -10
- package/src/core/workflow/spawn-codex.ts +13 -10
- package/src/core/workflow/spawn-cursor.ts +13 -10
- package/src/core/workflow/spawn-failure.ts +1 -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 +108 -51
- 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/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.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
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* UserPromptSubmit UX renderer. Combines the peer-refresh dedup, the
|
|
3
|
-
* council-pending hash-dedup,
|
|
3
|
+
* council-pending hash-dedup, the cross-adapter first-session naming nudge,
|
|
4
|
+
* the Cursor/Codex set-task staleness nudge, and the Codex status-footer
|
|
5
|
+
* reminder.
|
|
4
6
|
* agent-hook's user_prompt.submit post-emit handler calls this
|
|
5
|
-
* and forwards the result as the
|
|
7
|
+
* and forwards the result as the adapter-shaped additionalContext payload.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
9
|
+
* Four subsections combined into one additionalContext payload:
|
|
8
10
|
* 1. Peer table: semantically-relevant peer fields hashed; only re-emits
|
|
9
11
|
* when peers change (name + instance_id + session_id + kind + started_at
|
|
10
12
|
* + sorted(files_touched) + platform, sorted by instance_id).
|
|
11
13
|
* 2. Council pending: pending open-council IDs hashed; re-emits when the
|
|
12
14
|
* ID set changes.
|
|
13
|
-
* 3.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* 3. Focus nudge. Before the first set-task in a human-facing session, tells
|
|
16
|
+
* every adapter to reproduce set-task's suggested session name in a fenced
|
|
17
|
+
* code block. Cursor/Codex additionally get the existing unset/stale-task
|
|
18
|
+
* reminder because their Stop hooks do not enforce task declarations as
|
|
19
|
+
* reliably as Claude Code's.
|
|
20
|
+
* 4. Codex status footer: a non-deduplicated reminder to put the live status
|
|
21
|
+
* box after the substantive answer. Stop stays observe-only, so missing the
|
|
22
|
+
* footer can never trigger a replacement response.
|
|
17
23
|
*
|
|
18
24
|
* Hash files live at:
|
|
19
25
|
* .harnery/.last-peer-hash.<instance_id>
|
|
@@ -36,6 +42,7 @@ import {
|
|
|
36
42
|
import { join } from "node:path";
|
|
37
43
|
|
|
38
44
|
import { coordEnv } from "../../../lib/env.ts";
|
|
45
|
+
import { resolveBinName } from "../../config.ts";
|
|
39
46
|
import { type RemoteMachine, readRemoteMachines } from "../../presence/index.ts";
|
|
40
47
|
import { formatPendingCouncils } from "./session-context.ts";
|
|
41
48
|
|
|
@@ -51,7 +58,9 @@ interface HeartbeatRow {
|
|
|
51
58
|
files_touched?: string[];
|
|
52
59
|
platform?: string;
|
|
53
60
|
task?: string;
|
|
61
|
+
task_updated_at?: string;
|
|
54
62
|
turn_summary?: string;
|
|
63
|
+
workflow_run_id?: string;
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
export interface PromptContextOpts {
|
|
@@ -59,9 +68,15 @@ export interface PromptContextOpts {
|
|
|
59
68
|
instanceId: string;
|
|
60
69
|
sessionId: string;
|
|
61
70
|
agentName?: string;
|
|
62
|
-
/** When true,
|
|
63
|
-
*
|
|
71
|
+
/** When true, remind a human-facing session to print the session name before
|
|
72
|
+
* its first set-task. UserPromptSubmit enables this for every adapter. */
|
|
73
|
+
sessionNameNudge?: boolean;
|
|
74
|
+
/** When true, run the unset/stale-task check after the first set-task.
|
|
75
|
+
* Cursor/Codex use this; Claude Code has Stop-hook transcript enforcement. */
|
|
64
76
|
taskNudge?: boolean;
|
|
77
|
+
/** When true, append the non-deduplicated Codex status-footer reminder.
|
|
78
|
+
* The caller enables this only for human-facing Codex sessions. */
|
|
79
|
+
statusFooterNudge?: boolean;
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
/**
|
|
@@ -73,7 +88,15 @@ export interface PromptContextOpts {
|
|
|
73
88
|
* councils are pending, matching the bash behavior).
|
|
74
89
|
*/
|
|
75
90
|
export function renderPromptContext(opts: PromptContextOpts): string {
|
|
76
|
-
const {
|
|
91
|
+
const {
|
|
92
|
+
coordRoot,
|
|
93
|
+
instanceId,
|
|
94
|
+
sessionId,
|
|
95
|
+
agentName,
|
|
96
|
+
sessionNameNudge,
|
|
97
|
+
taskNudge,
|
|
98
|
+
statusFooterNudge,
|
|
99
|
+
} = opts;
|
|
77
100
|
const sections: string[] = [];
|
|
78
101
|
|
|
79
102
|
// 1. Peer table with hash dedup.
|
|
@@ -86,62 +109,107 @@ export function renderPromptContext(opts: PromptContextOpts): string {
|
|
|
86
109
|
if (councilMsg) sections.push(councilMsg);
|
|
87
110
|
}
|
|
88
111
|
|
|
89
|
-
// 3.
|
|
90
|
-
|
|
91
|
-
|
|
112
|
+
// 3. First-session naming for every adapter, plus unset/stale-task reminders
|
|
113
|
+
// for Cursor/Codex. One state machine avoids a second generic "task unset"
|
|
114
|
+
// reminder immediately after the first-session reminder has been deduped.
|
|
115
|
+
if (sessionNameNudge || taskNudge) {
|
|
116
|
+
const nudgeMsg = computeFocusNudgeIfChanged(coordRoot, instanceId, {
|
|
117
|
+
sessionNameNudge: sessionNameNudge ?? false,
|
|
118
|
+
taskNudge: taskNudge ?? false,
|
|
119
|
+
});
|
|
92
120
|
if (nudgeMsg) sections.push(nudgeMsg);
|
|
93
121
|
}
|
|
94
122
|
|
|
123
|
+
// 4. Codex gets this on every prompt. It is intentionally not hash-deduped:
|
|
124
|
+
// the reminder must be fresh in the model's context when it writes the reply.
|
|
125
|
+
// Missing it is harmless because Codex Stop enforcement remains observe-only.
|
|
126
|
+
if (statusFooterNudge) {
|
|
127
|
+
const statusMsg = renderStatusFooterReminder(coordRoot, instanceId);
|
|
128
|
+
if (statusMsg) sections.push(statusMsg);
|
|
129
|
+
}
|
|
130
|
+
|
|
95
131
|
return sections.join("\n\n");
|
|
96
132
|
}
|
|
97
133
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
134
|
+
function renderStatusFooterReminder(coordRoot: string, selfInstanceId: string): string {
|
|
135
|
+
const hb = readHeartbeat(join(coordRoot, ".harnery", "active", `${selfInstanceId}.json`));
|
|
136
|
+
if (!hb || hb.kind === "subagent" || hb.kind === "transient" || hb.workflow_run_id) return "";
|
|
137
|
+
|
|
138
|
+
const bin = resolveBinName(coordRoot);
|
|
139
|
+
return (
|
|
140
|
+
"Codex status footer: complete the user's request first. " +
|
|
141
|
+
`Then run \`${bin} agents status\` as your final shell call and append its stdout verbatim in a fenced code block at the bottom of the same substantive reply. ` +
|
|
142
|
+
"Keep the answer intact. If the footer is missed, do not retry or replace the reply; the Stop hook is observe-only."
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* First-session naming for every adapter, followed by Cursor/Codex task
|
|
148
|
+
* staleness checks. The absence of `task_updated_at` is the same invariant
|
|
149
|
+
* `agents set-task` uses for `first_of_session`; clears still stamp the field.
|
|
150
|
+
*/
|
|
151
|
+
function computeFocusNudgeIfChanged(
|
|
152
|
+
coordRoot: string,
|
|
153
|
+
selfInstanceId: string,
|
|
154
|
+
opts: { sessionNameNudge: boolean; taskNudge: boolean },
|
|
155
|
+
): string {
|
|
103
156
|
const hbPath = join(coordRoot, ".harnery", "active", `${selfInstanceId}.json`);
|
|
104
157
|
if (!existsSync(hbPath)) return "";
|
|
105
|
-
let hb:
|
|
158
|
+
let hb: HeartbeatRow;
|
|
106
159
|
try {
|
|
107
160
|
hb = JSON.parse(readFileSync(hbPath, "utf8"));
|
|
108
161
|
} catch {
|
|
109
162
|
return "";
|
|
110
163
|
}
|
|
111
164
|
|
|
165
|
+
const hashFile = join(coordRoot, ".harnery", `.last-task-nudge-hash.${selfInstanceId}`);
|
|
166
|
+
// Subagents and workflow children have no human-owned session/tab to rename.
|
|
167
|
+
if (hb.kind === "subagent" || hb.kind === "transient" || hb.workflow_run_id) {
|
|
168
|
+
clearHashFile(hashFile);
|
|
169
|
+
return "";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const bin = resolveBinName(coordRoot);
|
|
112
173
|
const threshold = Number.parseInt(coordEnv("TASK_STALE_SECONDS") ?? "1800", 10);
|
|
113
174
|
const taskValue = hb.task ?? "";
|
|
114
175
|
let needsNudge = false;
|
|
115
176
|
let message = "";
|
|
177
|
+
let nudgeKind = "";
|
|
116
178
|
|
|
117
|
-
if (!
|
|
179
|
+
if (opts.sessionNameNudge && !hb.task_updated_at) {
|
|
180
|
+
needsNudge = true;
|
|
181
|
+
nudgeKind = "session-name";
|
|
182
|
+
message =
|
|
183
|
+
`New session: run \`${bin} agents set-task "<2-5 word session topic>"\` as your first tool call. ` +
|
|
184
|
+
"When it returns `first_of_session: true`, reproduce its `suggested_session_name` value by itself inside a fenced code block at the very top of your reply so the operator can one-click-copy it as the session/tab title. " +
|
|
185
|
+
"Then continue with the task; that first `set-task` also satisfies this turn's focus declaration.";
|
|
186
|
+
} else if (opts.taskNudge && !taskValue) {
|
|
118
187
|
needsNudge = true;
|
|
188
|
+
nudgeKind = "task-unset";
|
|
119
189
|
message =
|
|
120
|
-
|
|
121
|
-
|
|
190
|
+
`Heads up: your \`task\` field is unset. Run \`${bin} agents set-task "<short focus>"\` so peers + the coord dashboard can see what you're working on. ` +
|
|
191
|
+
"(This adapter cannot enforce the declaration from its Stop hook as reliably as Claude Code, so this is a one-time soft reminder per staleness state.)";
|
|
192
|
+
} else if (opts.taskNudge && hb.task_updated_at) {
|
|
122
193
|
const updatedSec = Math.floor(Date.parse(hb.task_updated_at) / 1000);
|
|
123
194
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
124
195
|
if (Number.isFinite(updatedSec) && updatedSec > 0) {
|
|
125
196
|
const ageSec = nowSec - updatedSec;
|
|
126
197
|
if (ageSec > threshold) {
|
|
127
198
|
needsNudge = true;
|
|
128
|
-
|
|
199
|
+
nudgeKind = "task-stale";
|
|
200
|
+
message = `Heads up: your \`task\` field hasn't changed in ${ageSec}s (threshold ${threshold}s). If you've moved on from "${taskValue.slice(0, 60)}", update via \`${bin} agents set-task "<new focus>"\`. Pass an empty string to clear.`;
|
|
129
201
|
}
|
|
130
202
|
}
|
|
131
203
|
}
|
|
132
204
|
|
|
133
|
-
const hashFile = join(coordRoot, ".harnery", `.last-task-nudge-hash.${selfInstanceId}`);
|
|
134
205
|
if (!needsNudge) {
|
|
135
|
-
|
|
136
|
-
if (existsSync(hashFile)) rmSync(hashFile, { force: true });
|
|
137
|
-
} catch {
|
|
138
|
-
/* swallow */
|
|
139
|
-
}
|
|
206
|
+
clearHashFile(hashFile);
|
|
140
207
|
return "";
|
|
141
208
|
}
|
|
142
209
|
|
|
143
|
-
// Dedup on
|
|
144
|
-
|
|
210
|
+
// Dedup on kind + task state so the first-session reminder does not turn into
|
|
211
|
+
// a generic "task unset" reminder on the next prompt before set-task runs.
|
|
212
|
+
const state = `${nudgeKind}|task=${taskValue}|updated=${hb.task_updated_at ?? ""}|threshold=${threshold}`;
|
|
145
213
|
const newHash = sha256Hex16(state);
|
|
146
214
|
const oldHash = safeRead(hashFile);
|
|
147
215
|
if (oldHash && oldHash === newHash) return "";
|
|
@@ -325,6 +393,14 @@ function safeRead(path: string): string {
|
|
|
325
393
|
}
|
|
326
394
|
}
|
|
327
395
|
|
|
396
|
+
function clearHashFile(path: string): void {
|
|
397
|
+
try {
|
|
398
|
+
if (existsSync(path)) rmSync(path, { force: true });
|
|
399
|
+
} catch {
|
|
400
|
+
/* swallow */
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
328
404
|
function sha256Hex16(input: string): string {
|
|
329
405
|
return createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
330
406
|
}
|
|
@@ -11,7 +11,7 @@ import { spawnSync } from "node:child_process";
|
|
|
11
11
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { resolveBinName, resolveHooksSetupHint } from "../../config.ts";
|
|
14
|
-
import { harneryVersion,
|
|
14
|
+
import { harneryVersion, loadAdapterWiring } from "../../hooks/adapter/wiring.ts";
|
|
15
15
|
import { readRemoteMachines } from "../../presence/index.ts";
|
|
16
16
|
|
|
17
17
|
interface HeartbeatRow {
|
|
@@ -34,7 +34,7 @@ export interface RenderOpts {
|
|
|
34
34
|
instanceId: string;
|
|
35
35
|
sessionId: string;
|
|
36
36
|
agentName?: string;
|
|
37
|
-
/**
|
|
37
|
+
/** Adapter label rendered in parens after the self-name (e.g. "Cursor", "Codex").
|
|
38
38
|
* Claude Code omits it. */
|
|
39
39
|
platformLabel?: string;
|
|
40
40
|
}
|
|
@@ -84,11 +84,11 @@ export function renderSessionContext(opts: RenderOpts): string {
|
|
|
84
84
|
messages.push(wiringSummary);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
// 5.
|
|
88
|
-
// project's settings file hasn't been re-wired. Only fires for a
|
|
87
|
+
// 5. Adapter-hook drift: a harnery upgrade changed the hook set, but this
|
|
88
|
+
// project's settings file hasn't been re-wired. Only fires for a adapter the
|
|
89
89
|
// project already opted into (≥1 hook wired), so it never nags a project that
|
|
90
90
|
// simply has a settings file. Remedy is always `<bin> init` (idempotent).
|
|
91
|
-
const drift =
|
|
91
|
+
const drift = loadAdapterWiring(coordRoot);
|
|
92
92
|
if (drift.length > 0) {
|
|
93
93
|
const bin = resolveBinName(coordRoot);
|
|
94
94
|
const ver = harneryVersion();
|
|
@@ -101,7 +101,7 @@ export function renderSessionContext(opts: RenderOpts): string {
|
|
|
101
101
|
return ` - ${d.settingsFile} — ${bits.join("; ")}`;
|
|
102
102
|
});
|
|
103
103
|
messages.push(
|
|
104
|
-
`Harnery hook wiring is out of date${verPart}: an upgrade changed the hook set but the
|
|
104
|
+
`Harnery hook wiring is out of date${verPart}: an upgrade changed the hook set but the adapter ` +
|
|
105
105
|
`settings file hasn't been re-wired, so the new hook(s) won't fire. Run \`${bin} init\` to wire them ` +
|
|
106
106
|
`(idempotent, additive).\n${lines.join("\n")}`,
|
|
107
107
|
);
|
|
@@ -21,12 +21,9 @@ import { emit } from "../events/emit.ts";
|
|
|
21
21
|
|
|
22
22
|
const FRESHNESS_SECS = 600;
|
|
23
23
|
|
|
24
|
-
export type VerdictResult
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
rule: string;
|
|
28
|
-
reason?: string;
|
|
29
|
-
};
|
|
24
|
+
export type { VerdictResult } from "./verdict.ts";
|
|
25
|
+
|
|
26
|
+
import type { VerdictResult } from "./verdict.ts";
|
|
30
27
|
|
|
31
28
|
interface PeerView {
|
|
32
29
|
instance_id: string;
|
|
@@ -329,7 +326,7 @@ function pruneClaimFromPeer(coordRoot: string, instanceId: string, relPath: stri
|
|
|
329
326
|
event_type: "claim.release",
|
|
330
327
|
instance_id: instanceId,
|
|
331
328
|
session_id: (body.session_id as string | undefined) ?? instanceId,
|
|
332
|
-
|
|
329
|
+
adapter: platform === "cursor" ? "cursor" : platform === "codex" ? "codex" : "claude-code",
|
|
333
330
|
source: "agent-coord",
|
|
334
331
|
data: { path: target, reason: "heal" },
|
|
335
332
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { spawnSync } from "node:child_process";
|
|
24
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
25
|
-
import { join } from "node:path";
|
|
24
|
+
import { appendFileSync, existsSync, readdirSync, readFileSync } from "node:fs";
|
|
25
|
+
import { basename, dirname, join } from "node:path";
|
|
26
26
|
import { coordFreshnessSeconds } from "../../config.ts";
|
|
27
27
|
import { instanceHasLivePid } from "../state/pidmap.ts";
|
|
28
28
|
|
|
@@ -198,12 +198,38 @@ function findOverlap(
|
|
|
198
198
|
return null;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Repository-discovery environment that git exports to its own hooks.
|
|
203
|
+
*
|
|
204
|
+
* A hook runs with GIT_DIR (and friends) already pointing at the repository
|
|
205
|
+
* being committed, and every child inherits them. That silently outranks `cwd`,
|
|
206
|
+
* so a probe launched from some other directory still interrogates the hook's
|
|
207
|
+
* repository. Committing inside a submodule therefore asked the submodule about
|
|
208
|
+
* superproject paths and vice versa, and the answers were wrong in whichever
|
|
209
|
+
* direction did not match. Scrubbing these lets git rediscover the repository
|
|
210
|
+
* from `cwd`, which is the whole point of choosing a `cwd` per path.
|
|
211
|
+
*/
|
|
212
|
+
const GIT_DISCOVERY_VARS = [
|
|
213
|
+
"GIT_DIR",
|
|
214
|
+
"GIT_WORK_TREE",
|
|
215
|
+
"GIT_COMMON_DIR",
|
|
216
|
+
"GIT_INDEX_FILE",
|
|
217
|
+
"GIT_OBJECT_DIRECTORY",
|
|
218
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
219
|
+
"GIT_PREFIX",
|
|
220
|
+
"GIT_NAMESPACE",
|
|
221
|
+
] as const;
|
|
222
|
+
|
|
223
|
+
/** Run a read-only git probe in `cwd`, free of the caller's repo environment. */
|
|
224
|
+
function gitProbe(args: string[], cwd: string): { status: number; stdout: string } {
|
|
225
|
+
const env = { ...process.env };
|
|
226
|
+
for (const v of GIT_DISCOVERY_VARS) delete env[v];
|
|
227
|
+
const result = spawnSync("git", args, { cwd, env, encoding: "utf8", timeout: 2000 });
|
|
228
|
+
return { status: result.status ?? 1, stdout: result.stdout ?? "" };
|
|
229
|
+
}
|
|
230
|
+
|
|
201
231
|
function isGitlinkInIndex(coordRoot: string, path: string): boolean {
|
|
202
|
-
const result =
|
|
203
|
-
cwd: coordRoot,
|
|
204
|
-
encoding: "utf8",
|
|
205
|
-
timeout: 2000,
|
|
206
|
-
});
|
|
232
|
+
const result = gitProbe(["ls-files", "--stage", "--", path], coordRoot);
|
|
207
233
|
if (result.status !== 0) return false;
|
|
208
234
|
// ls-files --stage emits "<mode> <sha> <stage>\t<path>"; mode 160000 = gitlink.
|
|
209
235
|
return result.stdout.trim().startsWith("160000 ");
|
|
@@ -216,21 +242,73 @@ function isHolderSelfAttributed(
|
|
|
216
242
|
peers: readonly PeerHeartbeat[],
|
|
217
243
|
): boolean {
|
|
218
244
|
// Gate B (cheaper): live foreign pid-map entry blocks self-attribution.
|
|
219
|
-
if (holderHasLiveForeignPid(coordRoot, holderId))
|
|
245
|
+
if (holderHasLiveForeignPid(coordRoot, holderId)) {
|
|
246
|
+
debugGate(coordRoot, { holder: holderId, gate: "B", reason: "live_pid_anchor" });
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
220
249
|
|
|
221
250
|
// Gate A: every held path is either in the staged set or already clean in HEAD.
|
|
222
251
|
const holder = peers.find((p) => p.instance_id === holderId);
|
|
223
|
-
if (!holder)
|
|
252
|
+
if (!holder) {
|
|
253
|
+
debugGate(coordRoot, { holder: holderId, gate: "A", reason: "holder_not_found" });
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
224
256
|
const files = holder.files_touched ?? [];
|
|
225
|
-
if (files.length === 0)
|
|
257
|
+
if (files.length === 0) {
|
|
258
|
+
debugGate(coordRoot, { holder: holderId, gate: "A", reason: "holder_holds_nothing" });
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
226
261
|
for (const held of files) {
|
|
227
262
|
if (stagedSet.has(held)) continue;
|
|
228
263
|
if (isPathCleanInHead(coordRoot, held)) continue;
|
|
264
|
+
if (isPathIgnoredByGit(coordRoot, held)) continue;
|
|
265
|
+
debugGate(coordRoot, { holder: holderId, gate: "A", reason: "path_unverifiable", path: held });
|
|
229
266
|
return false;
|
|
230
267
|
}
|
|
231
268
|
return true;
|
|
232
269
|
}
|
|
233
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Append one line explaining a refused gate, when HARNERY_COORD_DEBUG is set.
|
|
273
|
+
*
|
|
274
|
+
* Worth carrying permanently: the deciding state lives inside a git hook, whose
|
|
275
|
+
* stderr the caller discards and whose process is gone before anyone can look.
|
|
276
|
+
* Reconstructing a refusal afterwards means guessing, because the holder's claim
|
|
277
|
+
* set has moved on by then. This turns that into evidence.
|
|
278
|
+
*/
|
|
279
|
+
function debugGate(coordRoot: string, detail: Record<string, string>): void {
|
|
280
|
+
if (!process.env.HARNERY_COORD_DEBUG) return;
|
|
281
|
+
try {
|
|
282
|
+
appendFileSync(
|
|
283
|
+
join(coordRoot, ".harnery", "coord-debug.ndjson"),
|
|
284
|
+
`${JSON.stringify({ event: "self_attribution_refused", pid: process.pid, ...detail })}\n`,
|
|
285
|
+
"utf8",
|
|
286
|
+
);
|
|
287
|
+
} catch {
|
|
288
|
+
/* diagnostics never break a verdict */
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Does git ignore this path?
|
|
294
|
+
*
|
|
295
|
+
* Such a claim is safe to look past. Agents write journal files into ignored
|
|
296
|
+
* directories constantly, and each one used to leave a claim that Gate A could
|
|
297
|
+
* never vouch for: not staged, not tracked, therefore "unknown", therefore
|
|
298
|
+
* blocked. That defeated self-attribution for the rest of a session and taught
|
|
299
|
+
* agents to reach for the bypass. An ignored path cannot enter anyone's commit,
|
|
300
|
+
* so it cannot be work this commit might clobber. A merely untracked path is a
|
|
301
|
+
* different matter and still counts against the holder, since anybody could add
|
|
302
|
+
* it.
|
|
303
|
+
*/
|
|
304
|
+
function isPathIgnoredByGit(coordRoot: string, relPath: string): boolean {
|
|
305
|
+
const abs = join(coordRoot, relPath);
|
|
306
|
+
const cwd = dirname(abs);
|
|
307
|
+
if (!existsSync(cwd)) return false;
|
|
308
|
+
// check-ignore exits 0 when the path is ignored, 1 when it is not.
|
|
309
|
+
return gitProbe(["check-ignore", "--quiet", "--", basename(abs)], cwd).status === 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
234
312
|
function holderHasLiveForeignPid(coordRoot: string, holderId: string): boolean {
|
|
235
313
|
return instanceHasLivePid(coordRoot, holderId);
|
|
236
314
|
}
|
|
@@ -238,18 +316,21 @@ function holderHasLiveForeignPid(coordRoot: string, holderId: string): boolean {
|
|
|
238
316
|
function isPathCleanInHead(coordRoot: string, relPath: string): boolean {
|
|
239
317
|
// Path is tracked + diff-clean against HEAD = "already committed, holder
|
|
240
318
|
// hasn't released the claim yet". Counts as self-attributable.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
319
|
+
//
|
|
320
|
+
// Ask the repository that actually tracks the path, by running git from the
|
|
321
|
+
// file's own directory instead of the coord root. Claims are recorded
|
|
322
|
+
// monorepo-relative, so a submodule file arrives here as `sub/src/x.ts`, and
|
|
323
|
+
// the superproject does not track submodule contents: asking it always
|
|
324
|
+
// answered "not tracked". Gate A therefore failed for every submodule path,
|
|
325
|
+
// self-attribution never suppressed a submodule commit, and the operator paid
|
|
326
|
+
// a HARNERY_AGENT_COORD_BYPASS=1 per commit. Running from the file's directory
|
|
327
|
+
// lands in the submodule's own repo and handles any nesting depth.
|
|
328
|
+
const abs = join(coordRoot, relPath);
|
|
329
|
+
const cwd = dirname(abs);
|
|
330
|
+
const base = basename(abs);
|
|
331
|
+
if (!existsSync(cwd)) return false;
|
|
332
|
+
if (gitProbe(["ls-files", "--error-unmatch", "--", base], cwd).status !== 0) return false;
|
|
333
|
+
return gitProbe(["diff", "--quiet", "HEAD", "--", base], cwd).status === 0;
|
|
253
334
|
}
|
|
254
335
|
|
|
255
336
|
function readActivePeers(coordRoot: string): PeerHeartbeat[] {
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Stop-hook verdict: rule 1/3, 2/3, 3/3 enforcement evaluated
|
|
3
3
|
* from the canonical event stream alone (no transcript scan).
|
|
4
4
|
*
|
|
5
|
+
* Codex is observe-only: its Stop continuation can replace the user-facing
|
|
6
|
+
* answer in clients that retain only the final continuation response.
|
|
7
|
+
*
|
|
8
|
+
* Enforced adapters:
|
|
5
9
|
* Rule 1/3: `state.status_checked` event with matching turn boundary exists.
|
|
6
10
|
* Rule 2/3: latest `turn.stop` event has `status_box_present: true` (or the
|
|
7
11
|
* stop currently firing carries that field via the in-flight event
|
|
@@ -17,12 +21,9 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
17
21
|
import { join } from "node:path";
|
|
18
22
|
import { resolveBinName } from "../../config.ts";
|
|
19
23
|
|
|
20
|
-
export type VerdictResult
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
rule: string;
|
|
24
|
-
reason?: string;
|
|
25
|
-
};
|
|
24
|
+
export type { VerdictResult } from "./verdict.ts";
|
|
25
|
+
|
|
26
|
+
import type { VerdictResult } from "./verdict.ts";
|
|
26
27
|
|
|
27
28
|
interface CanonicalEvent {
|
|
28
29
|
event_id: string;
|
|
@@ -31,7 +32,7 @@ interface CanonicalEvent {
|
|
|
31
32
|
instance_id: string;
|
|
32
33
|
session_id: string;
|
|
33
34
|
turn_id?: string;
|
|
34
|
-
|
|
35
|
+
adapter: string;
|
|
35
36
|
source: string;
|
|
36
37
|
data: Record<string, unknown>;
|
|
37
38
|
}
|
|
@@ -40,9 +41,9 @@ export interface StopHookRequest {
|
|
|
40
41
|
rule: "stop-hook";
|
|
41
42
|
instance_id: string;
|
|
42
43
|
session_id?: string;
|
|
43
|
-
/** Firing
|
|
44
|
+
/** Firing adapter. Selects the end-of-turn ack signal (see `ackSignalFor`).
|
|
44
45
|
* Undefined → Claude Code semantics (transcript-scanned status box). */
|
|
45
|
-
|
|
46
|
+
adapter?: string;
|
|
46
47
|
/** Wall-clock cutoff for the current turn; events strictly after this are not yet relevant. */
|
|
47
48
|
now_ms?: number;
|
|
48
49
|
/** Override the turn-window discovery (used by tests). */
|
|
@@ -51,7 +52,7 @@ export interface StopHookRequest {
|
|
|
51
52
|
bypass?: boolean;
|
|
52
53
|
/** Headless child spawned by `harn workflow` (HARNERY_WORKFLOW_CHILD=1).
|
|
53
54
|
* The end-of-turn ritual exists to surface status to a HUMAN reader; a
|
|
54
|
-
* workflow child reports to the engine's
|
|
55
|
+
* workflow child reports to the engine's transcript instead, so the ritual is
|
|
55
56
|
* meaningless there — worse, blocking burns the child's turn budget on
|
|
56
57
|
* re-prompts (observed as error_max_turns in the Phase 1 spike). Exempting
|
|
57
58
|
* here, rather than disabling the child's hooks wholesale, keeps heartbeat +
|
|
@@ -61,7 +62,7 @@ export interface StopHookRequest {
|
|
|
61
62
|
|
|
62
63
|
/**
|
|
63
64
|
* The end-of-turn "I surfaced my status" signal, detected differently per
|
|
64
|
-
*
|
|
65
|
+
* adapter because the ritual's *goal* (status visible to the human) is reached
|
|
65
66
|
* by different means:
|
|
66
67
|
*
|
|
67
68
|
* - **Claude Code / Codex** collapse tool calls in the UI, so the human-visible
|
|
@@ -78,12 +79,12 @@ export interface StopHookRequest {
|
|
|
78
79
|
* This is why the fix is not "relax 2/3 because we can't see it": it's "2/3
|
|
79
80
|
* and 1/3 are two detections of the same thing, and Cursor's inline UI makes
|
|
80
81
|
* 1/3 the right one." The enforcement *channel* (exit-2+stderr vs Cursor's
|
|
81
|
-
* `followup_message`) is handled separately in hooks/
|
|
82
|
+
* `followup_message`) is handled separately in hooks/adapter/output.ts.
|
|
82
83
|
*/
|
|
83
84
|
type AckSignal = "status_box_present" | "status_checked";
|
|
84
85
|
|
|
85
|
-
function ackSignalFor(
|
|
86
|
-
return
|
|
86
|
+
function ackSignalFor(adapter?: string): AckSignal {
|
|
87
|
+
return adapter === "cursor" ? "status_checked" : "status_box_present";
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
const RECENT_EVENT_WINDOW_LINES = 5_000;
|
|
@@ -112,6 +113,21 @@ export function evaluateStopHook(coordRoot: string, req: StopHookRequest): Verdi
|
|
|
112
113
|
};
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
// A blocked Codex Stop does not reject the turn. Codex creates a continuation
|
|
117
|
+
// prompt and asks the model to answer again. Some clients retain only that
|
|
118
|
+
// final continuation response, so a coordination reminder can replace a
|
|
119
|
+
// correct user-facing answer with status output. The Stop path has already
|
|
120
|
+
// emitted turn.stop and projected the coordination state before this verdict
|
|
121
|
+
// runs. Keep that telemetry, but never continue a Codex turn for this ritual.
|
|
122
|
+
if (req.adapter === "codex") {
|
|
123
|
+
return {
|
|
124
|
+
allow: true,
|
|
125
|
+
exit_code: 0,
|
|
126
|
+
rule: "stop-hook.codex_observe_only",
|
|
127
|
+
reason: "Codex Stop continuations must not replace the user-facing answer",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
115
131
|
let events: CanonicalEvent[];
|
|
116
132
|
try {
|
|
117
133
|
events = readRecentEvents(coordRoot, RECENT_EVENT_WINDOW_LINES);
|
|
@@ -160,16 +176,16 @@ export function evaluateStopHook(coordRoot: string, req: StopHookRequest): Verdi
|
|
|
160
176
|
const latestTurnStop = [...inTurn].reverse().find((e) => e.event_type === "turn.stop");
|
|
161
177
|
const boxPresent = latestTurnStop ? Boolean(latestTurnStop.data.status_box_present) : false;
|
|
162
178
|
|
|
163
|
-
//
|
|
179
|
+
// Adapter-aware end-of-turn ack signal (see `ackSignalFor`). On Cursor the
|
|
164
180
|
// ack is `status_checked` (running `harn agents status` shows the box inline);
|
|
165
181
|
// on Claude Code / Codex it's the transcript-scanned `status_box_present`.
|
|
166
182
|
// The matching block helper carries the right "how to fix" message.
|
|
167
|
-
const ackSignal = ackSignalFor(req.
|
|
183
|
+
const ackSignal = ackSignalFor(req.adapter);
|
|
168
184
|
const ackPresent = ackSignal === "status_checked" ? statusChecked : boxPresent;
|
|
169
185
|
const ackBlock = ackSignal === "status_checked" ? rule13Block : rule23Block;
|
|
170
186
|
|
|
171
187
|
// Pure-prose-turn exemption: only the ack signal applies. Parity
|
|
172
|
-
// across
|
|
188
|
+
// across adapters: CC requires the box; Cursor requires status_checked.
|
|
173
189
|
if (!toolPreUseInTurn) {
|
|
174
190
|
if (!ackPresent) {
|
|
175
191
|
return ackBlock();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape every coordination rule returns.
|
|
3
|
+
*
|
|
4
|
+
* Declared here once. The claim-conflict and stop-hook rules each carried a
|
|
5
|
+
* byte-identical private copy, which is how two rules drift into disagreeing
|
|
6
|
+
* about what a verdict is.
|
|
7
|
+
*/
|
|
8
|
+
export type VerdictResult = {
|
|
9
|
+
allow: boolean;
|
|
10
|
+
exit_code: 0 | 2;
|
|
11
|
+
rule: string;
|
|
12
|
+
reason?: string;
|
|
13
|
+
};
|
|
@@ -12,7 +12,7 @@ import { randomBytes } from "node:crypto";
|
|
|
12
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
13
13
|
import { dirname, resolve } from "node:path";
|
|
14
14
|
// Kept dependency-light: vendored verbatim into a downstream consumer, so no coordEnv import.
|
|
15
|
-
import {
|
|
15
|
+
import { normalizeAdapter, resolveEmitRoot } from "./canonical-emit.ts";
|
|
16
16
|
import { emit } from "./events/emit.ts";
|
|
17
17
|
|
|
18
18
|
/** Event types accepted by `writeSessionEvent`. Only the command stream +
|
|
@@ -98,7 +98,7 @@ const CANONICAL_TYPE: Partial<Record<SessionEventType, string>> = {
|
|
|
98
98
|
|
|
99
99
|
interface HeartbeatEnrichment {
|
|
100
100
|
session_id: string;
|
|
101
|
-
|
|
101
|
+
adapter: "claude-code" | "cursor" | "codex";
|
|
102
102
|
at: number;
|
|
103
103
|
}
|
|
104
104
|
/** Cache heartbeat-derived envelope fields per instance_id so a burst of
|
|
@@ -121,7 +121,7 @@ function enrichFromHeartbeat(coordRoot: string, instanceId: string): HeartbeatEn
|
|
|
121
121
|
};
|
|
122
122
|
const enrichment: HeartbeatEnrichment = {
|
|
123
123
|
session_id: hb.session_id || instanceId,
|
|
124
|
-
|
|
124
|
+
adapter: normalizeAdapter(hb.platform),
|
|
125
125
|
at: now,
|
|
126
126
|
};
|
|
127
127
|
enrichCache.set(instanceId, enrichment);
|
|
@@ -173,7 +173,7 @@ function emitCanonicalCommand(type: SessionEventType, fields: Record<string, unk
|
|
|
173
173
|
event_type: eventType,
|
|
174
174
|
instance_id: instanceId,
|
|
175
175
|
session_id: enrich.session_id,
|
|
176
|
-
|
|
176
|
+
adapter: enrich.adapter,
|
|
177
177
|
data: canonicalData(type, fields),
|
|
178
178
|
});
|
|
179
179
|
} catch {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* This module owns: lifecycle (advance, close, archive, unarchive, delete)
|
|
9
9
|
* and steward reassignment. Council CREATION + contribution writes stay
|
|
10
|
-
* separate (operator-side,
|
|
10
|
+
* separate (operator-side, adapter-agnostic).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {
|