harnery 0.38.0 → 0.40.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/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +32 -8
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +97 -6
- package/dist/commands/backup.d.ts +16 -0
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +239 -33
- package/dist/commands/browse.d.ts +8 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +466 -231
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +6 -3
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +20 -4
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +26 -3
- package/dist/commands/qa-status.d.ts.map +1 -1
- package/dist/commands/qa-status.js +7 -2
- package/dist/commands/resources.d.ts.map +1 -1
- package/dist/commands/resources.js +23 -3
- package/dist/commands/review-pack.d.ts.map +1 -1
- package/dist/commands/review-pack.js +98 -23
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -2
- package/dist/core/adapters/profiles.d.ts +1 -1
- package/dist/core/agents/canonical-emit.d.ts +3 -2
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +2 -0
- package/dist/core/agents/cli.js +13 -6
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +5 -3
- package/dist/core/agents/live-lifecycle-v3.d.ts +2 -2
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.d.ts +7 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +63 -8
- package/dist/core/agents/session-finalizer-v3.d.ts +2 -2
- package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.d.ts +3 -3
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +2 -1
- package/dist/core/agents/state/names.d.ts +35 -2
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +80 -5
- package/dist/core/agents/state/stale-sweep.d.ts +2 -1
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +23 -6
- package/dist/core/agents/turn-elapsed.d.ts +51 -0
- package/dist/core/agents/turn-elapsed.d.ts.map +1 -0
- package/dist/core/agents/turn-elapsed.js +91 -0
- package/dist/core/artifacts/activity.d.ts +14 -0
- package/dist/core/artifacts/activity.d.ts.map +1 -0
- package/dist/core/artifacts/activity.js +52 -0
- package/dist/core/artifacts/delivery-card.d.ts +39 -0
- package/dist/core/artifacts/delivery-card.d.ts.map +1 -0
- package/dist/core/artifacts/delivery-card.js +282 -0
- package/dist/core/artifacts/index.d.ts +42 -7
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +288 -76
- package/dist/core/artifacts/mutation-lock.d.ts +2 -0
- package/dist/core/artifacts/mutation-lock.d.ts.map +1 -0
- package/dist/core/artifacts/mutation-lock.js +91 -0
- package/dist/core/backup/host-snapshot.d.ts +18 -0
- package/dist/core/backup/host-snapshot.d.ts.map +1 -0
- package/dist/core/backup/host-snapshot.js +64 -0
- package/dist/core/config.d.ts +20 -5
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +44 -10
- package/dist/core/diagnostics/advice.d.ts +38 -3
- package/dist/core/diagnostics/advice.d.ts.map +1 -1
- package/dist/core/diagnostics/advice.js +103 -99
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +111 -47
- package/dist/core/diagnostics/comparison.d.ts.map +1 -1
- package/dist/core/diagnostics/comparison.js +3 -2
- package/dist/core/diagnostics/contract.d.ts +15 -31
- package/dist/core/diagnostics/contract.d.ts.map +1 -1
- package/dist/core/diagnostics/contract.js +2 -5
- package/dist/core/diagnostics/index.d.ts +2 -0
- package/dist/core/diagnostics/index.d.ts.map +1 -1
- package/dist/core/diagnostics/index.js +2 -0
- package/dist/core/diagnostics/pressure-contract.d.ts +260 -0
- package/dist/core/diagnostics/pressure-contract.d.ts.map +1 -0
- package/dist/core/diagnostics/pressure-contract.js +70 -0
- package/dist/core/diagnostics/pressure.d.ts +25 -0
- package/dist/core/diagnostics/pressure.d.ts.map +1 -0
- package/dist/core/diagnostics/pressure.js +1138 -0
- package/dist/core/diagnostics/replay.d.ts.map +1 -1
- package/dist/core/diagnostics/replay.js +90 -10
- package/dist/core/events/v3/adapter-id.d.ts +6 -0
- package/dist/core/events/v3/adapter-id.d.ts.map +1 -0
- package/dist/core/events/v3/adapter-id.js +7 -0
- package/dist/core/events/v3/archive-retention.d.ts.map +1 -1
- package/dist/core/events/v3/archive-retention.js +3 -2
- package/dist/core/events/v3/bootstrap.d.ts +41 -4
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +120 -17
- package/dist/core/events/v3/capabilities.d.ts +16 -9
- package/dist/core/events/v3/capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/capabilities.js +37 -2
- package/dist/core/events/v3/capability-drift.d.ts +2 -2
- package/dist/core/events/v3/capability-drift.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.d.ts +48 -5
- package/dist/core/events/v3/control-witness.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.js +91 -36
- package/dist/core/events/v3/control.d.ts.map +1 -1
- package/dist/core/events/v3/control.js +22 -2
- package/dist/core/events/v3/index.d.ts +2 -1
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +2 -1
- package/dist/core/events/v3/live-feed.d.ts +7 -1
- package/dist/core/events/v3/live-feed.d.ts.map +1 -1
- package/dist/core/events/v3/live-feed.js +21 -3
- package/dist/core/events/v3/live-routing.d.ts.map +1 -1
- package/dist/core/events/v3/live-routing.js +10 -2
- package/dist/core/events/v3/producers/command-recorder.d.ts +2 -2
- package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/command-recorder.js +2 -1
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +2 -2
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +2 -1
- package/dist/core/events/v3/producers/hook-base.d.ts +2 -2
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook-base.js +15 -5
- package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook.js +17 -4
- package/dist/core/events/v3/producers/intake.d.ts +4 -4
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +3 -2
- package/dist/core/events/v3/producers/producer-state-observer.d.ts.map +1 -1
- package/dist/core/events/v3/producers/producer-state-observer.js +2 -1
- package/dist/core/events/v3/producers/recorder.d.ts +16 -5
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +31 -9
- package/dist/core/events/v3/reader.d.ts.map +1 -1
- package/dist/core/events/v3/reader.js +10 -13
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
- package/dist/core/events/v3/runtime-build.js +3 -2
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +2 -2
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/turn-telemetry.d.ts +2 -2
- package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.d.ts +16 -14
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +36 -38
- package/dist/core/hooks/backup-schedule.d.ts +34 -0
- package/dist/core/hooks/backup-schedule.d.ts.map +1 -0
- package/dist/core/hooks/backup-schedule.js +114 -0
- package/dist/core/hooks/cli.js +61 -18
- package/dist/core/hooks/codex-wsl-bridge.d.ts +2 -0
- package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -1
- package/dist/core/hooks/codex-wsl-bridge.js +18 -4
- package/dist/core/hooks/index.d.ts +1 -0
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/hooks/prompt-context/contract.d.ts +61 -0
- package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/contract.js +102 -0
- package/dist/core/hooks/prompt-context/index.d.ts +5 -0
- package/dist/core/hooks/prompt-context/index.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/index.js +2 -0
- package/dist/core/hooks/prompt-context/runner.d.ts +24 -0
- package/dist/core/hooks/prompt-context/runner.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/runner.js +249 -0
- package/dist/core/qa-artifacts.d.ts +3 -0
- package/dist/core/qa-artifacts.d.ts.map +1 -1
- package/dist/core/qa-artifacts.js +25 -3
- package/dist/core/resources/contract.d.ts +69 -3
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/contract.js +1 -1
- package/dist/core/resources/index.d.ts +1 -0
- package/dist/core/resources/index.d.ts.map +1 -1
- package/dist/core/resources/index.js +1 -0
- package/dist/core/resources/native.d.ts +36 -0
- package/dist/core/resources/native.d.ts.map +1 -0
- package/dist/core/resources/native.js +418 -0
- package/dist/core/resources/sampler.d.ts +2 -0
- package/dist/core/resources/sampler.d.ts.map +1 -1
- package/dist/core/resources/sampler.js +31 -1
- package/dist/core/resources/status.d.ts +61 -0
- package/dist/core/resources/status.d.ts.map +1 -0
- package/dist/core/resources/status.js +536 -0
- package/dist/core/resources/system.d.ts +27 -0
- package/dist/core/resources/system.d.ts.map +1 -0
- package/dist/core/resources/system.js +439 -0
- package/dist/core/semantic/contract.d.ts +13 -13
- package/dist/core/semantic/service.d.ts +2 -0
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +27 -4
- package/dist/core/storage/builtins.js +41 -0
- package/dist/core/supervisor/contract.d.ts +41 -3
- package/dist/core/supervisor/contract.d.ts.map +1 -1
- package/dist/core/supervisor/contract.js +3 -2
- package/dist/core/supervisor/findings.d.ts +3 -0
- package/dist/core/supervisor/findings.d.ts.map +1 -1
- package/dist/core/supervisor/findings.js +88 -0
- package/dist/core/supervisor/history.d.ts.map +1 -1
- package/dist/core/supervisor/history.js +14 -0
- package/dist/core/supervisor/ledger-state.d.ts +55 -0
- package/dist/core/supervisor/ledger-state.d.ts.map +1 -0
- package/dist/core/supervisor/ledger-state.js +0 -0
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +120 -7
- package/dist/core/supervisor/storage.d.ts +3 -1
- package/dist/core/supervisor/storage.d.ts.map +1 -1
- package/dist/core/supervisor/storage.js +4 -0
- package/dist/core/workflow/admission.d.ts +3 -0
- package/dist/core/workflow/admission.d.ts.map +1 -1
- package/dist/core/workflow/admission.js +28 -16
- package/dist/core/workflow/engine.js +2 -2
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +6 -1
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +4 -3
- package/dist/lib/browser/capture-fidelity.d.ts +7 -0
- package/dist/lib/browser/capture-fidelity.d.ts.map +1 -1
- package/dist/lib/browser/capture-fidelity.js +17 -0
- package/dist/lib/browser/client.d.ts +9 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +85 -37
- package/dist/lib/browser/index.d.ts +3 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -1
- package/dist/lib/browser/page-review-budget.d.ts +13 -0
- package/dist/lib/browser/page-review-budget.d.ts.map +1 -0
- package/dist/lib/browser/page-review-budget.js +251 -0
- package/dist/lib/browser/page-review-contracts.d.ts +127 -0
- package/dist/lib/browser/page-review-contracts.d.ts.map +1 -0
- package/dist/lib/browser/page-review-contracts.js +5 -0
- package/dist/lib/browser/page-review-evidence.d.ts +48 -0
- package/dist/lib/browser/page-review-evidence.d.ts.map +1 -0
- package/dist/lib/browser/page-review-evidence.js +340 -0
- package/dist/lib/browser/page-review-judge.d.ts +1 -1
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +46 -38
- package/dist/lib/browser/page-review-pack.d.ts +44 -22
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
- package/dist/lib/browser/page-review-pack.js +116 -36
- package/dist/lib/browser/page-review-source.d.ts +12 -0
- package/dist/lib/browser/page-review-source.d.ts.map +1 -0
- package/dist/lib/browser/page-review-source.js +22 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +35 -9
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +48 -14
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +215 -68
- package/dist/lib/browser/qa-snapshot.d.ts +6 -0
- package/dist/lib/browser/qa-snapshot.d.ts.map +1 -1
- package/dist/lib/browser/qa-snapshot.js +259 -48
- package/dist/lib/browser/review-capture-transaction.d.ts +13 -0
- package/dist/lib/browser/review-capture-transaction.d.ts.map +1 -0
- package/dist/lib/browser/review-capture-transaction.js +79 -0
- package/dist/lib/browser/standalone-html.d.ts.map +1 -1
- package/dist/lib/browser/standalone-html.js +4 -2
- package/dist/lib/browser/thumbnail-association.d.ts +34 -0
- package/dist/lib/browser/thumbnail-association.d.ts.map +1 -0
- package/dist/lib/browser/thumbnail-association.js +158 -0
- package/dist/lib/browser/tiling.d.ts +30 -0
- package/dist/lib/browser/tiling.d.ts.map +1 -1
- package/dist/lib/browser/tiling.js +38 -0
- package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
- package/dist/lib/docs-metadata-managed.js +4 -1
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +7 -1
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +1 -0
- package/dist/lib/local-file-url.d.ts +12 -0
- package/dist/lib/local-file-url.d.ts.map +1 -1
- package/dist/lib/local-file-url.js +16 -2
- package/dist/lib/tunnel/state.d.ts +19 -4
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +53 -13
- package/package.json +7 -4
- package/schemas/config.schema.json +69 -2
- package/src/commands/agents.ts +42 -9
- package/src/commands/artifacts.ts +130 -6
- package/src/commands/backup.ts +287 -34
- package/src/commands/browse.ts +564 -267
- package/src/commands/diagnostics.ts +6 -3
- package/src/commands/ledger-v3.ts +20 -3
- package/src/commands/qa-run.ts +37 -3
- package/src/commands/qa-status.ts +11 -2
- package/src/commands/resources.ts +23 -3
- package/src/commands/review-pack.ts +165 -62
- package/src/commands/tunnel.ts +2 -1
- package/src/core/agents/canonical-emit.ts +4 -2
- package/src/core/agents/cli.ts +14 -6
- package/src/core/agents/live-authority-v3.ts +7 -3
- package/src/core/agents/live-lifecycle-v3.ts +2 -2
- package/src/core/agents/render/prompt-context.ts +68 -14
- package/src/core/agents/session-finalizer-v3.ts +2 -2
- package/src/core/agents/state/live-coordination-view.ts +4 -4
- package/src/core/agents/state/names.ts +92 -7
- package/src/core/agents/state/stale-sweep.ts +22 -8
- package/src/core/agents/turn-elapsed.ts +135 -0
- package/src/core/artifacts/activity.ts +71 -0
- package/src/core/artifacts/delivery-card.ts +375 -0
- package/src/core/artifacts/index.ts +385 -91
- package/src/core/artifacts/mutation-lock.ts +106 -0
- package/src/core/backup/host-snapshot.ts +76 -0
- package/src/core/config.ts +66 -13
- package/src/core/diagnostics/advice.ts +148 -120
- package/src/core/diagnostics/bundle.ts +138 -52
- package/src/core/diagnostics/comparison.ts +6 -2
- package/src/core/diagnostics/contract.ts +39 -42
- package/src/core/diagnostics/index.ts +2 -0
- package/src/core/diagnostics/pressure-contract.ts +294 -0
- package/src/core/diagnostics/pressure.ts +1361 -0
- package/src/core/diagnostics/replay.ts +116 -11
- package/src/core/events/v3/adapter-id.ts +12 -0
- package/src/core/events/v3/archive-retention.ts +3 -6
- package/src/core/events/v3/bootstrap.ts +163 -16
- package/src/core/events/v3/capabilities.ts +75 -25
- package/src/core/events/v3/capability-drift.ts +2 -2
- package/src/core/events/v3/control-witness.ts +172 -41
- package/src/core/events/v3/control.ts +34 -2
- package/src/core/events/v3/index.ts +3 -0
- package/src/core/events/v3/live-feed.ts +23 -5
- package/src/core/events/v3/live-routing.ts +14 -2
- package/src/core/events/v3/producers/command-recorder.ts +4 -4
- package/src/core/events/v3/producers/coordination-recorder.ts +4 -4
- package/src/core/events/v3/producers/hook-base.ts +21 -16
- package/src/core/events/v3/producers/hook.ts +21 -4
- package/src/core/events/v3/producers/intake.ts +6 -6
- package/src/core/events/v3/producers/producer-state-observer.ts +2 -1
- package/src/core/events/v3/producers/recorder.ts +50 -21
- package/src/core/events/v3/reader.ts +14 -13
- package/src/core/events/v3/runtime-build.ts +3 -2
- package/src/core/events/v3/runtime-telemetry-capabilities.ts +3 -3
- package/src/core/events/v3/turn-telemetry.ts +4 -4
- package/src/core/hooks/adapter/output.ts +50 -36
- package/src/core/hooks/backup-schedule.ts +147 -0
- package/src/core/hooks/cli.ts +65 -14
- package/src/core/hooks/codex-wsl-bridge.ts +21 -5
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/prompt-context/contract.ts +177 -0
- package/src/core/hooks/prompt-context/index.ts +22 -0
- package/src/core/hooks/prompt-context/runner.ts +303 -0
- package/src/core/qa-artifacts.ts +29 -3
- package/src/core/resources/contract.ts +76 -3
- package/src/core/resources/index.ts +1 -0
- package/src/core/resources/native.ts +481 -0
- package/src/core/resources/sampler.ts +46 -1
- package/src/core/resources/status.ts +717 -0
- package/src/core/resources/system.ts +530 -0
- package/src/core/semantic/service.ts +37 -3
- package/src/core/storage/builtins.ts +41 -0
- package/src/core/supervisor/contract.ts +45 -3
- package/src/core/supervisor/findings.ts +159 -0
- package/src/core/supervisor/history.ts +15 -0
- package/src/core/supervisor/ledger-state.ts +0 -0
- package/src/core/supervisor/service.ts +133 -5
- package/src/core/supervisor/storage.ts +7 -0
- package/src/core/workflow/admission.ts +33 -17
- package/src/core/workflow/engine.ts +2 -2
- package/src/core/workflow/live-session-v3.ts +6 -8
- package/src/core/workflow/proof.ts +4 -3
- package/src/lib/browser/capture-fidelity.ts +23 -0
- package/src/lib/browser/client.ts +96 -44
- package/src/lib/browser/index.ts +15 -1
- package/src/lib/browser/page-review-budget.ts +309 -0
- package/src/lib/browser/page-review-contracts.ts +129 -0
- package/src/lib/browser/page-review-evidence.ts +465 -0
- package/src/lib/browser/page-review-judge.ts +56 -40
- package/src/lib/browser/page-review-pack.ts +181 -43
- package/src/lib/browser/page-review-source.ts +30 -0
- package/src/lib/browser/qa-run-contracts.ts +62 -18
- package/src/lib/browser/qa-run.ts +259 -71
- package/src/lib/browser/qa-snapshot.ts +289 -48
- package/src/lib/browser/review-capture-transaction.ts +106 -0
- package/src/lib/browser/standalone-html.ts +4 -2
- package/src/lib/browser/thumbnail-association.ts +190 -0
- package/src/lib/browser/tiling.ts +63 -0
- package/src/lib/docs-metadata-managed.ts +4 -1
- package/src/lib/identities/assume.ts +12 -4
- package/src/lib/instructions/templates.ts +1 -0
- package/src/lib/local-file-url.ts +17 -2
- package/src/lib/tunnel/state.ts +70 -13
package/src/core/agents/cli.ts
CHANGED
|
@@ -260,7 +260,10 @@ async function handleStateAction(root: string, action: string, rest: string[]):
|
|
|
260
260
|
// same durable, idempotent pool path used by normal SessionStart.
|
|
261
261
|
if (task && before && humanFacing && !before.name?.trim()) {
|
|
262
262
|
const { assignName } = await import("./state/names.ts");
|
|
263
|
-
const
|
|
263
|
+
const { coordFreshnessSeconds } = await import("../config.ts");
|
|
264
|
+
const name = assignName(root, owner, "session", {
|
|
265
|
+
freshnessSecs: coordFreshnessSeconds(root),
|
|
266
|
+
});
|
|
264
267
|
before = writer.setAssignedNameCache(root, owner, name, "session");
|
|
265
268
|
if (!before) {
|
|
266
269
|
process.stderr.write(
|
|
@@ -302,7 +305,10 @@ async function handleStateAction(root: string, action: string, rest: string[]):
|
|
|
302
305
|
(!hb.name?.trim() || /^Agent unknown - /.test(hb.suggested_session_name ?? ""))
|
|
303
306
|
) {
|
|
304
307
|
const { assignName } = await import("./state/names.ts");
|
|
305
|
-
const
|
|
308
|
+
const { coordFreshnessSeconds } = await import("../config.ts");
|
|
309
|
+
const name =
|
|
310
|
+
hb.name?.trim() ||
|
|
311
|
+
assignName(root, owner, "session", { freshnessSecs: coordFreshnessSeconds(root) });
|
|
306
312
|
hb = writer.setAssignedNameCache(root, owner, name, "session");
|
|
307
313
|
}
|
|
308
314
|
} catch (error) {
|
|
@@ -640,7 +646,11 @@ async function handleAssignName(root: string, rest: string[]): Promise<number> {
|
|
|
640
646
|
process.stderr.write(`agent-coord assign-name: invalid kind ${kindArg}\n`);
|
|
641
647
|
return 2;
|
|
642
648
|
}
|
|
643
|
-
const
|
|
649
|
+
const { coordFreshnessSeconds } = await import("../config.ts");
|
|
650
|
+
const name = assignName(root, owner, kindArg, {
|
|
651
|
+
freshnessSecs: coordFreshnessSeconds(root),
|
|
652
|
+
...(forkedFrom ? { forkedFrom } : {}),
|
|
653
|
+
});
|
|
644
654
|
process.stdout.write(
|
|
645
655
|
`${JSON.stringify({
|
|
646
656
|
instance_id: owner,
|
|
@@ -761,7 +771,6 @@ async function handlePromptContext(root: string, rest: string[]): Promise<number
|
|
|
761
771
|
const instanceId = args.instance;
|
|
762
772
|
const sessionId = args.session ?? instanceId;
|
|
763
773
|
const agentName = args.name;
|
|
764
|
-
const adapter = args.adapter;
|
|
765
774
|
const sessionNameNudge = args["session-name-nudge"] === "true";
|
|
766
775
|
const taskNudge = args["task-nudge"] === "true";
|
|
767
776
|
const hostPromptReminder = args["host-prompt-reminder"] === "true";
|
|
@@ -772,7 +781,7 @@ async function handlePromptContext(root: string, rest: string[]): Promise<number
|
|
|
772
781
|
args["turn-ritual-nudge"] === "true" ? "generic" : args["turn-ritual-nudge"];
|
|
773
782
|
if (!instanceId) {
|
|
774
783
|
process.stderr.write(
|
|
775
|
-
"agent-coord prompt-context --instance <id> [--session <id>] [--name <agent-name>] [--
|
|
784
|
+
"agent-coord prompt-context --instance <id> [--session <id>] [--name <agent-name>] [--session-name-nudge] [--task-nudge] [--host-prompt-reminder] [--status-footer-nudge] [--turn-ritual-nudge <adapter>]\n",
|
|
776
785
|
);
|
|
777
786
|
return 2;
|
|
778
787
|
}
|
|
@@ -787,7 +796,6 @@ async function handlePromptContext(root: string, rest: string[]): Promise<number
|
|
|
787
796
|
hostPromptReminder,
|
|
788
797
|
statusFooterNudge,
|
|
789
798
|
turnRitualNudge,
|
|
790
|
-
adapter,
|
|
791
799
|
});
|
|
792
800
|
process.stdout.write(text);
|
|
793
801
|
return 0;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { Adapter } from "../adapter.ts";
|
|
3
|
+
import { EVENT_ADAPTER_IDS_V3 } from "../events/v3/adapter-id.ts";
|
|
3
4
|
import type { AuthorityMutationV3 } from "../events/v3/authority-outbox.ts";
|
|
4
5
|
import { canonicalJsonV3, normalizeNativeIdV3, sha256V3 } from "../events/v3/canonical.ts";
|
|
5
6
|
import { type EventV3WriteMode, readEventV3ControlState } from "../events/v3/control.ts";
|
|
@@ -246,9 +247,9 @@ function readSessionProducerStatesForBootstrap(
|
|
|
246
247
|
nativeSessionId: string,
|
|
247
248
|
): HookProducerStateV3[] {
|
|
248
249
|
try {
|
|
249
|
-
return (
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
return EVENT_ADAPTER_IDS_V3.map((adapter) =>
|
|
251
|
+
readHookProducerStateV3(coordRoot, adapter, nativeSessionId),
|
|
252
|
+
).filter((state): state is HookProducerStateV3 => state !== undefined);
|
|
252
253
|
} catch {
|
|
253
254
|
throw new LiveCoordinationAuthorityV3Error("producer_state_unsafe");
|
|
254
255
|
}
|
|
@@ -424,6 +425,9 @@ export function reopenLiveCoordinationGenerationV3(input: {
|
|
|
424
425
|
if (!terminalView || terminalView.instance_id !== instanceId) {
|
|
425
426
|
throw new LiveCoordinationAuthorityV3Error("terminal_generation_authority_missing");
|
|
426
427
|
}
|
|
428
|
+
if (terminal.adapter === "openclaw") {
|
|
429
|
+
throw new LiveCoordinationAuthorityV3Error("lifecycle_not_human_facing");
|
|
430
|
+
}
|
|
427
431
|
if (terminalView.parent_generation_id || terminalView.delegation_id || terminalView.workflow_id) {
|
|
428
432
|
throw new LiveCoordinationAuthorityV3Error("lifecycle_not_human_facing");
|
|
429
433
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import type {
|
|
2
|
+
import type { EventAdapterIdV3 } from "../events/v3/adapter-id.ts";
|
|
3
3
|
import { buildEventV3 } from "../events/v3/builder.ts";
|
|
4
4
|
import { normalizeNativeIdV3 } from "../events/v3/canonical.ts";
|
|
5
5
|
import type { EventV3 } from "../events/v3/contract.ts";
|
|
@@ -21,7 +21,7 @@ interface LiveLifecycleObservationBaseV3 {
|
|
|
21
21
|
coordRoot: string;
|
|
22
22
|
owner: string;
|
|
23
23
|
nativeSessionId: string;
|
|
24
|
-
adapter:
|
|
24
|
+
adapter: EventAdapterIdV3;
|
|
25
25
|
observedAt?: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -46,12 +46,13 @@ import {
|
|
|
46
46
|
import { join } from "node:path";
|
|
47
47
|
|
|
48
48
|
import { coordEnv } from "../../../lib/env.ts";
|
|
49
|
-
import type { Adapter } from "../../adapter.ts";
|
|
50
49
|
import { endOfTurnStatusCommand, hostPromptReminder, resolveBinName } from "../../config.ts";
|
|
51
|
-
import {
|
|
50
|
+
import type { PressureWorkloadClass } from "../../diagnostics/contract.ts";
|
|
52
51
|
import { type RemoteMachine, readRemoteMachines } from "../../presence/index.ts";
|
|
52
|
+
import { readResourceStatus } from "../../resources/status.ts";
|
|
53
53
|
import { drainMailbox, formatMailboxDelivery } from "../mailbox.ts";
|
|
54
54
|
import { sessionNameDisplayBlock, sessionNameDisplayPending } from "../session-name-display.ts";
|
|
55
|
+
import { heartbeatPath } from "../state/heartbeat-reader.ts";
|
|
55
56
|
import {
|
|
56
57
|
readLiveCoordinationRow,
|
|
57
58
|
readLiveCoordinationRows,
|
|
@@ -95,11 +96,6 @@ export interface PromptContextOpts {
|
|
|
95
96
|
/** When true, append the non-deduplicated Codex status-footer reminder.
|
|
96
97
|
* The caller enables this only for human-facing Codex sessions. */
|
|
97
98
|
statusFooterNudge?: boolean;
|
|
98
|
-
/** Adapter id this prompt is being rendered for. Gates the mailbox drain:
|
|
99
|
-
* an adapter that cannot receive UserPromptSubmit context must not consume
|
|
100
|
-
* messages here, or they are removed from the queue and never shown. When
|
|
101
|
-
* omitted, delivery is attempted (the default route can receive). */
|
|
102
|
-
adapter?: string;
|
|
103
99
|
/** Adapter id whose Stop hook ENFORCES the end-of-turn ritual (task_set +
|
|
104
100
|
* status check). When set, append a fresh per-prompt reminder of that
|
|
105
101
|
* contract so the model satisfies it before ending the turn instead of
|
|
@@ -127,7 +123,6 @@ export function renderPromptContext(opts: PromptContextOpts): string {
|
|
|
127
123
|
hostPromptReminder: promptReminderEnabled,
|
|
128
124
|
statusFooterNudge,
|
|
129
125
|
turnRitualNudge,
|
|
130
|
-
adapter,
|
|
131
126
|
} = opts;
|
|
132
127
|
const sections: string[] = [];
|
|
133
128
|
|
|
@@ -140,12 +135,7 @@ export function renderPromptContext(opts: PromptContextOpts): string {
|
|
|
140
135
|
// exactly once, and it is the one section here that carries new information
|
|
141
136
|
// from another agent rather than a reminder.
|
|
142
137
|
//
|
|
143
|
-
|
|
144
|
-
// context. Cursor cannot, so draining there would empty the queue into output
|
|
145
|
-
// that is discarded; those sessions get their messages at SessionStart, which
|
|
146
|
-
// Cursor does deliver.
|
|
147
|
-
const canDeliverHere = !adapter || canReceiveContext(adapter as Adapter, "UserPromptSubmit");
|
|
148
|
-
if (resolvedName && canDeliverHere) {
|
|
138
|
+
if (resolvedName) {
|
|
149
139
|
const delivered = drainMailbox(coordRoot, resolvedName);
|
|
150
140
|
if (delivered.length > 0) {
|
|
151
141
|
recordDeliveredMessages(instanceId, delivered);
|
|
@@ -157,6 +147,9 @@ export function renderPromptContext(opts: PromptContextOpts): string {
|
|
|
157
147
|
const peerTable = computePeerTableIfChanged(coordRoot, instanceId, sessionId);
|
|
158
148
|
if (peerTable) sections.push(peerTable);
|
|
159
149
|
|
|
150
|
+
const resources = resourceWarningIfChanged(coordRoot, instanceId);
|
|
151
|
+
if (resources) sections.push(resources);
|
|
152
|
+
|
|
160
153
|
// 3. Council pending with hash dedup.
|
|
161
154
|
if (resolvedName) {
|
|
162
155
|
const councilMsg = computeCouncilPendingIfChanged(coordRoot, instanceId, resolvedName);
|
|
@@ -203,6 +196,67 @@ export function renderPromptContext(opts: PromptContextOpts): string {
|
|
|
203
196
|
return sections.join("\n\n");
|
|
204
197
|
}
|
|
205
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Prompts receive the published assessment, and only when it changes in a way
|
|
201
|
+
* that changes what an agent should do. The dedupe key is scope, state, and
|
|
202
|
+
* recommended action, so a moving number or a different contributor does not
|
|
203
|
+
* re-emit the same advice.
|
|
204
|
+
*/
|
|
205
|
+
export function resourceWarningIfChanged(coordRoot: string, instanceId: string): string {
|
|
206
|
+
try {
|
|
207
|
+
heartbeatPath(coordRoot, instanceId);
|
|
208
|
+
const status = readResourceStatus(coordRoot);
|
|
209
|
+
const assessment = status.assessment;
|
|
210
|
+
const hashFile = join(coordRoot, ".harnery", `.last-resource-hash.${instanceId}`);
|
|
211
|
+
const previous = safeRead(hashFile);
|
|
212
|
+
// Existing users without an observer should retain ordinary coordination.
|
|
213
|
+
if (status.assessment_capability.reason_code === "pressure_record_missing" && !previous) {
|
|
214
|
+
return "";
|
|
215
|
+
}
|
|
216
|
+
const key = `${assessment.scope}:${assessment.state}:${assessment.recommended_action}`;
|
|
217
|
+
if (previous === key) return "";
|
|
218
|
+
writeHashFile(hashFile, key);
|
|
219
|
+
const warning = assessment.state === "elevated" || assessment.state === "critical";
|
|
220
|
+
const wasWarning =
|
|
221
|
+
!!previous && (previous.includes(":elevated:") || previous.includes(":critical:"));
|
|
222
|
+
if (!warning && !wasWarning) return "";
|
|
223
|
+
const lines = [`Resource update: ${assessment.summary}`];
|
|
224
|
+
if (assessment.state === "unknown") {
|
|
225
|
+
lines.push(
|
|
226
|
+
"Resource headroom cannot be measured right now, so continue normal coordination and judge heavy work yourself.",
|
|
227
|
+
);
|
|
228
|
+
} else if (!warning && wasWarning) {
|
|
229
|
+
lines.push("The previously reported resource warning has cleared.");
|
|
230
|
+
}
|
|
231
|
+
for (const item of assessment.guidance) {
|
|
232
|
+
lines.push(`${WORKLOAD_LABEL[item.workload_class]}: ${item.summary}`);
|
|
233
|
+
}
|
|
234
|
+
const owners = assessment.contributors
|
|
235
|
+
.filter((row) => row.attribution_confidence === "exact" && row.owner_id)
|
|
236
|
+
.slice(0, 3)
|
|
237
|
+
.map((row) => `${row.owner_kind ?? "owner"} ${row.owner_id}`);
|
|
238
|
+
if (owners.length) {
|
|
239
|
+
lines.push(
|
|
240
|
+
`Measured owners of the contended resource: ${owners.join(", ")}. That says who is holding it, not what the machine as a whole can take.`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
lines.push(
|
|
244
|
+
`Read \`${resolveBinName(coordRoot)} resources status --json\` for the evidence behind this.`,
|
|
245
|
+
);
|
|
246
|
+
return lines.join("\n");
|
|
247
|
+
} catch {
|
|
248
|
+
return "";
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** How an agent recognizes the work each guidance entry is about. */
|
|
253
|
+
const WORKLOAD_LABEL: Readonly<Record<PressureWorkloadClass, string>> = {
|
|
254
|
+
lightweight: "Reads and small edits",
|
|
255
|
+
"cpu-heavy": "Builds and test runs",
|
|
256
|
+
"memory-heavy": "Browser captures and page QA",
|
|
257
|
+
"storage-heavy": "Large writes and exports",
|
|
258
|
+
};
|
|
259
|
+
|
|
206
260
|
function renderHostPromptReminder(coordRoot: string, selfInstanceId: string): string {
|
|
207
261
|
const hb = readLiveCoordinationRow(coordRoot, selfInstanceId);
|
|
208
262
|
if (!hb || hb.kind === "subagent" || hb.kind === "transient" || hb.workflow_run_id) return "";
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
} from "node:fs";
|
|
13
13
|
import { hostname } from "node:os";
|
|
14
14
|
import { join, resolve } from "node:path";
|
|
15
|
-
import type { Adapter } from "../adapter.ts";
|
|
16
15
|
import { sessionFinalizationConfig } from "../config.ts";
|
|
16
|
+
import type { EventAdapterIdV3 } from "../events/v3/adapter-id.ts";
|
|
17
17
|
import { buildEventV3 } from "../events/v3/builder.ts";
|
|
18
18
|
import { normalizeNativeIdV3 } from "../events/v3/canonical.ts";
|
|
19
19
|
import type { EventV3 } from "../events/v3/contract.ts";
|
|
@@ -55,7 +55,7 @@ export {
|
|
|
55
55
|
const EXPLICIT_END_PENDING_EXPIRY_MS = 24 * 60 * 60 * 1000;
|
|
56
56
|
|
|
57
57
|
export interface SessionArchiveObservationV3 {
|
|
58
|
-
adapter:
|
|
58
|
+
adapter: EventAdapterIdV3;
|
|
59
59
|
native_session_id: string;
|
|
60
60
|
archived: boolean;
|
|
61
61
|
observed_at: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import type
|
|
3
|
+
import { EVENT_ADAPTER_IDS_V3, type EventAdapterIdV3 } from "../../events/v3/adapter-id.ts";
|
|
4
4
|
import {
|
|
5
5
|
type CoordinationGenerationViewV3,
|
|
6
6
|
type CoordinationViewV3,
|
|
@@ -94,7 +94,7 @@ export function readLiveCoordinationRow(
|
|
|
94
94
|
export function liveCoordinationAdapterV3(
|
|
95
95
|
coordRoot: string,
|
|
96
96
|
nativeInstanceId: string,
|
|
97
|
-
):
|
|
97
|
+
): EventAdapterIdV3 | null {
|
|
98
98
|
const route = observeLiveEventLedgerRouteV3(coordRoot);
|
|
99
99
|
if (route.state === "blocked") throw new Error(`event_v3_coordination_view:${route.reason}`);
|
|
100
100
|
const view = requireAuthoritySafeCoordinationViewV3(readCoordinationViewV3(coordRoot));
|
|
@@ -213,8 +213,8 @@ export function observedAdapterV3(generation: CoordinationGenerationViewV3): str
|
|
|
213
213
|
return observation.state === "observed" ? observation.value.id : undefined;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
export function isAdapterV3(value: string | undefined): value is
|
|
217
|
-
return
|
|
216
|
+
export function isAdapterV3(value: string | undefined): value is EventAdapterIdV3 {
|
|
217
|
+
return (EVENT_ADAPTER_IDS_V3 as readonly string[]).includes(value ?? "");
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
function observedModel(generation: CoordinationGenerationViewV3): string | undefined {
|
|
@@ -25,9 +25,23 @@
|
|
|
25
25
|
* new binding instead of rewriting history; readers therefore resolve from the
|
|
26
26
|
* end of the file. Older one-row histories retain their original behavior.
|
|
27
27
|
* 3. Else: new assignment, consume a counter slot.
|
|
28
|
+
*
|
|
29
|
+
* Live-name skip: the pool holds 260 names and a busy repo consumes ~100 a
|
|
30
|
+
* day, so the counter wraps roughly every three days while sessions that
|
|
31
|
+
* resume under a stable instance_id keep a name for far longer. A counter-only
|
|
32
|
+
* pick therefore hands a live agent's name to a new one (observed 2026-09-06:
|
|
33
|
+
* two concurrent "Maya" agents, their pool slots exactly 520 apart). Assignment
|
|
34
|
+
* now probes forward past any name a fresh heartbeat still holds.
|
|
28
35
|
*/
|
|
29
36
|
|
|
30
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
existsSync,
|
|
39
|
+
mkdirSync,
|
|
40
|
+
readdirSync,
|
|
41
|
+
readFileSync,
|
|
42
|
+
renameSync,
|
|
43
|
+
writeFileSync,
|
|
44
|
+
} from "node:fs";
|
|
31
45
|
import { dirname, join } from "node:path";
|
|
32
46
|
|
|
33
47
|
/** All 260 names. */
|
|
@@ -308,7 +322,7 @@ if (COORD_NAMES.length !== 260) {
|
|
|
308
322
|
throw new Error(`COORD_NAMES table corrupt: expected 260 entries, got ${COORD_NAMES.length}`);
|
|
309
323
|
}
|
|
310
324
|
|
|
311
|
-
export type NameKind = "session" | "subagent" | "transient";
|
|
325
|
+
export type NameKind = "session" | "subagent" | "transient" | "workflow-child";
|
|
312
326
|
|
|
313
327
|
export interface NameHistoryRow {
|
|
314
328
|
instance_id: string;
|
|
@@ -441,15 +455,68 @@ export function recordNameAssumption(
|
|
|
441
455
|
return { changed: true, previous, current };
|
|
442
456
|
}
|
|
443
457
|
|
|
458
|
+
/**
|
|
459
|
+
* Names a live agent currently holds, read from the generation-bound heartbeat
|
|
460
|
+
* cache (`.harnery/active/<instance>.json`).
|
|
461
|
+
*
|
|
462
|
+
* Deliberately reads the cache directly rather than the V3 coordination view:
|
|
463
|
+
* that view imports `resolveName` from this module, so depending on it here
|
|
464
|
+
* would build an import cycle. The cache is the same set the view materializes
|
|
465
|
+
* from and is pruned as sessions end, so it answers "is this name taken right
|
|
466
|
+
* now" without one.
|
|
467
|
+
*
|
|
468
|
+
* Both error directions are safe for the caller. A stale row that slips past
|
|
469
|
+
* the freshness window costs one skipped pool slot; a live agent missing from
|
|
470
|
+
* the cache just restores the old counter-only behavior for that assignment.
|
|
471
|
+
*/
|
|
472
|
+
export function readLiveNames(
|
|
473
|
+
coordRoot: string,
|
|
474
|
+
opts: { freshnessSecs: number; nowMs?: number },
|
|
475
|
+
): Set<string> {
|
|
476
|
+
const live = new Set<string>();
|
|
477
|
+
const dir = join(coordRoot, ".harnery", "active");
|
|
478
|
+
let entries: string[];
|
|
479
|
+
try {
|
|
480
|
+
entries = readdirSync(dir);
|
|
481
|
+
} catch {
|
|
482
|
+
return live; // No cache yet (fresh checkout) → nothing is live.
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
486
|
+
const cutoffMs = nowMs - opts.freshnessSecs * 1000;
|
|
487
|
+
|
|
488
|
+
for (const entry of entries) {
|
|
489
|
+
if (!entry.endsWith(".json")) continue;
|
|
490
|
+
let row: { name?: unknown; last_heartbeat?: unknown };
|
|
491
|
+
try {
|
|
492
|
+
row = JSON.parse(readFileSync(join(dir, entry), "utf8"));
|
|
493
|
+
} catch {
|
|
494
|
+
continue; // A torn or half-written row cannot prove a name is held.
|
|
495
|
+
}
|
|
496
|
+
if (typeof row.name !== "string" || row.name.length === 0) continue;
|
|
497
|
+
if (typeof row.last_heartbeat !== "string") continue;
|
|
498
|
+
const beatMs = Date.parse(row.last_heartbeat);
|
|
499
|
+
if (!Number.isFinite(beatMs) || beatMs < cutoffMs) continue;
|
|
500
|
+
live.add(row.name);
|
|
501
|
+
}
|
|
502
|
+
return live;
|
|
503
|
+
}
|
|
504
|
+
|
|
444
505
|
/**
|
|
445
506
|
* Assign a name to <instanceId> with the given <kind>. Counter-consuming when
|
|
446
507
|
* the owner is new. Idempotent: returns existing name on resume.
|
|
508
|
+
*
|
|
509
|
+
* Slot choice probes forward from the counter and takes the first name no live
|
|
510
|
+
* agent holds, then advances the counter past every slot it skipped so the skip
|
|
511
|
+
* is durable rather than re-probed on the next assignment. A full lap without a
|
|
512
|
+
* free name means all 260 are genuinely live, so the raw counter slot is used:
|
|
513
|
+
* a duplicate name beats refusing to name an agent at all.
|
|
447
514
|
*/
|
|
448
515
|
export function assignName(
|
|
449
516
|
coordRoot: string,
|
|
450
517
|
instanceId: string,
|
|
451
518
|
kind: NameKind,
|
|
452
|
-
opts
|
|
519
|
+
opts: { freshnessSecs: number; forkedFrom?: string; nowMs?: number },
|
|
453
520
|
): string {
|
|
454
521
|
// Check 1: existing history row → original name. A resume re-enters here,
|
|
455
522
|
// which also makes fork stamping naturally idempotent: lineage lands only on
|
|
@@ -457,16 +524,34 @@ export function assignName(
|
|
|
457
524
|
const existing = resolveName(coordRoot, instanceId);
|
|
458
525
|
if (existing) return existing.name;
|
|
459
526
|
|
|
460
|
-
// New owner: consume a counter slot.
|
|
527
|
+
// New owner: consume a counter slot. The live set is gathered BEFORE the
|
|
528
|
+
// counter read on purpose. Counter read-modify-write is already unlocked, so
|
|
529
|
+
// concurrent assignments can collide on one slot; doing the directory scan
|
|
530
|
+
// first keeps that window exactly as narrow as it was before the skip existed.
|
|
531
|
+
const live = readLiveNames(coordRoot, {
|
|
532
|
+
freshnessSecs: opts.freshnessSecs,
|
|
533
|
+
...(opts.nowMs !== undefined ? { nowMs: opts.nowMs } : {}),
|
|
534
|
+
});
|
|
535
|
+
|
|
461
536
|
const cPath = counterPath(coordRoot);
|
|
462
537
|
let counter = 0;
|
|
463
538
|
if (existsSync(cPath)) {
|
|
464
539
|
const raw = readFileSync(cPath, "utf8").trim();
|
|
465
540
|
if (/^\d+$/.test(raw)) counter = Number.parseInt(raw, 10);
|
|
466
541
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
542
|
+
|
|
543
|
+
let chosen = counter;
|
|
544
|
+
for (let probe = 0; probe < 260; probe++) {
|
|
545
|
+
const candidate = COORD_NAMES[(counter + probe) % 260]!;
|
|
546
|
+
if (!live.has(candidate)) {
|
|
547
|
+
chosen = counter + probe;
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const name = COORD_NAMES[chosen % 260]!;
|
|
553
|
+
atomicWrite(cPath, String(chosen + 1));
|
|
554
|
+
const forkedFrom = opts.forkedFrom;
|
|
470
555
|
appendHistory(coordRoot, {
|
|
471
556
|
instance_id: instanceId,
|
|
472
557
|
name,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Stale-sweep: prune dead heartbeats + orphaned pid-map +
|
|
2
|
+
* Stale-sweep: prune dead heartbeats + orphaned pid-map + prompt hash
|
|
3
3
|
* files.
|
|
4
4
|
*
|
|
5
5
|
* Runs after session.started to clean up crashed-peer detritus before the new
|
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
import { existsSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { coordFreshnessSeconds } from "../../config.ts";
|
|
16
|
+
import type { EventAdapterIdV3 } from "../../events/v3/adapter-id.ts";
|
|
16
17
|
import { writeProducerDiagnosticV3 } from "../../events/v3/producers/intake.ts";
|
|
17
18
|
import { recordLiveSweepObservationV3 } from "../live-lifecycle-v3.ts";
|
|
18
19
|
|
|
19
20
|
/** platform → adapter, for the swept-event envelope (mirrors heartbeat-writer's adapterOf). */
|
|
20
|
-
function adapterFromPlatform(platform: unknown):
|
|
21
|
+
function adapterFromPlatform(platform: unknown): EventAdapterIdV3 {
|
|
21
22
|
if (platform === "cursor") return "cursor";
|
|
22
23
|
if (platform === "codex") return "codex";
|
|
24
|
+
if (platform === "openclaw") return "openclaw";
|
|
23
25
|
return "claude-code";
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -35,7 +37,7 @@ function adapterFromPlatform(platform: unknown): "claude-code" | "cursor" | "cod
|
|
|
35
37
|
function emitSwept(
|
|
36
38
|
coordRoot: string,
|
|
37
39
|
instanceId: string,
|
|
38
|
-
adapter:
|
|
40
|
+
adapter: EventAdapterIdV3,
|
|
39
41
|
sessionId: string,
|
|
40
42
|
reason: "stale" | "unparseable" | "missing_ts",
|
|
41
43
|
ageSecs?: number,
|
|
@@ -83,10 +85,12 @@ export function staleSweep(coordRoot: string): {
|
|
|
83
85
|
heartbeatsRemoved: string[];
|
|
84
86
|
pidmapsRemoved: number;
|
|
85
87
|
peerHashesRemoved: number;
|
|
88
|
+
resourceHashesRemoved: number;
|
|
86
89
|
} {
|
|
87
90
|
const heartbeatsRemoved: string[] = [];
|
|
88
91
|
let pidmapsRemoved = 0;
|
|
89
92
|
let peerHashesRemoved = 0;
|
|
93
|
+
let resourceHashesRemoved = 0;
|
|
90
94
|
|
|
91
95
|
const freshness = coordFreshnessSeconds(coordRoot);
|
|
92
96
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
@@ -207,16 +211,26 @@ export function staleSweep(coordRoot: string): {
|
|
|
207
211
|
}
|
|
208
212
|
}
|
|
209
213
|
|
|
210
|
-
// 3. Prune
|
|
214
|
+
// 3. Prune prompt hashes for dead owners. Resource hashes retain their own
|
|
215
|
+
// count so existing peer-hash telemetry keeps its original meaning.
|
|
211
216
|
const agentsDir = join(coordRoot, ".harnery");
|
|
212
217
|
if (existsSync(agentsDir)) {
|
|
213
218
|
for (const f of readdirSync(agentsDir)) {
|
|
214
|
-
|
|
215
|
-
const
|
|
219
|
+
const resourceHash = f.startsWith(".last-resource-hash.");
|
|
220
|
+
const prefix = resourceHash ? ".last-resource-hash." : ".last-peer-hash.";
|
|
221
|
+
if (!f.startsWith(prefix)) continue;
|
|
222
|
+
const owner = f.slice(prefix.length);
|
|
223
|
+
if (resourceHash) {
|
|
224
|
+
// Ignore atomic-write temporary files and preserve hashes when a
|
|
225
|
+
// retained heartbeat's liveness could not be determined safely.
|
|
226
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(owner)) continue;
|
|
227
|
+
if (existsSync(join(d, `${owner}.json`))) continue;
|
|
228
|
+
}
|
|
216
229
|
if (!liveInstanceIds.has(owner)) {
|
|
217
230
|
try {
|
|
218
231
|
unlinkSync(join(agentsDir, f));
|
|
219
|
-
|
|
232
|
+
if (resourceHash) resourceHashesRemoved += 1;
|
|
233
|
+
else peerHashesRemoved += 1;
|
|
220
234
|
} catch {
|
|
221
235
|
/* swallow */
|
|
222
236
|
}
|
|
@@ -224,5 +238,5 @@ export function staleSweep(coordRoot: string): {
|
|
|
224
238
|
}
|
|
225
239
|
}
|
|
226
240
|
|
|
227
|
-
return { heartbeatsRemoved, pidmapsRemoved, peerHashesRemoved };
|
|
241
|
+
return { heartbeatsRemoved, pidmapsRemoved, peerHashesRemoved, resourceHashesRemoved };
|
|
228
242
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long the current turn has been running, for the closing status box.
|
|
3
|
+
*
|
|
4
|
+
* The box already reports `session`, which is cumulative and says nothing
|
|
5
|
+
* about the work just done: a 9-minute session hides whether this turn took
|
|
6
|
+
* twelve seconds or eight minutes. `turn` measures from the operator's prompt
|
|
7
|
+
* (`turn.started`) to the moment the box renders, so the operator reads the
|
|
8
|
+
* cost of the turn they are about to be handed.
|
|
9
|
+
*
|
|
10
|
+
* Every read here is best-effort by contract. A missing ledger, an
|
|
11
|
+
* unvalidated one, or no `turn.started` for this owner renders no row rather
|
|
12
|
+
* than failing the status command.
|
|
13
|
+
*/
|
|
14
|
+
import type { EventV3 } from "../events/v3/contract.ts";
|
|
15
|
+
import { liveInstanceIdV3 } from "../events/v3/live-routing.ts";
|
|
16
|
+
import { readLedgerV3 } from "../events/v3/reader.ts";
|
|
17
|
+
|
|
18
|
+
/** Stop-hook remediation restarts to walk back through before giving up. */
|
|
19
|
+
const MAX_REMEDIATION_WALK_BACK = 8;
|
|
20
|
+
|
|
21
|
+
export interface TurnElapsed {
|
|
22
|
+
/** Seconds from the turn's start to its terminal, or to `now` while open. */
|
|
23
|
+
secs: number;
|
|
24
|
+
/** True once a `turn.completed` closed the turn this measures. */
|
|
25
|
+
complete: boolean;
|
|
26
|
+
/** Stop-hook restarts folded into this measurement. */
|
|
27
|
+
remediation_restarts: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function timeMs(event: EventV3 | undefined): number {
|
|
31
|
+
if (!event) return Number.NaN;
|
|
32
|
+
return Date.parse(event.time.observed_at);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function generationOf(event: EventV3): string | undefined {
|
|
36
|
+
return "generation_id" in event.scope ? event.scope.generation_id : undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Measure the newest turn for `instanceId` from an already-read event stream.
|
|
41
|
+
*
|
|
42
|
+
* A Stop-hook bounce ends one `turn.started` and opens another carrying
|
|
43
|
+
* `stop_remediation`. The operator experiences those as one turn, so the walk
|
|
44
|
+
* back over remediation restarts reports elapsed time since their prompt, not
|
|
45
|
+
* since the last retry. The walk stays inside one generation: a resumed
|
|
46
|
+
* session must not absorb the previous generation's turn into its own.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveTurnElapsed(
|
|
49
|
+
events: readonly EventV3[],
|
|
50
|
+
instanceId: string,
|
|
51
|
+
now: number = Date.now(),
|
|
52
|
+
): TurnElapsed | null {
|
|
53
|
+
const owner = liveInstanceIdV3(instanceId);
|
|
54
|
+
const ownerEvents = events.filter((event) => event.scope.instance_id === owner);
|
|
55
|
+
const starts = ownerEvents.filter(
|
|
56
|
+
(event): event is Extract<EventV3, { event_type: "turn.started" }> =>
|
|
57
|
+
event.event_type === "turn.started" && timeMs(event) <= now,
|
|
58
|
+
);
|
|
59
|
+
if (starts.length === 0) return null;
|
|
60
|
+
|
|
61
|
+
let index = starts.length - 1;
|
|
62
|
+
const generation = generationOf(starts[index]!);
|
|
63
|
+
let restarts = 0;
|
|
64
|
+
while (index > 0 && restarts < MAX_REMEDIATION_WALK_BACK) {
|
|
65
|
+
const turn = starts[index];
|
|
66
|
+
if (turn?.payload.stop_remediation !== true) break;
|
|
67
|
+
const previous = starts[index - 1];
|
|
68
|
+
if (!previous || generationOf(previous) !== generation) break;
|
|
69
|
+
index -= 1;
|
|
70
|
+
restarts += 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const startMs = timeMs(starts[index]);
|
|
74
|
+
if (!Number.isFinite(startMs)) return null;
|
|
75
|
+
|
|
76
|
+
// A terminal only closes the measurement when nothing restarted the turn
|
|
77
|
+
// after it, so match against the newest start rather than the walked-back
|
|
78
|
+
// one. Otherwise the `turn.completed` that a Stop-hook bounce produced would
|
|
79
|
+
// end the measurement mid-remediation.
|
|
80
|
+
const newestStartMs = timeMs(starts[starts.length - 1]);
|
|
81
|
+
const terminal = ownerEvents.find(
|
|
82
|
+
(event) =>
|
|
83
|
+
event.event_type === "turn.completed" &&
|
|
84
|
+
generationOf(event) === generation &&
|
|
85
|
+
timeMs(event) >= newestStartMs,
|
|
86
|
+
);
|
|
87
|
+
const terminalMs = timeMs(terminal);
|
|
88
|
+
const endMs = Number.isFinite(terminalMs) ? terminalMs : now;
|
|
89
|
+
return {
|
|
90
|
+
secs: Math.max(0, Math.floor((endMs - startMs) / 1000)),
|
|
91
|
+
complete: Number.isFinite(terminalMs),
|
|
92
|
+
remediation_restarts: restarts,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Render a turn duration for the box. Seconds stay visible below an hour
|
|
98
|
+
* because most turns land there and a bare `4m` cannot be compared with the
|
|
99
|
+
* turn before it.
|
|
100
|
+
*/
|
|
101
|
+
export function formatTurnElapsed(secs: number): string {
|
|
102
|
+
if (secs < 60) return `${secs}s`;
|
|
103
|
+
if (secs < 3600) return `${Math.floor(secs / 60)}m ${secs % 60}s`;
|
|
104
|
+
const hours = Math.floor(secs / 3600);
|
|
105
|
+
const minutes = Math.floor((secs % 3600) / 60);
|
|
106
|
+
return `${hours}h ${minutes}m`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface TurnElapsedTarget {
|
|
110
|
+
coordRoot: string;
|
|
111
|
+
instanceId: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The whole status-box contribution in one best-effort call: read the ledger
|
|
116
|
+
* and render this session's `turn` row. Null means render no row.
|
|
117
|
+
*/
|
|
118
|
+
export function turnElapsedStatusRow(
|
|
119
|
+
target: TurnElapsedTarget,
|
|
120
|
+
now: number = Date.now(),
|
|
121
|
+
): { value: string; elapsed: TurnElapsed } | null {
|
|
122
|
+
try {
|
|
123
|
+
const ledger = readLedgerV3(target.coordRoot);
|
|
124
|
+
if (!ledger.complete) return null;
|
|
125
|
+
const elapsed = resolveTurnElapsed(
|
|
126
|
+
ledger.events.map((positioned) => positioned.event),
|
|
127
|
+
target.instanceId,
|
|
128
|
+
now,
|
|
129
|
+
);
|
|
130
|
+
if (!elapsed) return null;
|
|
131
|
+
return { value: formatTurnElapsed(elapsed.secs), elapsed };
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|