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
|
@@ -15,8 +15,8 @@ import { join } from "node:path";
|
|
|
15
15
|
import { coordFreshnessSeconds } from "../../config.ts";
|
|
16
16
|
import { emit } from "../events/emit.ts";
|
|
17
17
|
|
|
18
|
-
/** platform →
|
|
19
|
-
function
|
|
18
|
+
/** platform → adapter, for the swept-event envelope (mirrors heartbeat-writer's adapterOf). */
|
|
19
|
+
function adapterFromPlatform(platform: unknown): "claude-code" | "cursor" | "codex" {
|
|
20
20
|
if (platform === "cursor") return "cursor";
|
|
21
21
|
if (platform === "codex") return "codex";
|
|
22
22
|
return "claude-code";
|
|
@@ -26,7 +26,7 @@ function harnessFromPlatform(platform: unknown): "claude-code" | "cursor" | "cod
|
|
|
26
26
|
function emitSwept(
|
|
27
27
|
coordRoot: string,
|
|
28
28
|
instanceId: string,
|
|
29
|
-
|
|
29
|
+
adapter: "claude-code" | "cursor" | "codex",
|
|
30
30
|
sessionId: string,
|
|
31
31
|
reason: "stale" | "unparseable" | "missing_ts",
|
|
32
32
|
ageSecs?: number,
|
|
@@ -36,7 +36,7 @@ function emitSwept(
|
|
|
36
36
|
event_type: "health.heartbeat_swept",
|
|
37
37
|
instance_id: instanceId,
|
|
38
38
|
session_id: sessionId,
|
|
39
|
-
|
|
39
|
+
adapter,
|
|
40
40
|
source: "agent-coord",
|
|
41
41
|
data: { reason, ...(ageSecs !== undefined ? { age_secs: ageSecs } : {}) },
|
|
42
42
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -116,7 +116,7 @@ export function staleSweep(coordRoot: string): {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
const instanceId = parsed.instance_id ?? idFromFile;
|
|
119
|
-
const
|
|
119
|
+
const adapter = adapterFromPlatform(parsed.platform);
|
|
120
120
|
const sessionId = parsed.session_id ?? instanceId;
|
|
121
121
|
const ts = parsed.last_heartbeat
|
|
122
122
|
? Math.floor(Date.parse(parsed.last_heartbeat) / 1000)
|
|
@@ -127,7 +127,7 @@ export function staleSweep(coordRoot: string): {
|
|
|
127
127
|
if (mtimeSecs(path) < cutoff) {
|
|
128
128
|
unlinkSync(path);
|
|
129
129
|
heartbeatsRemoved.push(f);
|
|
130
|
-
emitSwept(coordRoot, instanceId,
|
|
130
|
+
emitSwept(coordRoot, instanceId, adapter, sessionId, "missing_ts");
|
|
131
131
|
} else if (parsed.instance_id) {
|
|
132
132
|
liveInstanceIds.add(parsed.instance_id);
|
|
133
133
|
}
|
|
@@ -138,7 +138,7 @@ export function staleSweep(coordRoot: string): {
|
|
|
138
138
|
// Legitimate stale prune (valid timestamp, past the freshness cutoff).
|
|
139
139
|
unlinkSync(path);
|
|
140
140
|
heartbeatsRemoved.push(f);
|
|
141
|
-
emitSwept(coordRoot, instanceId,
|
|
141
|
+
emitSwept(coordRoot, instanceId, adapter, sessionId, "stale", nowSec - ts);
|
|
142
142
|
continue;
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository-local working artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Artifacts are file trees that should survive an agent session but should not
|
|
5
|
+
* become project records: screenshots, exports, audit dumps, rollback inputs,
|
|
6
|
+
* and similar material. Each direct child of `.harnery/artifacts/` is one
|
|
7
|
+
* managed unit with a small manifest. Cleanup fails closed: only a valid,
|
|
8
|
+
* expired, inactive, untracked managed unit is deletable.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
import type { Stats } from "node:fs";
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
lstatSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
readdirSync,
|
|
19
|
+
readFileSync,
|
|
20
|
+
renameSync,
|
|
21
|
+
rmSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
} from "node:fs";
|
|
24
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
25
|
+
import { artifactDefaultRetentionDays } from "../config.ts";
|
|
26
|
+
|
|
27
|
+
export const ARTIFACTS_DIR = join(".harnery", "artifacts");
|
|
28
|
+
export const ARTIFACT_MANIFEST = ".harnery-artifact.json";
|
|
29
|
+
export const ARTIFACT_SCHEMA_VERSION = 1 as const;
|
|
30
|
+
|
|
31
|
+
export interface ArtifactActor {
|
|
32
|
+
instance_id: string;
|
|
33
|
+
session_id?: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ArtifactManifestV1 {
|
|
38
|
+
schema_version: typeof ARTIFACT_SCHEMA_VERSION;
|
|
39
|
+
artifact_id: string;
|
|
40
|
+
slug: string;
|
|
41
|
+
purpose: string;
|
|
42
|
+
created_at: string;
|
|
43
|
+
created_by?: ArtifactActor;
|
|
44
|
+
retention: {
|
|
45
|
+
expires_at: string;
|
|
46
|
+
renewed_at?: string;
|
|
47
|
+
reason?: string;
|
|
48
|
+
};
|
|
49
|
+
released_at?: string;
|
|
50
|
+
released_by?: ArtifactActor;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type ArtifactClassification =
|
|
54
|
+
| "managed-active"
|
|
55
|
+
| "managed-current"
|
|
56
|
+
| "managed-expired"
|
|
57
|
+
| "managed-tracked"
|
|
58
|
+
| "invalid-manifest"
|
|
59
|
+
| "unmanaged"
|
|
60
|
+
| "symlink"
|
|
61
|
+
| "unknown";
|
|
62
|
+
|
|
63
|
+
export interface ArtifactInventoryEntry {
|
|
64
|
+
name: string;
|
|
65
|
+
path: string;
|
|
66
|
+
relative_path: string;
|
|
67
|
+
classification: ArtifactClassification;
|
|
68
|
+
reason: string;
|
|
69
|
+
action: "keep" | "would-delete" | "deleted";
|
|
70
|
+
bytes: number | null;
|
|
71
|
+
artifact_id: string | null;
|
|
72
|
+
slug: string | null;
|
|
73
|
+
created_at: string | null;
|
|
74
|
+
expires_at: string | null;
|
|
75
|
+
owner_instance_id: string | null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ArtifactCreateInput {
|
|
79
|
+
slug: string;
|
|
80
|
+
purpose: string;
|
|
81
|
+
retentionDays: number;
|
|
82
|
+
actor?: ArtifactActor;
|
|
83
|
+
now?: Date;
|
|
84
|
+
id?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ArtifactMutationInput {
|
|
88
|
+
actor?: ArtifactActor;
|
|
89
|
+
now?: Date;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface ParsedManifest {
|
|
93
|
+
ok: true;
|
|
94
|
+
manifest: ArtifactManifestV1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface ManifestError {
|
|
98
|
+
ok: false;
|
|
99
|
+
reason: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function artifactsRoot(repoRoot: string): string {
|
|
103
|
+
return join(resolve(repoRoot), ARTIFACTS_DIR);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Public config-aware default for embedding hosts that create artifact units. */
|
|
107
|
+
export function configuredArtifactRetentionDays(repoRoot: string): number {
|
|
108
|
+
return artifactDefaultRetentionDays(repoRoot);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createArtifact(
|
|
112
|
+
repoRoot: string,
|
|
113
|
+
input: ArtifactCreateInput,
|
|
114
|
+
): { path: string; manifest: ArtifactManifestV1 } {
|
|
115
|
+
const now = input.now ?? new Date();
|
|
116
|
+
assertValidDate(now, "now");
|
|
117
|
+
const slug = normalizeSlug(input.slug);
|
|
118
|
+
if (!slug) throw new Error("slug must contain at least one ASCII letter or digit");
|
|
119
|
+
const purpose = input.purpose.trim();
|
|
120
|
+
if (!purpose) throw new Error("purpose must not be empty");
|
|
121
|
+
const retentionDays = positiveDays(input.retentionDays);
|
|
122
|
+
const artifactId = input.id ?? randomUUID();
|
|
123
|
+
if (!isSafeId(artifactId)) {
|
|
124
|
+
throw new Error("artifact id must use ASCII letters, digits, hyphens, or underscores");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const root = artifactsRoot(repoRoot);
|
|
128
|
+
mkdirSync(root, { recursive: true });
|
|
129
|
+
const date = now.toISOString().slice(0, 10);
|
|
130
|
+
const path = join(root, `${date}_${slug}_${artifactId.slice(0, 8)}`);
|
|
131
|
+
mkdirSync(path);
|
|
132
|
+
|
|
133
|
+
const manifest: ArtifactManifestV1 = {
|
|
134
|
+
schema_version: ARTIFACT_SCHEMA_VERSION,
|
|
135
|
+
artifact_id: artifactId,
|
|
136
|
+
slug,
|
|
137
|
+
purpose,
|
|
138
|
+
created_at: now.toISOString(),
|
|
139
|
+
created_by: input.actor,
|
|
140
|
+
retention: {
|
|
141
|
+
expires_at: addDays(now, retentionDays).toISOString(),
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
atomicWriteManifest(path, manifest);
|
|
145
|
+
return { path, manifest };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function inventoryArtifacts(
|
|
149
|
+
repoRoot: string,
|
|
150
|
+
opts: { now?: Date; freshnessSeconds?: number } = {},
|
|
151
|
+
): ArtifactInventoryEntry[] {
|
|
152
|
+
const root = artifactsRoot(repoRoot);
|
|
153
|
+
if (!existsSync(root)) return [];
|
|
154
|
+
const now = opts.now ?? new Date();
|
|
155
|
+
assertValidDate(now, "now");
|
|
156
|
+
const freshnessSeconds = opts.freshnessSeconds ?? 600;
|
|
157
|
+
const rows: ArtifactInventoryEntry[] = [];
|
|
158
|
+
let names: string[];
|
|
159
|
+
try {
|
|
160
|
+
names = readdirSync(root).sort();
|
|
161
|
+
} catch (error) {
|
|
162
|
+
return [
|
|
163
|
+
rowFor(
|
|
164
|
+
root,
|
|
165
|
+
basename(root),
|
|
166
|
+
repoRoot,
|
|
167
|
+
"unknown",
|
|
168
|
+
errorMessage("cannot read artifact root", error),
|
|
169
|
+
),
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
for (const name of names) {
|
|
173
|
+
rows.push(classifyArtifactPath(repoRoot, join(root, name), now, freshnessSeconds));
|
|
174
|
+
}
|
|
175
|
+
return rows;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function showArtifact(
|
|
179
|
+
repoRoot: string,
|
|
180
|
+
ref: string,
|
|
181
|
+
opts: { now?: Date; freshnessSeconds?: number } = {},
|
|
182
|
+
): { entry: ArtifactInventoryEntry; manifest: ArtifactManifestV1 } {
|
|
183
|
+
const path = resolveArtifactRef(repoRoot, ref);
|
|
184
|
+
const entry = classifyArtifactPath(
|
|
185
|
+
repoRoot,
|
|
186
|
+
path,
|
|
187
|
+
opts.now ?? new Date(),
|
|
188
|
+
opts.freshnessSeconds ?? 600,
|
|
189
|
+
);
|
|
190
|
+
const parsed = readManifest(path);
|
|
191
|
+
if (!parsed.ok) throw new Error(parsed.reason);
|
|
192
|
+
return { entry, manifest: parsed.manifest };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function renewArtifact(
|
|
196
|
+
repoRoot: string,
|
|
197
|
+
ref: string,
|
|
198
|
+
days: number,
|
|
199
|
+
reason: string,
|
|
200
|
+
input: ArtifactMutationInput = {},
|
|
201
|
+
): ArtifactManifestV1 {
|
|
202
|
+
const now = input.now ?? new Date();
|
|
203
|
+
assertValidDate(now, "now");
|
|
204
|
+
const retentionDays = positiveDays(days);
|
|
205
|
+
const why = reason.trim();
|
|
206
|
+
if (!why) throw new Error("renewal reason must not be empty");
|
|
207
|
+
const path = resolveArtifactRef(repoRoot, ref);
|
|
208
|
+
const parsed = readManifest(path);
|
|
209
|
+
if (!parsed.ok) throw new Error(parsed.reason);
|
|
210
|
+
const manifest: ArtifactManifestV1 = {
|
|
211
|
+
...parsed.manifest,
|
|
212
|
+
retention: {
|
|
213
|
+
expires_at: addDays(now, retentionDays).toISOString(),
|
|
214
|
+
renewed_at: now.toISOString(),
|
|
215
|
+
reason: why,
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
atomicWriteManifest(path, manifest);
|
|
219
|
+
return manifest;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function releaseArtifact(
|
|
223
|
+
repoRoot: string,
|
|
224
|
+
ref: string,
|
|
225
|
+
input: ArtifactMutationInput = {},
|
|
226
|
+
): ArtifactManifestV1 {
|
|
227
|
+
const now = input.now ?? new Date();
|
|
228
|
+
assertValidDate(now, "now");
|
|
229
|
+
const path = resolveArtifactRef(repoRoot, ref);
|
|
230
|
+
const parsed = readManifest(path);
|
|
231
|
+
if (!parsed.ok) throw new Error(parsed.reason);
|
|
232
|
+
const manifest: ArtifactManifestV1 = {
|
|
233
|
+
...parsed.manifest,
|
|
234
|
+
released_at: now.toISOString(),
|
|
235
|
+
released_by: input.actor,
|
|
236
|
+
};
|
|
237
|
+
atomicWriteManifest(path, manifest);
|
|
238
|
+
return manifest;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function cleanArtifacts(
|
|
242
|
+
repoRoot: string,
|
|
243
|
+
opts: { yes?: boolean; now?: Date; freshnessSeconds?: number } = {},
|
|
244
|
+
): ArtifactInventoryEntry[] {
|
|
245
|
+
const now = opts.now ?? new Date();
|
|
246
|
+
const freshnessSeconds = opts.freshnessSeconds ?? 600;
|
|
247
|
+
const rows = inventoryArtifacts(repoRoot, { now, freshnessSeconds });
|
|
248
|
+
if (!opts.yes) return rows;
|
|
249
|
+
|
|
250
|
+
return rows.map((entry) => {
|
|
251
|
+
if (entry.classification !== "managed-expired") return entry;
|
|
252
|
+
// Reclassify immediately before removal. A renewal, release-state change,
|
|
253
|
+
// heartbeat, symlink swap, or tracked file added since inventory must win.
|
|
254
|
+
const current = classifyArtifactPath(repoRoot, entry.path, now, freshnessSeconds);
|
|
255
|
+
if (current.classification !== "managed-expired") return current;
|
|
256
|
+
try {
|
|
257
|
+
const top = lstatSync(current.path);
|
|
258
|
+
if (!top.isDirectory() || top.isSymbolicLink()) {
|
|
259
|
+
return {
|
|
260
|
+
...current,
|
|
261
|
+
classification: "unknown",
|
|
262
|
+
reason: "entry changed before deletion",
|
|
263
|
+
action: "keep",
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
rmSync(current.path, { recursive: true, force: false });
|
|
267
|
+
return { ...current, action: "deleted" };
|
|
268
|
+
} catch (error) {
|
|
269
|
+
return {
|
|
270
|
+
...current,
|
|
271
|
+
classification: "unknown",
|
|
272
|
+
reason: errorMessage("entry changed or could not be deleted", error),
|
|
273
|
+
action: "keep",
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function resolveArtifactRef(repoRoot: string, ref: string): string {
|
|
280
|
+
const root = artifactsRoot(repoRoot);
|
|
281
|
+
const candidate = isAbsolute(ref) ? resolve(ref) : resolve(root, ref);
|
|
282
|
+
if (candidate === root || !candidate.startsWith(`${root}${sep}`) || dirname(candidate) !== root) {
|
|
283
|
+
// A bare artifact id is the only non-path lookup. It must match exactly,
|
|
284
|
+
// never by prefix, so two UUIDs cannot make a command ambiguous.
|
|
285
|
+
const matches = inventoryArtifacts(repoRoot)
|
|
286
|
+
.filter((entry) => entry.artifact_id === ref)
|
|
287
|
+
.map((entry) => entry.path);
|
|
288
|
+
if (matches.length === 1) return matches[0]!;
|
|
289
|
+
throw new Error(`artifact "${ref}" was not found`);
|
|
290
|
+
}
|
|
291
|
+
if (!existsSync(candidate)) {
|
|
292
|
+
const matches = inventoryArtifacts(repoRoot)
|
|
293
|
+
.filter((entry) => entry.artifact_id === ref)
|
|
294
|
+
.map((entry) => entry.path);
|
|
295
|
+
if (matches.length === 1) return matches[0]!;
|
|
296
|
+
throw new Error(`artifact "${ref}" was not found`);
|
|
297
|
+
}
|
|
298
|
+
return candidate;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function classifyArtifactPath(
|
|
302
|
+
repoRoot: string,
|
|
303
|
+
path: string,
|
|
304
|
+
now: Date,
|
|
305
|
+
freshnessSeconds: number,
|
|
306
|
+
): ArtifactInventoryEntry {
|
|
307
|
+
const name = basename(path);
|
|
308
|
+
let st: Stats;
|
|
309
|
+
try {
|
|
310
|
+
st = lstatSync(path);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
return rowFor(path, name, repoRoot, "unknown", errorMessage("cannot inspect entry", error));
|
|
313
|
+
}
|
|
314
|
+
if (st.isSymbolicLink()) {
|
|
315
|
+
return rowFor(path, name, repoRoot, "symlink", "symlinks are never traversed or deleted");
|
|
316
|
+
}
|
|
317
|
+
if (!st.isDirectory()) {
|
|
318
|
+
return rowFor(path, name, repoRoot, "unmanaged", "workspace entries must be directories");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const parsed = readManifest(path);
|
|
322
|
+
if (!parsed.ok) {
|
|
323
|
+
const classification = existsSync(join(path, ARTIFACT_MANIFEST))
|
|
324
|
+
? "invalid-manifest"
|
|
325
|
+
: "unmanaged";
|
|
326
|
+
return rowFor(path, name, repoRoot, classification, parsed.reason);
|
|
327
|
+
}
|
|
328
|
+
const manifest = parsed.manifest;
|
|
329
|
+
const bytes = safeTreeSize(path);
|
|
330
|
+
const base = rowFor(path, name, repoRoot, "managed-current", "retention has not expired", bytes);
|
|
331
|
+
Object.assign(base, {
|
|
332
|
+
artifact_id: manifest.artifact_id,
|
|
333
|
+
slug: manifest.slug,
|
|
334
|
+
created_at: manifest.created_at,
|
|
335
|
+
expires_at: manifest.retention.expires_at,
|
|
336
|
+
owner_instance_id: manifest.created_by?.instance_id ?? null,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
if (base.bytes === null) {
|
|
340
|
+
return {
|
|
341
|
+
...base,
|
|
342
|
+
classification: "unknown",
|
|
343
|
+
reason: "one or more artifact paths are unreadable",
|
|
344
|
+
action: "keep",
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (containsTrackedPath(repoRoot, path)) {
|
|
348
|
+
return {
|
|
349
|
+
...base,
|
|
350
|
+
classification: "managed-tracked",
|
|
351
|
+
reason: "Git tracks one or more paths inside this artifact",
|
|
352
|
+
action: "keep",
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
if (!manifest.released_at && manifest.created_by?.instance_id) {
|
|
356
|
+
const live = ownerLiveness(repoRoot, manifest.created_by.instance_id, now, freshnessSeconds);
|
|
357
|
+
if (live === "live") {
|
|
358
|
+
return {
|
|
359
|
+
...base,
|
|
360
|
+
classification: "managed-active",
|
|
361
|
+
reason: `owner ${manifest.created_by.instance_id} has a fresh heartbeat`,
|
|
362
|
+
action: "keep",
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
if (live === "unknown") {
|
|
366
|
+
return {
|
|
367
|
+
...base,
|
|
368
|
+
classification: "unknown",
|
|
369
|
+
reason: `owner ${manifest.created_by.instance_id} heartbeat is unreadable`,
|
|
370
|
+
action: "keep",
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (Date.parse(manifest.retention.expires_at) > now.getTime()) return base;
|
|
375
|
+
return {
|
|
376
|
+
...base,
|
|
377
|
+
classification: "managed-expired",
|
|
378
|
+
reason: `retention expired at ${manifest.retention.expires_at}`,
|
|
379
|
+
action: "would-delete",
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function readManifest(path: string): ParsedManifest | ManifestError {
|
|
384
|
+
const manifestPath = join(path, ARTIFACT_MANIFEST);
|
|
385
|
+
if (!existsSync(manifestPath)) {
|
|
386
|
+
return { ok: false, reason: `missing ${ARTIFACT_MANIFEST}` };
|
|
387
|
+
}
|
|
388
|
+
let value: unknown;
|
|
389
|
+
try {
|
|
390
|
+
value = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
391
|
+
} catch (error) {
|
|
392
|
+
return { ok: false, reason: errorMessage("manifest is unreadable", error) };
|
|
393
|
+
}
|
|
394
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
395
|
+
return { ok: false, reason: "manifest must be a JSON object" };
|
|
396
|
+
}
|
|
397
|
+
const m = value as Partial<ArtifactManifestV1>;
|
|
398
|
+
if (m.schema_version !== ARTIFACT_SCHEMA_VERSION) {
|
|
399
|
+
return { ok: false, reason: `unsupported schema_version ${String(m.schema_version)}` };
|
|
400
|
+
}
|
|
401
|
+
if (!isSafeId(m.artifact_id)) return { ok: false, reason: "invalid artifact_id" };
|
|
402
|
+
if (typeof m.slug !== "string" || !m.slug || normalizeSlug(m.slug) !== m.slug) {
|
|
403
|
+
return { ok: false, reason: "invalid slug" };
|
|
404
|
+
}
|
|
405
|
+
if (typeof m.purpose !== "string" || !m.purpose.trim()) {
|
|
406
|
+
return { ok: false, reason: "invalid purpose" };
|
|
407
|
+
}
|
|
408
|
+
if (!validIso(m.created_at)) return { ok: false, reason: "invalid created_at" };
|
|
409
|
+
if (!m.retention || typeof m.retention !== "object" || !validIso(m.retention.expires_at)) {
|
|
410
|
+
return { ok: false, reason: "invalid retention.expires_at" };
|
|
411
|
+
}
|
|
412
|
+
if (m.released_at !== undefined && !validIso(m.released_at)) {
|
|
413
|
+
return { ok: false, reason: "invalid released_at" };
|
|
414
|
+
}
|
|
415
|
+
if (m.created_by !== undefined && !validActor(m.created_by)) {
|
|
416
|
+
return { ok: false, reason: "invalid created_by" };
|
|
417
|
+
}
|
|
418
|
+
if (m.released_by !== undefined && !validActor(m.released_by)) {
|
|
419
|
+
return { ok: false, reason: "invalid released_by" };
|
|
420
|
+
}
|
|
421
|
+
return { ok: true, manifest: m as ArtifactManifestV1 };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function atomicWriteManifest(path: string, manifest: ArtifactManifestV1): void {
|
|
425
|
+
const target = join(path, ARTIFACT_MANIFEST);
|
|
426
|
+
const tmp = `${target}.tmp.${process.pid}.${randomUUID().slice(0, 8)}`;
|
|
427
|
+
writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`, {
|
|
428
|
+
encoding: "utf8",
|
|
429
|
+
mode: 0o600,
|
|
430
|
+
});
|
|
431
|
+
renameSync(tmp, target);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function ownerLiveness(
|
|
435
|
+
repoRoot: string,
|
|
436
|
+
instanceId: string,
|
|
437
|
+
now: Date,
|
|
438
|
+
freshnessSeconds: number,
|
|
439
|
+
): "live" | "stale" | "unknown" {
|
|
440
|
+
const path = join(repoRoot, ".harnery", "active", `${instanceId}.json`);
|
|
441
|
+
if (!existsSync(path)) return "stale";
|
|
442
|
+
try {
|
|
443
|
+
const hb = JSON.parse(readFileSync(path, "utf8")) as { last_heartbeat?: unknown };
|
|
444
|
+
if (typeof hb.last_heartbeat !== "string") return "unknown";
|
|
445
|
+
const ts = Date.parse(hb.last_heartbeat);
|
|
446
|
+
if (!Number.isFinite(ts)) return "unknown";
|
|
447
|
+
return now.getTime() - ts <= freshnessSeconds * 1000 ? "live" : "stale";
|
|
448
|
+
} catch {
|
|
449
|
+
return "unknown";
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function containsTrackedPath(repoRoot: string, path: string): boolean {
|
|
454
|
+
const rel = relative(repoRoot, path);
|
|
455
|
+
if (rel.startsWith("..") || isAbsolute(rel)) return true;
|
|
456
|
+
const result = spawnSync("git", ["ls-files", "-z", "--", rel], {
|
|
457
|
+
cwd: repoRoot,
|
|
458
|
+
encoding: "utf8",
|
|
459
|
+
});
|
|
460
|
+
// If Git itself cannot answer, fail closed.
|
|
461
|
+
return result.status !== 0 || result.stdout.length > 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function safeTreeSize(path: string): number | null {
|
|
465
|
+
try {
|
|
466
|
+
const st = lstatSync(path);
|
|
467
|
+
if (st.isSymbolicLink()) return st.size;
|
|
468
|
+
if (st.isFile()) return st.size;
|
|
469
|
+
if (!st.isDirectory()) return 0;
|
|
470
|
+
let total = st.size;
|
|
471
|
+
for (const child of readdirSync(path)) {
|
|
472
|
+
const size = safeTreeSize(join(path, child));
|
|
473
|
+
if (size === null) return null;
|
|
474
|
+
total += size;
|
|
475
|
+
}
|
|
476
|
+
return total;
|
|
477
|
+
} catch {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function rowFor(
|
|
483
|
+
path: string,
|
|
484
|
+
name: string,
|
|
485
|
+
repoRoot: string,
|
|
486
|
+
classification: ArtifactClassification,
|
|
487
|
+
reason: string,
|
|
488
|
+
bytes: number | null = safeTreeSize(path),
|
|
489
|
+
): ArtifactInventoryEntry {
|
|
490
|
+
return {
|
|
491
|
+
name,
|
|
492
|
+
path,
|
|
493
|
+
relative_path: relative(repoRoot, path),
|
|
494
|
+
classification,
|
|
495
|
+
reason,
|
|
496
|
+
action: classification === "managed-expired" ? "would-delete" : "keep",
|
|
497
|
+
bytes,
|
|
498
|
+
artifact_id: null,
|
|
499
|
+
slug: null,
|
|
500
|
+
created_at: null,
|
|
501
|
+
expires_at: null,
|
|
502
|
+
owner_instance_id: null,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function normalizeSlug(value: string): string {
|
|
507
|
+
// Collapsing every non-alphanumeric run to a single "-" leaves no two
|
|
508
|
+
// adjacent dashes, so trimming the edges needs fixed-length patterns rather
|
|
509
|
+
// than "-+", whose backtracking is polynomial on a long run of dashes.
|
|
510
|
+
const collapsed = value
|
|
511
|
+
.trim()
|
|
512
|
+
.toLowerCase()
|
|
513
|
+
.replace(/[^a-z0-9]+/g, "-");
|
|
514
|
+
return collapsed.replace(/^-/, "").replace(/-$/, "").slice(0, 64);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function positiveDays(value: number): number {
|
|
518
|
+
if (!Number.isInteger(value) || value <= 0 || value > 3650) {
|
|
519
|
+
throw new Error("retention days must be between 1 and 3650");
|
|
520
|
+
}
|
|
521
|
+
return value;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function addDays(date: Date, days: number): Date {
|
|
525
|
+
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function isSafeId(value: unknown): value is string {
|
|
529
|
+
return (
|
|
530
|
+
typeof value === "string" &&
|
|
531
|
+
value.length >= 8 &&
|
|
532
|
+
value.length <= 128 &&
|
|
533
|
+
/^[A-Za-z0-9_-]+$/.test(value)
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function validActor(value: unknown): value is ArtifactActor {
|
|
538
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
539
|
+
const actor = value as Partial<ArtifactActor>;
|
|
540
|
+
return (
|
|
541
|
+
isSafeId(actor.instance_id) &&
|
|
542
|
+
(actor.session_id === undefined || isSafeId(actor.session_id)) &&
|
|
543
|
+
(actor.name === undefined || (typeof actor.name === "string" && actor.name.length <= 128))
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function validIso(value: unknown): value is string {
|
|
548
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function assertValidDate(value: Date, label: string): void {
|
|
552
|
+
if (!Number.isFinite(value.getTime())) throw new Error(`${label} must be a valid date`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function errorMessage(prefix: string, error: unknown): string {
|
|
556
|
+
return `${prefix}: ${error instanceof Error ? error.message : String(error)}`;
|
|
557
|
+
}
|
package/src/core/config.ts
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Fields owned here: `binName` (host CLI name for agent-facing strings),
|
|
10
10
|
* `hooksSetupHint`, `tools`, `workflow`, `skills`, `presence`, plus the tunable
|
|
11
|
-
* `coord` (heartbeat freshness), `
|
|
12
|
-
*
|
|
11
|
+
* `coord` (heartbeat freshness), `artifacts` (working-file retention),
|
|
12
|
+
* `backup` (restic repo/password/prune policy), and `sync` (rclone
|
|
13
|
+
* remote/prefix) sections. The `files` deny/override section
|
|
13
14
|
* is parsed separately by `web/lib/files.ts`.
|
|
14
15
|
*
|
|
15
16
|
* Env vars and CLI flags override any config value per invocation (each accessor
|
|
@@ -72,6 +73,11 @@ interface HarneryConfig {
|
|
|
72
73
|
* the sweeper prunes an agent (default 600). Read via `coordFreshnessSeconds()`.
|
|
73
74
|
*/
|
|
74
75
|
coord?: { freshness_seconds?: number };
|
|
76
|
+
/**
|
|
77
|
+
* Managed working-artifact defaults. `default_retention_days` is the
|
|
78
|
+
* create-time TTL when the caller does not pass `artifacts create --days`.
|
|
79
|
+
*/
|
|
80
|
+
artifacts?: { default_retention_days?: number };
|
|
75
81
|
/**
|
|
76
82
|
* `harn backup` (restic) defaults: `repo` path/URL, `password_file`, and the
|
|
77
83
|
* `keep_daily`/`keep_weekly`/`keep_monthly` prune policy. Read via `backupConfig()`.
|
|
@@ -359,6 +365,28 @@ export function coordFreshnessSeconds(coordRoot?: string | null): number {
|
|
|
359
365
|
return DEFAULT_FRESHNESS_SECS;
|
|
360
366
|
}
|
|
361
367
|
|
|
368
|
+
/**
|
|
369
|
+
* Default retention for a newly-created working artifact. Precedence:
|
|
370
|
+
* `HARNERY_ARTIFACT_RETENTION_DAYS` -> project/user config
|
|
371
|
+
* `artifacts.default_retention_days` -> 3 days.
|
|
372
|
+
*/
|
|
373
|
+
export function artifactDefaultRetentionDays(coordRoot?: string | null): number {
|
|
374
|
+
const env = coordEnv("ARTIFACT_RETENTION_DAYS");
|
|
375
|
+
if (env !== undefined) {
|
|
376
|
+
const n = Number(env);
|
|
377
|
+
if (Number.isFinite(n) && n > 0 && n <= 3650) return n;
|
|
378
|
+
}
|
|
379
|
+
const root = coordRoot ?? findCoordRoot();
|
|
380
|
+
if (!root) return 3;
|
|
381
|
+
const configured = readConfig(root).artifacts?.default_retention_days;
|
|
382
|
+
return typeof configured === "number" &&
|
|
383
|
+
Number.isFinite(configured) &&
|
|
384
|
+
configured > 0 &&
|
|
385
|
+
configured <= 3650
|
|
386
|
+
? configured
|
|
387
|
+
: 3;
|
|
388
|
+
}
|
|
389
|
+
|
|
362
390
|
/** Resolved `harn backup` defaults (restic repo/password + prune policy). */
|
|
363
391
|
export interface BackupConfig {
|
|
364
392
|
repo: string;
|