harnery 0.34.1 → 0.36.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/commander.d.ts +30 -5
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +124 -94
- package/dist/commands/agents.d.ts +37 -6
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +466 -371
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +10 -10
- package/dist/commands/browse-session.d.ts.map +1 -1
- package/dist/commands/browse-session.js +12 -0
- package/dist/commands/browse.d.ts +1 -1
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +31 -6
- package/dist/commands/conversations.d.ts +10 -0
- package/dist/commands/conversations.d.ts.map +1 -0
- package/dist/commands/conversations.js +201 -0
- package/dist/commands/decision.d.ts.map +1 -1
- package/dist/commands/decision.js +2 -2
- package/dist/commands/deinit.d.ts.map +1 -1
- package/dist/commands/deinit.js +5 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +13 -2
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +51 -7
- package/dist/commands/events.d.ts.map +1 -1
- package/dist/commands/events.js +18 -1
- package/dist/commands/fetch.js +6 -5
- package/dist/commands/files.d.ts +22 -0
- package/dist/commands/files.d.ts.map +1 -0
- package/dist/commands/files.js +113 -0
- package/dist/commands/governor.js +14 -12
- package/dist/commands/inbox.d.ts +5 -0
- package/dist/commands/inbox.d.ts.map +1 -0
- package/dist/commands/inbox.js +57 -0
- package/dist/commands/init.d.ts +4 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +63 -13
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +347 -1
- package/dist/commands/logs.d.ts +4 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +169 -0
- package/dist/commands/messages.d.ts +5 -0
- package/dist/commands/messages.d.ts.map +1 -0
- package/dist/commands/messages.js +45 -0
- package/dist/commands/outline.d.ts +1 -1
- package/dist/commands/outline.d.ts.map +1 -1
- package/dist/commands/outline.js +2 -2
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +2 -2
- package/dist/commands/read.d.ts +1 -1
- package/dist/commands/read.d.ts.map +1 -1
- package/dist/commands/read.js +2 -2
- package/dist/commands/semantic.d.ts +4 -0
- package/dist/commands/semantic.d.ts.map +1 -0
- package/dist/commands/semantic.js +199 -0
- package/dist/commands/storage.d.ts +4 -0
- package/dist/commands/storage.d.ts.map +1 -0
- package/dist/commands/storage.js +238 -0
- package/dist/commands/tunnel.d.ts +5 -1
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +44 -31
- package/dist/commands/web.d.ts +3 -0
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +84 -7
- package/dist/commands/work.js +3 -3
- package/dist/commands/workflow.js +13 -13
- package/dist/core/adapters/profiles.d.ts +1 -1
- package/dist/core/agents/canonical-emit.d.ts +3 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +12 -0
- package/dist/core/agents/claim-path.d.ts +11 -0
- package/dist/core/agents/claim-path.d.ts.map +1 -0
- package/dist/core/agents/claim-path.js +22 -0
- package/dist/core/agents/cli.js +209 -39
- package/dist/core/agents/coord-client.d.ts +29 -3
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +88 -9
- package/dist/core/agents/live-authority-v3.d.ts +47 -1
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +245 -13
- package/dist/core/agents/live-lifecycle-v3.js +9 -4
- package/dist/core/agents/live-observation-v3.d.ts.map +1 -1
- package/dist/core/agents/live-observation-v3.js +8 -3
- package/dist/core/agents/render/prompt-context.d.ts +9 -3
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +36 -17
- package/dist/core/agents/rules/stop-hook.d.ts +2 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +115 -22
- package/dist/core/agents/rules/verdict.d.ts +1 -0
- package/dist/core/agents/rules/verdict.d.ts.map +1 -1
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +4 -0
- package/dist/core/agents/session-finalization-state-v3.js +1 -1
- package/dist/core/agents/session-finalizer-v3.js +5 -1
- package/dist/core/agents/session-name-display.d.ts +1 -0
- package/dist/core/agents/session-name-display.d.ts.map +1 -1
- package/dist/core/agents/session-name-display.js +8 -1
- package/dist/core/agents/state/activity-log.d.ts +3 -0
- package/dist/core/agents/state/activity-log.d.ts.map +1 -1
- package/dist/core/agents/state/activity-log.js +6 -3
- package/dist/core/agents/state/heartbeat-reader.d.ts +46 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -0
- package/dist/core/agents/state/heartbeat-reader.js +25 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts +9 -48
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +34 -34
- package/dist/core/agents/state/live-coordination-view.d.ts +6 -8
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +16 -67
- package/dist/core/agents/state/live-coordination-writer.d.ts +12 -0
- package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -0
- package/dist/core/agents/state/live-coordination-writer.js +92 -0
- package/dist/core/agents/state/names.d.ts +9 -1
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +190 -182
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +23 -5
- package/dist/core/artifacts/constants.d.ts +5 -0
- package/dist/core/artifacts/constants.d.ts.map +1 -0
- package/dist/core/artifacts/constants.js +4 -0
- package/dist/core/artifacts/index.d.ts +24 -3
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +96 -8
- package/dist/core/config.d.ts +62 -4
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +98 -4
- package/dist/core/conversations/archive.d.ts +49 -0
- package/dist/core/conversations/archive.d.ts.map +1 -0
- package/dist/core/conversations/archive.js +136 -0
- package/dist/core/conversations/catalog.d.ts +10 -0
- package/dist/core/conversations/catalog.d.ts.map +1 -0
- package/dist/core/conversations/catalog.js +72 -0
- package/dist/core/conversations/context-pack.d.ts +6 -0
- package/dist/core/conversations/context-pack.d.ts.map +1 -0
- package/dist/core/conversations/context-pack.js +60 -0
- package/dist/core/conversations/contract.d.ts +155 -0
- package/dist/core/conversations/contract.d.ts.map +1 -0
- package/dist/core/conversations/contract.js +1 -0
- package/dist/core/conversations/evaluation.d.ts +16 -0
- package/dist/core/conversations/evaluation.d.ts.map +1 -0
- package/dist/core/conversations/evaluation.js +9 -0
- package/dist/core/conversations/index.d.ts +10 -0
- package/dist/core/conversations/index.d.ts.map +1 -0
- package/dist/core/conversations/index.js +9 -0
- package/dist/core/conversations/lifecycle.d.ts +28 -0
- package/dist/core/conversations/lifecycle.d.ts.map +1 -0
- package/dist/core/conversations/lifecycle.js +29 -0
- package/dist/core/conversations/normalize.d.ts +14 -0
- package/dist/core/conversations/normalize.d.ts.map +1 -0
- package/dist/core/conversations/normalize.js +82 -0
- package/dist/core/conversations/query.d.ts +8 -0
- package/dist/core/conversations/query.d.ts.map +1 -0
- package/dist/core/conversations/query.js +280 -0
- package/dist/core/conversations/search-index.d.ts +9 -0
- package/dist/core/conversations/search-index.d.ts.map +1 -0
- package/dist/core/conversations/search-index.js +29 -0
- package/dist/core/events/legacy-storage/canary.d.ts +18 -0
- package/dist/core/events/legacy-storage/canary.d.ts.map +1 -0
- package/dist/core/events/legacy-storage/canary.js +150 -0
- package/dist/core/events/legacy-storage/fence.d.ts +15 -0
- package/dist/core/events/legacy-storage/fence.d.ts.map +1 -0
- package/dist/core/events/legacy-storage/fence.js +115 -0
- package/dist/core/events/legacy-storage/index.d.ts +6 -0
- package/dist/core/events/legacy-storage/index.d.ts.map +1 -0
- package/dist/core/events/legacy-storage/index.js +5 -0
- package/dist/core/events/legacy-storage/inventory.d.ts +10 -0
- package/dist/core/events/legacy-storage/inventory.d.ts.map +1 -0
- package/dist/core/events/legacy-storage/inventory.js +40 -0
- package/dist/core/events/legacy-storage/manifest.d.ts +23 -0
- package/dist/core/events/legacy-storage/manifest.d.ts.map +1 -0
- package/dist/core/events/legacy-storage/manifest.js +108 -0
- package/dist/core/events/legacy-storage/reader.d.ts +15 -0
- package/dist/core/events/legacy-storage/reader.d.ts.map +1 -0
- package/dist/core/events/legacy-storage/reader.js +154 -0
- package/dist/core/events/v3/authority-outbox.d.ts +7 -2
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +16 -3
- package/dist/core/events/v3/base-contract.d.ts +851 -16
- package/dist/core/events/v3/base-contract.d.ts.map +1 -1
- package/dist/core/events/v3/base-contract.js +50 -7
- package/dist/core/events/v3/bootstrap.d.ts +39 -0
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +167 -3
- package/dist/core/events/v3/capabilities.d.ts +11 -0
- package/dist/core/events/v3/capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/capabilities.js +26 -3
- package/dist/core/events/v3/control-witness.d.ts +44 -0
- package/dist/core/events/v3/control-witness.d.ts.map +1 -0
- package/dist/core/events/v3/control-witness.js +233 -0
- package/dist/core/events/v3/control-writer.d.ts +4 -0
- package/dist/core/events/v3/control-writer.d.ts.map +1 -0
- package/dist/core/events/v3/control-writer.js +16 -0
- package/dist/core/events/v3/control.d.ts +8 -2
- package/dist/core/events/v3/control.d.ts.map +1 -1
- package/dist/core/events/v3/control.js +46 -18
- package/dist/core/events/v3/coordination-transaction-recovery.d.ts +42 -0
- package/dist/core/events/v3/coordination-transaction-recovery.d.ts.map +1 -0
- package/dist/core/events/v3/coordination-transaction-recovery.js +342 -0
- package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
- package/dist/core/events/v3/coordination-view.js +16 -1
- package/dist/core/events/v3/generated.d.ts +1 -1
- package/dist/core/events/v3/generated.js +1 -1
- package/dist/core/events/v3/index.d.ts +8 -5
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +7 -4
- package/dist/core/events/v3/latency.d.ts +31 -1
- package/dist/core/events/v3/latency.d.ts.map +1 -1
- package/dist/core/events/v3/latency.js +127 -3
- package/dist/core/events/v3/live-route-observer.d.ts +31 -0
- package/dist/core/events/v3/live-route-observer.d.ts.map +1 -0
- package/dist/core/events/v3/live-route-observer.js +58 -0
- package/dist/core/events/v3/live-routing.d.ts +42 -20
- package/dist/core/events/v3/live-routing.d.ts.map +1 -1
- package/dist/core/events/v3/live-routing.js +153 -41
- package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/command-recorder.js +10 -1
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +11 -1
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +90 -13
- package/dist/core/events/v3/producers/diagnostic-summaries.d.ts +129 -0
- package/dist/core/events/v3/producers/diagnostic-summaries.d.ts.map +1 -0
- package/dist/core/events/v3/producers/diagnostic-summaries.js +499 -0
- package/dist/core/events/v3/producers/hook-base.d.ts +17 -1
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook-base.js +83 -2
- package/dist/core/events/v3/producers/intake.d.ts +13 -0
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +36 -2
- package/dist/core/events/v3/producers/producer-state-observer.d.ts +8 -0
- package/dist/core/events/v3/producers/producer-state-observer.d.ts.map +1 -0
- package/dist/core/events/v3/producers/producer-state-observer.js +45 -0
- package/dist/core/events/v3/producers/recorder.d.ts +117 -1
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +992 -27
- package/dist/core/events/v3/reader.d.ts +51 -0
- package/dist/core/events/v3/reader.d.ts.map +1 -1
- package/dist/core/events/v3/reader.js +181 -1
- package/dist/core/events/v3/rotation-config.d.ts +9 -0
- package/dist/core/events/v3/rotation-config.d.ts.map +1 -0
- package/dist/core/events/v3/rotation-config.js +91 -0
- package/dist/core/events/v3/runtime-build.d.ts +11 -0
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -0
- package/dist/core/events/v3/runtime-build.js +39 -0
- package/dist/core/events/v3/runtime-identity.d.ts +5 -0
- package/dist/core/events/v3/runtime-identity.d.ts.map +1 -0
- package/dist/core/events/v3/runtime-identity.js +18 -0
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +52 -0
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -0
- package/dist/core/events/v3/runtime-telemetry-capabilities.js +54 -0
- package/dist/core/events/v3/support-storage/classify.d.ts +46 -0
- package/dist/core/events/v3/support-storage/classify.d.ts.map +1 -0
- package/dist/core/events/v3/support-storage/classify.js +149 -0
- package/dist/core/events/v3/support-storage/eligibility.d.ts +29 -0
- package/dist/core/events/v3/support-storage/eligibility.d.ts.map +1 -0
- package/dist/core/events/v3/support-storage/eligibility.js +35 -0
- package/dist/core/events/v3/support-storage/index.d.ts +10 -0
- package/dist/core/events/v3/support-storage/index.d.ts.map +1 -0
- package/dist/core/events/v3/support-storage/index.js +9 -0
- package/dist/core/events/v3/support-storage/inventory.d.ts +30 -0
- package/dist/core/events/v3/support-storage/inventory.d.ts.map +1 -0
- package/dist/core/events/v3/support-storage/inventory.js +94 -0
- package/dist/core/events/v3/support-storage/logical-authority.d.ts +14 -0
- package/dist/core/events/v3/support-storage/logical-authority.d.ts.map +1 -0
- package/dist/core/events/v3/support-storage/logical-authority.js +104 -0
- package/dist/core/events/v3/support-storage/maintenance.d.ts +98 -0
- package/dist/core/events/v3/support-storage/maintenance.d.ts.map +1 -0
- package/dist/core/events/v3/support-storage/maintenance.js +352 -0
- package/dist/core/events/v3/support-storage/pack-contract.d.ts +59 -0
- package/dist/core/events/v3/support-storage/pack-contract.d.ts.map +1 -0
- package/dist/core/events/v3/support-storage/pack-contract.js +249 -0
- package/dist/core/events/v3/support-storage/pack-reader.d.ts +34 -0
- package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -0
- package/dist/core/events/v3/support-storage/pack-reader.js +258 -0
- package/dist/core/events/v3/support-storage/pack-writer.d.ts +30 -0
- package/dist/core/events/v3/support-storage/pack-writer.d.ts.map +1 -0
- package/dist/core/events/v3/support-storage/pack-writer.js +268 -0
- package/dist/core/events/v3/support-storage/recovery.d.ts +15 -0
- package/dist/core/events/v3/support-storage/recovery.d.ts.map +1 -0
- package/dist/core/events/v3/support-storage/recovery.js +22 -0
- package/dist/core/events/v3/turn-telemetry.d.ts +21 -6
- package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
- package/dist/core/events/v3/turn-telemetry.js +32 -2
- package/dist/core/events/v3/writer.d.ts +13 -1
- package/dist/core/events/v3/writer.d.ts.map +1 -1
- package/dist/core/events/v3/writer.js +126 -12
- package/dist/core/governor/plan-read.d.ts.map +1 -1
- package/dist/core/governor/plan-read.js +5 -21
- package/dist/core/governor/planning.js +6 -8
- package/dist/core/governor/service-read.d.ts +12 -0
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +266 -2
- package/dist/core/governor/service.d.ts +1 -0
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +53 -18
- package/dist/core/guard/evidence-v3.d.ts.map +1 -1
- package/dist/core/guard/evidence-v3.js +2 -0
- package/dist/core/hooks/adapter/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +1 -0
- package/dist/core/hooks/adapter/output.d.ts +4 -2
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +13 -5
- package/dist/core/hooks/adapter/parse.d.ts +3 -0
- package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
- package/dist/core/hooks/adapter/parse.js +14 -0
- package/dist/core/hooks/adapter/runtime-telemetry.d.ts +142 -0
- package/dist/core/hooks/adapter/runtime-telemetry.d.ts.map +1 -0
- package/dist/core/hooks/adapter/runtime-telemetry.js +869 -0
- package/dist/core/hooks/cli.js +318 -61
- package/dist/core/hooks/effects/image-capture.d.ts +1 -1
- package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
- package/dist/core/hooks/effects/image-capture.js +4 -2
- package/dist/core/hooks/remediation-cap.d.ts +39 -0
- package/dist/core/hooks/remediation-cap.d.ts.map +1 -0
- package/dist/core/hooks/remediation-cap.js +63 -0
- package/dist/core/hooks/resolve/coord-root.d.ts +22 -9
- package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -1
- package/dist/core/hooks/resolve/coord-root.js +92 -11
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +5 -0
- package/dist/core/hooks/resolve/transcript.d.ts +33 -9
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +33 -10
- package/dist/core/inbox/contract.d.ts +64 -0
- package/dist/core/inbox/contract.d.ts.map +1 -0
- package/dist/core/inbox/contract.js +13 -0
- package/dist/core/inbox/index.d.ts +5 -0
- package/dist/core/inbox/index.d.ts.map +1 -0
- package/dist/core/inbox/index.js +4 -0
- package/dist/core/inbox/lifecycle.d.ts +22 -0
- package/dist/core/inbox/lifecycle.d.ts.map +1 -0
- package/dist/core/inbox/lifecycle.js +41 -0
- package/dist/core/inbox/service.d.ts +36 -0
- package/dist/core/inbox/service.d.ts.map +1 -0
- package/dist/core/inbox/service.js +210 -0
- package/dist/core/inbox/watcher.d.ts +7 -0
- package/dist/core/inbox/watcher.d.ts.map +1 -0
- package/dist/core/inbox/watcher.js +26 -0
- package/dist/core/presence/codec-digest.d.ts.map +1 -1
- package/dist/core/presence/codec-digest.js +14 -3
- package/dist/core/presence/relay-client.d.ts +1 -0
- package/dist/core/presence/relay-client.d.ts.map +1 -1
- package/dist/core/presence/relay-client.js +52 -8
- package/dist/core/semantic/adapters.d.ts +37 -0
- package/dist/core/semantic/adapters.d.ts.map +1 -0
- package/dist/core/semantic/adapters.js +360 -0
- package/dist/core/semantic/contract.d.ts +1072 -0
- package/dist/core/semantic/contract.d.ts.map +1 -0
- package/dist/core/semantic/contract.js +331 -0
- package/dist/core/semantic/evidence.d.ts +11 -0
- package/dist/core/semantic/evidence.d.ts.map +1 -0
- package/dist/core/semantic/evidence.js +262 -0
- package/dist/core/semantic/index.d.ts +14 -0
- package/dist/core/semantic/index.d.ts.map +1 -0
- package/dist/core/semantic/index.js +13 -0
- package/dist/core/semantic/once.d.ts +38 -0
- package/dist/core/semantic/once.d.ts.map +1 -0
- package/dist/core/semantic/once.js +364 -0
- package/dist/core/semantic/prompt.d.ts +12 -0
- package/dist/core/semantic/prompt.d.ts.map +1 -0
- package/dist/core/semantic/prompt.js +83 -0
- package/dist/core/semantic/review.d.ts +144 -0
- package/dist/core/semantic/review.d.ts.map +1 -0
- package/dist/core/semantic/review.js +544 -0
- package/dist/core/semantic/routes.d.ts +10 -0
- package/dist/core/semantic/routes.d.ts.map +1 -0
- package/dist/core/semantic/routes.js +21 -0
- package/dist/core/semantic/scheduler.d.ts +27 -0
- package/dist/core/semantic/scheduler.d.ts.map +1 -0
- package/dist/core/semantic/scheduler.js +91 -0
- package/dist/core/semantic/service-status.d.ts +51 -0
- package/dist/core/semantic/service-status.d.ts.map +1 -0
- package/dist/core/semantic/service-status.js +140 -0
- package/dist/core/semantic/service.d.ts +52 -0
- package/dist/core/semantic/service.d.ts.map +1 -0
- package/dist/core/semantic/service.js +559 -0
- package/dist/core/semantic/soak.d.ts +75 -0
- package/dist/core/semantic/soak.d.ts.map +1 -0
- package/dist/core/semantic/soak.js +476 -0
- package/dist/core/semantic/storage.d.ts +81 -0
- package/dist/core/semantic/storage.d.ts.map +1 -0
- package/dist/core/semantic/storage.js +180 -0
- package/dist/core/semantic/usage.d.ts +43 -0
- package/dist/core/semantic/usage.d.ts.map +1 -0
- package/dist/core/semantic/usage.js +193 -0
- package/dist/core/semantic/validate.d.ts +18 -0
- package/dist/core/semantic/validate.d.ts.map +1 -0
- package/dist/core/semantic/validate.js +207 -0
- package/dist/core/storage/buffer.d.ts +28 -0
- package/dist/core/storage/buffer.d.ts.map +1 -0
- package/dist/core/storage/buffer.js +101 -0
- package/dist/core/storage/builtins.d.ts +3 -0
- package/dist/core/storage/builtins.d.ts.map +1 -0
- package/dist/core/storage/builtins.js +683 -0
- package/dist/core/storage/catalog.d.ts +19 -0
- package/dist/core/storage/catalog.d.ts.map +1 -0
- package/dist/core/storage/catalog.js +365 -0
- package/dist/core/storage/config.d.ts +16 -0
- package/dist/core/storage/config.d.ts.map +1 -0
- package/dist/core/storage/config.js +311 -0
- package/dist/core/storage/contract.d.ts +275 -0
- package/dist/core/storage/contract.d.ts.map +1 -0
- package/dist/core/storage/contract.js +3 -0
- package/dist/core/storage/durable-history.d.ts +36 -0
- package/dist/core/storage/durable-history.d.ts.map +1 -0
- package/dist/core/storage/durable-history.js +606 -0
- package/dist/core/storage/health.d.ts +4 -0
- package/dist/core/storage/health.d.ts.map +1 -0
- package/dist/core/storage/health.js +65 -0
- package/dist/core/storage/index.d.ts +16 -0
- package/dist/core/storage/index.d.ts.map +1 -0
- package/dist/core/storage/index.js +15 -0
- package/dist/core/storage/inventory.d.ts +25 -0
- package/dist/core/storage/inventory.d.ts.map +1 -0
- package/dist/core/storage/inventory.js +666 -0
- package/dist/core/storage/jsonl.d.ts +52 -0
- package/dist/core/storage/jsonl.d.ts.map +1 -0
- package/dist/core/storage/jsonl.js +156 -0
- package/dist/core/storage/log-retention.d.ts +52 -0
- package/dist/core/storage/log-retention.d.ts.map +1 -0
- package/dist/core/storage/log-retention.js +419 -0
- package/dist/core/storage/logger.d.ts +82 -0
- package/dist/core/storage/logger.d.ts.map +1 -0
- package/dist/core/storage/logger.js +411 -0
- package/dist/core/storage/maintenance-providers.d.ts +26 -0
- package/dist/core/storage/maintenance-providers.d.ts.map +1 -0
- package/dist/core/storage/maintenance-providers.js +141 -0
- package/dist/core/storage/maintenance.d.ts +125 -0
- package/dist/core/storage/maintenance.d.ts.map +1 -0
- package/dist/core/storage/maintenance.js +609 -0
- package/dist/core/storage/metrics.d.ts +60 -0
- package/dist/core/storage/metrics.d.ts.map +1 -0
- package/dist/core/storage/metrics.js +134 -0
- package/dist/core/storage/policy.d.ts +7 -0
- package/dist/core/storage/policy.d.ts.map +1 -0
- package/dist/core/storage/policy.js +110 -0
- package/dist/core/storage/process-log.d.ts +38 -0
- package/dist/core/storage/process-log.d.ts.map +1 -0
- package/dist/core/storage/process-log.js +165 -0
- package/dist/core/storage/query.d.ts +34 -0
- package/dist/core/storage/query.d.ts.map +1 -0
- package/dist/core/storage/query.js +295 -0
- package/dist/core/storage/receipts.d.ts +23 -0
- package/dist/core/storage/receipts.d.ts.map +1 -0
- package/dist/core/storage/receipts.js +117 -0
- package/dist/core/storage/segments.d.ts +84 -0
- package/dist/core/storage/segments.d.ts.map +1 -0
- package/dist/core/storage/segments.js +542 -0
- package/dist/core/storage/sink-graph.d.ts +65 -0
- package/dist/core/storage/sink-graph.d.ts.map +1 -0
- package/dist/core/storage/sink-graph.js +213 -0
- package/dist/core/storage/storm-control.d.ts +23 -0
- package/dist/core/storage/storm-control.d.ts.map +1 -0
- package/dist/core/storage/storm-control.js +59 -0
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +10 -14
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +2 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lazy-commands.d.ts +26 -0
- package/dist/lazy-commands.d.ts.map +1 -0
- package/dist/lazy-commands.js +210 -0
- package/dist/lib/browser/client.d.ts +11 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +13 -0
- package/dist/lib/browser/index.d.ts +1 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/standalone-html.d.ts +25 -0
- package/dist/lib/browser/standalone-html.d.ts.map +1 -0
- package/dist/lib/browser/standalone-html.js +284 -0
- package/dist/lib/docs-lint.d.ts +2 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +45 -80
- package/dist/lib/docs-metadata-audit.d.ts +3 -0
- package/dist/lib/docs-metadata-audit.d.ts.map +1 -1
- package/dist/lib/docs-metadata-audit.js +13 -23
- package/dist/lib/docs-metadata-sync.d.ts +1 -0
- package/dist/lib/docs-metadata-sync.d.ts.map +1 -1
- package/dist/lib/docs-metadata-sync.js +28 -10
- package/dist/lib/docs-repository-view.d.ts +22 -0
- package/dist/lib/docs-repository-view.d.ts.map +1 -0
- package/dist/lib/docs-repository-view.js +185 -0
- package/dist/lib/http/client.d.ts +11 -5
- package/dist/lib/http/client.d.ts.map +1 -1
- package/dist/lib/http/client.js +22 -4
- package/dist/lib/http/index.d.ts +1 -1
- package/dist/lib/http/index.d.ts.map +1 -1
- package/dist/lib/http/index.js +1 -1
- package/dist/lib/indexer-exclusions.d.ts +27 -0
- package/dist/lib/indexer-exclusions.d.ts.map +1 -0
- package/dist/lib/indexer-exclusions.js +176 -0
- package/dist/lib/instructions/apply.d.ts.map +1 -1
- package/dist/lib/instructions/apply.js +1 -0
- package/dist/lib/instructions/templates.d.ts +2 -0
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +149 -4
- package/dist/lib/local-file-url.d.ts +9 -0
- package/dist/lib/local-file-url.d.ts.map +1 -0
- package/dist/lib/local-file-url.js +20 -0
- package/dist/lib/web-performance.d.ts +118 -0
- package/dist/lib/web-performance.d.ts.map +1 -0
- package/dist/lib/web-performance.js +192 -0
- package/package.json +34 -2
- package/schemas/config.schema.json +58 -0
- package/schemas/event-v3.schema.json +3073 -581
- package/src/commander.ts +462 -93
- package/src/commands/agents.ts +617 -355
- package/src/commands/backup.ts +18 -14
- package/src/commands/browse-session.ts +12 -0
- package/src/commands/browse.ts +31 -9
- package/src/commands/conversations.ts +279 -0
- package/src/commands/decision.ts +8 -2
- package/src/commands/deinit.ts +6 -0
- package/src/commands/docs.ts +25 -6
- package/src/commands/doctor.ts +75 -9
- package/src/commands/events.ts +21 -1
- package/src/commands/fetch.ts +7 -6
- package/src/commands/files.ts +143 -0
- package/src/commands/governor.ts +13 -12
- package/src/commands/inbox.ts +61 -0
- package/src/commands/init.ts +78 -16
- package/src/commands/ledger-v3.ts +421 -1
- package/src/commands/logs.ts +224 -0
- package/src/commands/messages.ts +61 -0
- package/src/commands/outline.ts +6 -2
- package/src/commands/presence.ts +7 -2
- package/src/commands/read.ts +6 -2
- package/src/commands/semantic.ts +244 -0
- package/src/commands/storage.ts +310 -0
- package/src/commands/tunnel.ts +66 -52
- package/src/commands/web.ts +92 -7
- package/src/commands/work.ts +3 -3
- package/src/commands/workflow.ts +13 -13
- package/src/core/agents/canonical-emit.ts +15 -0
- package/src/core/agents/claim-path.ts +23 -0
- package/src/core/agents/cli.ts +259 -53
- package/src/core/agents/coord-client.ts +92 -8
- package/src/core/agents/live-authority-v3.ts +342 -14
- package/src/core/agents/live-lifecycle-v3.ts +14 -4
- package/src/core/agents/live-observation-v3.ts +13 -3
- package/src/core/agents/render/prompt-context.ts +40 -16
- package/src/core/agents/rules/stop-hook.ts +129 -25
- package/src/core/agents/rules/verdict.ts +1 -0
- package/src/core/agents/session-events.ts +4 -0
- package/src/core/agents/session-finalization-state-v3.ts +1 -1
- package/src/core/agents/session-finalizer-v3.ts +4 -1
- package/src/core/agents/session-name-display.ts +9 -1
- package/src/core/agents/state/activity-log.ts +7 -2
- package/src/core/agents/state/heartbeat-reader.ts +69 -0
- package/src/core/agents/state/heartbeat-writer.ts +47 -84
- package/src/core/agents/state/live-coordination-view.ts +17 -81
- package/src/core/agents/state/live-coordination-writer.ts +141 -0
- package/src/core/agents/state/names.ts +190 -182
- package/src/core/agents/state/stale-sweep.ts +25 -5
- package/src/core/artifacts/constants.ts +4 -0
- package/src/core/artifacts/index.ts +113 -8
- package/src/core/config.ts +126 -5
- package/src/core/conversations/archive.ts +237 -0
- package/src/core/conversations/catalog.ts +79 -0
- package/src/core/conversations/context-pack.ts +72 -0
- package/src/core/conversations/contract.ts +178 -0
- package/src/core/conversations/evaluation.ts +30 -0
- package/src/core/conversations/index.ts +9 -0
- package/src/core/conversations/lifecycle.ts +63 -0
- package/src/core/conversations/normalize.ts +105 -0
- package/src/core/conversations/query.ts +318 -0
- package/src/core/conversations/search-index.ts +48 -0
- package/src/core/events/legacy-storage/canary.ts +177 -0
- package/src/core/events/legacy-storage/fence.ts +129 -0
- package/src/core/events/legacy-storage/index.ts +5 -0
- package/src/core/events/legacy-storage/inventory.ts +51 -0
- package/src/core/events/legacy-storage/manifest.ts +136 -0
- package/src/core/events/legacy-storage/reader.ts +178 -0
- package/src/core/events/v3/authority-outbox.ts +22 -1
- package/src/core/events/v3/base-contract.ts +56 -7
- package/src/core/events/v3/bootstrap.ts +195 -2
- package/src/core/events/v3/capabilities.ts +37 -3
- package/src/core/events/v3/control-witness.ts +339 -0
- package/src/core/events/v3/control-writer.ts +16 -0
- package/src/core/events/v3/control.ts +54 -18
- package/src/core/events/v3/coordination-transaction-recovery.ts +477 -0
- package/src/core/events/v3/coordination-view.ts +17 -1
- package/src/core/events/v3/generated.ts +1 -1
- package/src/core/events/v3/index.ts +26 -2
- package/src/core/events/v3/latency.ts +213 -2
- package/src/core/events/v3/live-route-observer.ts +80 -0
- package/src/core/events/v3/live-routing.ts +182 -45
- package/src/core/events/v3/producers/command-recorder.ts +10 -1
- package/src/core/events/v3/producers/coordination-recorder.ts +112 -15
- package/src/core/events/v3/producers/diagnostic-summaries.ts +593 -0
- package/src/core/events/v3/producers/hook-base.ts +116 -2
- package/src/core/events/v3/producers/intake.ts +48 -2
- package/src/core/events/v3/producers/producer-state-observer.ts +52 -0
- package/src/core/events/v3/producers/recorder.ts +1403 -30
- package/src/core/events/v3/reader.ts +243 -2
- package/src/core/events/v3/rotation-config.ts +98 -0
- package/src/core/events/v3/runtime-build.ts +41 -0
- package/src/core/events/v3/runtime-identity.ts +16 -0
- package/src/core/events/v3/runtime-telemetry-capabilities.ts +143 -0
- package/src/core/events/v3/support-storage/classify.ts +209 -0
- package/src/core/events/v3/support-storage/eligibility.ts +58 -0
- package/src/core/events/v3/support-storage/index.ts +9 -0
- package/src/core/events/v3/support-storage/inventory.ts +135 -0
- package/src/core/events/v3/support-storage/logical-authority.ts +117 -0
- package/src/core/events/v3/support-storage/maintenance.ts +501 -0
- package/src/core/events/v3/support-storage/pack-contract.ts +337 -0
- package/src/core/events/v3/support-storage/pack-reader.ts +346 -0
- package/src/core/events/v3/support-storage/pack-writer.ts +352 -0
- package/src/core/events/v3/support-storage/recovery.ts +43 -0
- package/src/core/events/v3/turn-telemetry.ts +66 -5
- package/src/core/events/v3/writer.ts +168 -16
- package/src/core/governor/plan-read.ts +5 -18
- package/src/core/governor/planning.ts +5 -8
- package/src/core/governor/service-read.ts +320 -2
- package/src/core/governor/service.ts +49 -22
- package/src/core/guard/evidence-v3.ts +1 -0
- package/src/core/hooks/adapter/events.ts +1 -0
- package/src/core/hooks/adapter/output.ts +12 -5
- package/src/core/hooks/adapter/parse.ts +16 -0
- package/src/core/hooks/adapter/runtime-telemetry.ts +1200 -0
- package/src/core/hooks/cli.ts +342 -64
- package/src/core/hooks/effects/image-capture.ts +4 -1
- package/src/core/hooks/remediation-cap.ts +83 -0
- package/src/core/hooks/resolve/coord-root.ts +91 -11
- package/src/core/hooks/resolve/owner.ts +4 -0
- package/src/core/hooks/resolve/transcript.ts +55 -11
- package/src/core/inbox/contract.ts +81 -0
- package/src/core/inbox/index.ts +4 -0
- package/src/core/inbox/lifecycle.ts +104 -0
- package/src/core/inbox/service.ts +305 -0
- package/src/core/inbox/watcher.ts +36 -0
- package/src/core/presence/codec-digest.ts +15 -9
- package/src/core/presence/relay-client.ts +55 -7
- package/src/core/semantic/adapters.ts +452 -0
- package/src/core/semantic/contract.ts +430 -0
- package/src/core/semantic/evidence.ts +336 -0
- package/src/core/semantic/index.ts +201 -0
- package/src/core/semantic/once.ts +496 -0
- package/src/core/semantic/prompt.ts +96 -0
- package/src/core/semantic/review.ts +803 -0
- package/src/core/semantic/routes.ts +30 -0
- package/src/core/semantic/scheduler.ts +151 -0
- package/src/core/semantic/service-status.ts +212 -0
- package/src/core/semantic/service.ts +681 -0
- package/src/core/semantic/soak.ts +638 -0
- package/src/core/semantic/storage.ts +300 -0
- package/src/core/semantic/usage.ts +272 -0
- package/src/core/semantic/validate.ts +243 -0
- package/src/core/storage/buffer.ts +124 -0
- package/src/core/storage/builtins.ts +916 -0
- package/src/core/storage/catalog.ts +467 -0
- package/src/core/storage/config.ts +423 -0
- package/src/core/storage/contract.ts +342 -0
- package/src/core/storage/durable-history.ts +738 -0
- package/src/core/storage/health.ts +76 -0
- package/src/core/storage/index.ts +15 -0
- package/src/core/storage/inventory.ts +859 -0
- package/src/core/storage/jsonl.ts +213 -0
- package/src/core/storage/log-retention.ts +555 -0
- package/src/core/storage/logger.ts +546 -0
- package/src/core/storage/maintenance-providers.ts +200 -0
- package/src/core/storage/maintenance.ts +929 -0
- package/src/core/storage/metrics.ts +199 -0
- package/src/core/storage/policy.ts +141 -0
- package/src/core/storage/process-log.ts +203 -0
- package/src/core/storage/query.ts +360 -0
- package/src/core/storage/receipts.ts +162 -0
- package/src/core/storage/segments.ts +721 -0
- package/src/core/storage/sink-graph.ts +343 -0
- package/src/core/storage/storm-control.ts +91 -0
- package/src/core/work/state.ts +12 -21
- package/src/core/workflow/live-session-v3.ts +2 -4
- package/src/index.ts +2 -0
- package/src/lazy-commands.ts +294 -0
- package/src/lib/browser/client.ts +21 -0
- package/src/lib/browser/index.ts +4 -0
- package/src/lib/browser/standalone-html.ts +312 -0
- package/src/lib/docs-lint.ts +58 -77
- package/src/lib/docs-metadata-audit.ts +26 -21
- package/src/lib/docs-metadata-sync.ts +32 -9
- package/src/lib/docs-repository-view.ts +220 -0
- package/src/lib/http/client.ts +44 -8
- package/src/lib/http/index.ts +6 -1
- package/src/lib/indexer-exclusions.ts +173 -0
- package/src/lib/instructions/apply.ts +1 -0
- package/src/lib/instructions/templates.ts +154 -4
- package/src/lib/local-file-url.ts +31 -0
- package/src/lib/web-performance.ts +335 -0
package/src/commands/agents.ts
CHANGED
|
@@ -21,10 +21,9 @@ import {
|
|
|
21
21
|
readdirSync,
|
|
22
22
|
readFileSync,
|
|
23
23
|
rmSync,
|
|
24
|
-
statSync,
|
|
25
24
|
writeFileSync,
|
|
26
25
|
} from "node:fs";
|
|
27
|
-
import {
|
|
26
|
+
import { tmpdir } from "node:os";
|
|
28
27
|
import { basename, join, resolve } from "node:path";
|
|
29
28
|
import type { Command } from "commander";
|
|
30
29
|
import type { EmitContext, HarneryProgramContext } from "../commander.ts";
|
|
@@ -38,13 +37,18 @@ import {
|
|
|
38
37
|
import {
|
|
39
38
|
emitEventV3,
|
|
40
39
|
monorepoRoot,
|
|
40
|
+
nativeSessionIdentity,
|
|
41
41
|
normalizeAdapter,
|
|
42
42
|
resolveOwner,
|
|
43
43
|
resolveOwnerBySessionEnv,
|
|
44
44
|
resolveOwnerWithSource,
|
|
45
45
|
sessionIdentityFromEnv,
|
|
46
46
|
} from "../core/agents/index.ts";
|
|
47
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
bootstrapLiveCoordinationAuthorityV3,
|
|
49
|
+
LiveCoordinationAuthorityV3Error,
|
|
50
|
+
reopenLiveCoordinationGenerationV3,
|
|
51
|
+
} from "../core/agents/live-authority-v3.ts";
|
|
48
52
|
import {
|
|
49
53
|
listSessionFinalizationRequestsV3,
|
|
50
54
|
observeHostDisappearedV3,
|
|
@@ -52,7 +56,11 @@ import {
|
|
|
52
56
|
requestSessionEndExplicitV3,
|
|
53
57
|
type SessionFinalizationRequestV3,
|
|
54
58
|
} from "../core/agents/session-finalizer-v3.ts";
|
|
55
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
SESSION_NAME_DISPLAY_NOTE,
|
|
61
|
+
sessionNameDisplayPending,
|
|
62
|
+
} from "../core/agents/session-name-display.ts";
|
|
63
|
+
import { agentDisplayName } from "../core/agents/state/activity-log.ts";
|
|
56
64
|
import {
|
|
57
65
|
buildLifecycleSuggestedName,
|
|
58
66
|
buildSuggestedName,
|
|
@@ -77,6 +85,10 @@ import type { EventTypeV3 } from "../core/events/v3/contract.ts";
|
|
|
77
85
|
import { readEventV3ControlState } from "../core/events/v3/control.ts";
|
|
78
86
|
import { projectCoordinationViewV3 } from "../core/events/v3/coordination-view.ts";
|
|
79
87
|
import { liveInstanceIdV3 } from "../core/events/v3/live-routing.ts";
|
|
88
|
+
import {
|
|
89
|
+
countSummarizedSinceV3,
|
|
90
|
+
listDiagnosticSummariesV3,
|
|
91
|
+
} from "../core/events/v3/producers/diagnostic-summaries.ts";
|
|
80
92
|
import {
|
|
81
93
|
listHookIntakeGroupsV3,
|
|
82
94
|
listHookIntakeRecordsV3,
|
|
@@ -89,6 +101,10 @@ import { readLedgerV3 } from "../core/events/v3/reader.ts";
|
|
|
89
101
|
import { EVENT_V3_LEDGER_RELATIVE_ROOT } from "../core/events/v3/writer.ts";
|
|
90
102
|
import type { RunQualitySnapshot, RunQualityStatus } from "../core/guard/index.ts";
|
|
91
103
|
import { evaluateRunQualityIfDue } from "../core/guard/index.ts";
|
|
104
|
+
import {
|
|
105
|
+
readRuntimeContextTelemetry,
|
|
106
|
+
readRuntimeContextUsage,
|
|
107
|
+
} from "../core/hooks/adapter/runtime-telemetry.ts";
|
|
92
108
|
import { type RemoteMachine, readRemoteMachines } from "../core/presence/index.ts";
|
|
93
109
|
import { registerContextCommand } from "./context.ts";
|
|
94
110
|
|
|
@@ -152,9 +168,8 @@ function freshnessCutoffSecs(): number {
|
|
|
152
168
|
}
|
|
153
169
|
|
|
154
170
|
const SUBAGENT_NOTE =
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"bridge (per-shell marker file at .harnery/shells/<pid>) is out of scope.";
|
|
171
|
+
"Native adapter subagents resolve to their own child generation after the hook bridge " +
|
|
172
|
+
"observes delegation. An unbridged subprocess shell still resolves through its parent.";
|
|
158
173
|
|
|
159
174
|
/**
|
|
160
175
|
* Spawn options for agent-coord child processes: pin the coord root the
|
|
@@ -248,19 +263,7 @@ function taskStateOf(hb: Pick<Heartbeat, "task_state">): TaskState {
|
|
|
248
263
|
return hb.task_state ?? "active";
|
|
249
264
|
}
|
|
250
265
|
|
|
251
|
-
|
|
252
|
-
export function nativeSessionIdentity(
|
|
253
|
-
row: Pick<Heartbeat, "native_session_id" | "session_id"> | null | undefined,
|
|
254
|
-
fallback: string,
|
|
255
|
-
): string {
|
|
256
|
-
if (row?.native_session_id) return row.native_session_id;
|
|
257
|
-
// A projection-only row carries the privacy-safe canonical session
|
|
258
|
-
// fingerprint, not the adapter's native session ID needed to join the hook
|
|
259
|
-
// producer. Until a disposable cache exists, the resolved owner is the only
|
|
260
|
-
// native identity available to command surfaces such as `agents status`.
|
|
261
|
-
if (row?.session_id && !/^sid_[a-f0-9]{64}$/.test(row.session_id)) return row.session_id;
|
|
262
|
-
return fallback;
|
|
263
|
-
}
|
|
266
|
+
export { nativeSessionIdentity };
|
|
264
267
|
|
|
265
268
|
function lifecycleLabel(hb: Pick<Heartbeat, "task_state" | "task_state_reason">): string {
|
|
266
269
|
const state = taskStateOf(hb);
|
|
@@ -273,6 +276,7 @@ export function registerAgentsCommand(
|
|
|
273
276
|
program: Command,
|
|
274
277
|
emitParam: EmitContext,
|
|
275
278
|
programContext?: HarneryProgramContext,
|
|
279
|
+
binName = resolveBinName(),
|
|
276
280
|
): void {
|
|
277
281
|
emit = emitParam;
|
|
278
282
|
const cmd = program
|
|
@@ -329,9 +333,9 @@ export function registerAgentsCommand(
|
|
|
329
333
|
.command("suggest-name [description...]")
|
|
330
334
|
.description(
|
|
331
335
|
'Reprint a session name ("Agent <you> - <description>") for the operator to set as their ' +
|
|
332
|
-
"adapter tab title. With no arg, derives
|
|
336
|
+
"adapter tab title. With no arg, prefers the exact pending name, then derives from your current task. The primary naming path " +
|
|
333
337
|
"is set-task (it suggests a name on the first focus declaration); reach for this to reprint " +
|
|
334
|
-
"or
|
|
338
|
+
"or to create an explicit pending-display retry with --json. Read-only.",
|
|
335
339
|
)
|
|
336
340
|
.option("--json", "JSON output instead of the bare name")
|
|
337
341
|
.option(
|
|
@@ -394,7 +398,7 @@ export function registerAgentsCommand(
|
|
|
394
398
|
)
|
|
395
399
|
.option(
|
|
396
400
|
"--session-id <id>",
|
|
397
|
-
`Set the task on the heartbeat with this session_id directly, bypassing the ppid walk. Mirror of \`status --session-id\`: use it when the ppid walk can't resolve self (e.g. Cursor, whose shell tool calls don't descend from a pid-map-registered anchor). Discover the id via \`${
|
|
401
|
+
`Set the task on the heartbeat with this session_id directly, bypassing the ppid walk. Mirror of \`status --session-id\`: use it when the ppid walk can't resolve self (e.g. Cursor, whose shell tool calls don't descend from a pid-map-registered anchor). Discover the id via \`${binName} agents list --json\`.`,
|
|
398
402
|
)
|
|
399
403
|
.action((text: string[], opts: { sessionId?: string }) => {
|
|
400
404
|
runSetTask(text.join(" "), opts);
|
|
@@ -493,7 +497,7 @@ export function registerAgentsCommand(
|
|
|
493
497
|
cmd
|
|
494
498
|
.command("wait <name>")
|
|
495
499
|
.description(
|
|
496
|
-
`Block until a peer agent releases files (their \`files_touched\` becomes empty, OR they exit). Pair with \`${
|
|
500
|
+
`Block until a peer agent releases files (their \`files_touched\` becomes empty, OR they exit). Pair with \`${binName} agents ping\` to coordinate hand-offs.`,
|
|
497
501
|
)
|
|
498
502
|
.option(
|
|
499
503
|
"--file <path>",
|
|
@@ -576,12 +580,11 @@ export function registerAgentsCommand(
|
|
|
576
580
|
cmd
|
|
577
581
|
.command("heal")
|
|
578
582
|
.description(
|
|
579
|
-
"
|
|
580
|
-
"
|
|
581
|
-
"disposable V3 coordination cache from the authoritative ledger).",
|
|
583
|
+
"Repair the current session's disposable coordination cache from V3. " +
|
|
584
|
+
"Use explicit options to target another generation or repair process attribution.",
|
|
582
585
|
)
|
|
583
|
-
.
|
|
584
|
-
.
|
|
586
|
+
.option("--owner <id>", "Target agent's instance_id. Defaults to the current session.")
|
|
587
|
+
.option("--kind <kind>", "pidmap | cache. Defaults to cache.")
|
|
585
588
|
.option(
|
|
586
589
|
"--session-id <id>",
|
|
587
590
|
"(--kind cache) native session id used to join the authoritative V3 generation.",
|
|
@@ -598,14 +601,26 @@ export function registerAgentsCommand(
|
|
|
598
601
|
"when calling from outside Claude Code's Bash tool tree (e.g. " +
|
|
599
602
|
"from cron or an external script).",
|
|
600
603
|
)
|
|
604
|
+
.option(
|
|
605
|
+
"--quarantine-transaction <id>",
|
|
606
|
+
"Quarantine one irreconcilable prepared authority transaction after proving its event was never committed.",
|
|
607
|
+
)
|
|
608
|
+
.option(
|
|
609
|
+
"--approval-record-id <id>",
|
|
610
|
+
"Durable identifier for the operator approval authorizing transaction quarantine.",
|
|
611
|
+
)
|
|
612
|
+
.option("--yes", "Confirm the exact prepared transaction may be quarantined.")
|
|
601
613
|
.option("--json", "JSON envelope output")
|
|
602
614
|
.action(
|
|
603
615
|
(opts: {
|
|
604
|
-
owner
|
|
605
|
-
kind
|
|
616
|
+
owner?: string;
|
|
617
|
+
kind?: string;
|
|
606
618
|
sessionId?: string;
|
|
607
619
|
adapter?: string;
|
|
608
620
|
pid?: string;
|
|
621
|
+
quarantineTransaction?: string;
|
|
622
|
+
approvalRecordId?: string;
|
|
623
|
+
yes?: boolean;
|
|
609
624
|
json?: boolean;
|
|
610
625
|
}) => {
|
|
611
626
|
runHeal(opts);
|
|
@@ -944,14 +959,15 @@ function runIdentityAssume(
|
|
|
944
959
|
});
|
|
945
960
|
process.exit(1);
|
|
946
961
|
}
|
|
947
|
-
if (!opts.sessionId)
|
|
962
|
+
if (!opts.sessionId) ensureAdapterSession(root);
|
|
948
963
|
const owner = opts.sessionId ?? resolveOwner();
|
|
949
964
|
if (!owner) {
|
|
950
|
-
emit.error(
|
|
951
|
-
|
|
952
|
-
|
|
965
|
+
emit.error(
|
|
966
|
+
sessionResolutionFailure(
|
|
967
|
+
root,
|
|
953
968
|
"not in an agent session; ppid walk found no pid-map entry (pass --session-id to bypass)",
|
|
954
|
-
|
|
969
|
+
),
|
|
970
|
+
);
|
|
955
971
|
process.exit(1);
|
|
956
972
|
}
|
|
957
973
|
try {
|
|
@@ -1223,14 +1239,13 @@ function runWhoami(opts: { json?: boolean }): void {
|
|
|
1223
1239
|
process.exit(1);
|
|
1224
1240
|
}
|
|
1225
1241
|
|
|
1226
|
-
|
|
1242
|
+
ensureAdapterSession(root);
|
|
1227
1243
|
const resolved = resolveOwnerWithSource();
|
|
1228
1244
|
const myOwner = resolved.owner;
|
|
1229
1245
|
if (!myOwner) {
|
|
1230
|
-
emit.error(
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
});
|
|
1246
|
+
emit.error(
|
|
1247
|
+
sessionResolutionFailure(root, "not in an agent session; ppid walk found no pid-map entry"),
|
|
1248
|
+
);
|
|
1234
1249
|
process.exit(1);
|
|
1235
1250
|
}
|
|
1236
1251
|
|
|
@@ -1712,49 +1727,96 @@ function cursorEnvSessionId(): string | null {
|
|
|
1712
1727
|
return raw.startsWith("bc-") && raw.length > 3 ? raw.slice(3) : raw;
|
|
1713
1728
|
}
|
|
1714
1729
|
|
|
1715
|
-
|
|
1716
|
-
|
|
1730
|
+
interface CommandSessionBootstrap {
|
|
1731
|
+
adapter: "claude-code" | "cursor" | "codex";
|
|
1732
|
+
sessionId: string;
|
|
1717
1733
|
}
|
|
1718
1734
|
|
|
1719
|
-
function
|
|
1720
|
-
|
|
1735
|
+
function commandSessionBootstrap(): CommandSessionBootstrap | null {
|
|
1736
|
+
const cursorSessionId = cursorEnvSessionId();
|
|
1737
|
+
if (process.env.CURSOR_AGENT === "1" && cursorSessionId) {
|
|
1738
|
+
return { adapter: "cursor", sessionId: cursorSessionId };
|
|
1739
|
+
}
|
|
1740
|
+
if (process.env.HARNERY_AGENT_COORD_BRIDGE?.trim() === "codex-wsl") {
|
|
1741
|
+
const sessionId = sessionIdentityFromEnv();
|
|
1742
|
+
if (sessionId) return { adapter: "codex", sessionId };
|
|
1743
|
+
}
|
|
1744
|
+
const claudeSessionId = process.env.CLAUDE_CODE_SESSION_ID?.trim();
|
|
1745
|
+
if (claudeSessionId) {
|
|
1746
|
+
return { adapter: "claude-code", sessionId: claudeSessionId };
|
|
1747
|
+
}
|
|
1748
|
+
return null;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function ensureAdapterSession(root: string): void {
|
|
1752
|
+
const bootstrap = commandSessionBootstrap();
|
|
1753
|
+
if (!bootstrap) return;
|
|
1754
|
+
// Claude Code owns a reliable native SessionStart hook. Its environment is
|
|
1755
|
+
// accepted by the explicit `agents heal` recovery path, but ordinary
|
|
1756
|
+
// identity and status commands must not impersonate that hook. The heal
|
|
1757
|
+
// bootstrap records derived provenance instead.
|
|
1758
|
+
if (bootstrap.adapter === "claude-code") return;
|
|
1721
1759
|
// Explicit owner override means the caller already knows who they are.
|
|
1722
1760
|
// Bootstrapping sessionStart inherits that env into the child hook, which
|
|
1723
1761
|
// re-projects a fresh heartbeat onto the override owner and can wipe an
|
|
1724
1762
|
// assumed persona agent_id (seen under Cursor integration fixtures).
|
|
1725
|
-
if (process.env.HARNERY_AGENT_COORD_OWNER?.trim()) return;
|
|
1763
|
+
if (bootstrap.adapter === "cursor" && process.env.HARNERY_AGENT_COORD_OWNER?.trim()) return;
|
|
1726
1764
|
if (resolveOwnerBySessionEnv(root)) return;
|
|
1727
1765
|
|
|
1728
|
-
const sessionId = cursorEnvSessionId();
|
|
1729
|
-
if (!sessionId) return;
|
|
1730
1766
|
const agentHook = coordBinPath("agent-hook", root);
|
|
1731
1767
|
if (!agentHook) return;
|
|
1732
1768
|
|
|
1733
1769
|
const payload = JSON.stringify({
|
|
1734
|
-
conversation_id: sessionId,
|
|
1735
|
-
session_id: sessionId,
|
|
1770
|
+
...(bootstrap.adapter === "cursor" ? { conversation_id: bootstrap.sessionId } : {}),
|
|
1771
|
+
session_id: bootstrap.sessionId,
|
|
1736
1772
|
hook_event_name: "sessionStart",
|
|
1737
1773
|
workspace_roots: [root],
|
|
1738
1774
|
cwd: root,
|
|
1739
|
-
|
|
1740
|
-
|
|
1775
|
+
...(bootstrap.adapter === "cursor"
|
|
1776
|
+
? { composer_mode: "agent", is_background_agent: false }
|
|
1777
|
+
: { source: "resume" }),
|
|
1741
1778
|
});
|
|
1742
1779
|
|
|
1743
|
-
spawnSync("bash", [agentHook, "session-start", "--adapter",
|
|
1780
|
+
spawnSync("bash", [agentHook, "session-start", "--adapter", bootstrap.adapter], {
|
|
1744
1781
|
input: payload,
|
|
1745
1782
|
cwd: root,
|
|
1746
1783
|
encoding: "utf8",
|
|
1747
1784
|
timeout: 3000,
|
|
1748
1785
|
env: {
|
|
1749
1786
|
...process.env,
|
|
1750
|
-
HARNERY_AGENT_COORD_PLATFORM:
|
|
1787
|
+
HARNERY_AGENT_COORD_PLATFORM: bootstrap.adapter,
|
|
1751
1788
|
HARNERY_COORD_ROOT_OVERRIDE: root,
|
|
1752
|
-
|
|
1753
|
-
|
|
1789
|
+
HARNERY_AGENT_COORD_SESSION_ID: bootstrap.sessionId,
|
|
1790
|
+
...(bootstrap.adapter === "cursor"
|
|
1791
|
+
? {
|
|
1792
|
+
CURSOR_SESSION_ID: bootstrap.sessionId,
|
|
1793
|
+
CURSOR_CONVERSATION_ID: bootstrap.sessionId,
|
|
1794
|
+
}
|
|
1795
|
+
: { CODEX_THREAD_ID: bootstrap.sessionId }),
|
|
1754
1796
|
},
|
|
1755
1797
|
});
|
|
1756
1798
|
}
|
|
1757
1799
|
|
|
1800
|
+
function sessionResolutionFailure(
|
|
1801
|
+
root: string,
|
|
1802
|
+
fallbackMessage: string,
|
|
1803
|
+
): { code: string; message: string } {
|
|
1804
|
+
if (!sessionIdentityFromEnv()) return { code: "no_pidmap_entry", message: fallbackMessage };
|
|
1805
|
+
const control = readEventV3ControlState(root);
|
|
1806
|
+
if (control.state !== "candidate" && control.state !== "active") {
|
|
1807
|
+
const detail = `${control.state}:${control.reason}`;
|
|
1808
|
+
return {
|
|
1809
|
+
code: "event_v3_control_unavailable",
|
|
1810
|
+
message: `the adapter session identity is present, but the V3 control state cannot accept session onboarding (${detail})`,
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
return {
|
|
1814
|
+
code: "session_generation_unavailable",
|
|
1815
|
+
message:
|
|
1816
|
+
"the adapter session identity is present, but no current V3 session generation was available after onboarding",
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1758
1820
|
function runReleaseClaim(path: string): void {
|
|
1759
1821
|
const root = monorepoRoot();
|
|
1760
1822
|
if (!root) {
|
|
@@ -1766,10 +1828,9 @@ function runReleaseClaim(path: string): void {
|
|
|
1766
1828
|
}
|
|
1767
1829
|
const myOwner = resolveOwner();
|
|
1768
1830
|
if (!myOwner) {
|
|
1769
|
-
emit.error(
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
});
|
|
1831
|
+
emit.error(
|
|
1832
|
+
sessionResolutionFailure(root, "not in an agent session; ppid walk found no pid-map entry"),
|
|
1833
|
+
);
|
|
1773
1834
|
process.exit(1);
|
|
1774
1835
|
}
|
|
1775
1836
|
// Canonicalize: absolute paths under coordRoot get the prefix stripped;
|
|
@@ -1810,14 +1871,15 @@ function runSetTask(task: string, opts?: { sessionId?: string }): void {
|
|
|
1810
1871
|
// returns null by design, and erroring here orphans the session's first
|
|
1811
1872
|
// ritual command. The env id carries the same trust as an explicit
|
|
1812
1873
|
// --session-id, and set-task mints the heartbeat exactly as that path does.
|
|
1813
|
-
if (!opts?.sessionId)
|
|
1874
|
+
if (!opts?.sessionId) ensureAdapterSession(root);
|
|
1814
1875
|
const myOwner = opts?.sessionId ?? resolveOwner() ?? sessionIdentityFromEnv();
|
|
1815
1876
|
if (!myOwner) {
|
|
1816
|
-
emit.error(
|
|
1817
|
-
|
|
1818
|
-
|
|
1877
|
+
emit.error(
|
|
1878
|
+
sessionResolutionFailure(
|
|
1879
|
+
root,
|
|
1819
1880
|
"not in an agent session; no session-id environment identity and the ppid walk found no pid-map entry (pass --session-id to bypass)",
|
|
1820
|
-
|
|
1881
|
+
),
|
|
1882
|
+
);
|
|
1821
1883
|
process.exit(1);
|
|
1822
1884
|
}
|
|
1823
1885
|
|
|
@@ -1854,27 +1916,18 @@ function runSetTask(task: string, opts?: { sessionId?: string }): void {
|
|
|
1854
1916
|
// the first time. `first_of_session: true` means exactly "this call produced
|
|
1855
1917
|
// the session name" — it is never true with a null name (an empty first
|
|
1856
1918
|
// declaration or a `--session-id` relay both keep the window open instead).
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
//
|
|
1864
|
-
//
|
|
1865
|
-
//
|
|
1866
|
-
|
|
1867
|
-
// display was seen; it only gives the next exact block new mint evidence.
|
|
1868
|
-
const retriedSuggestedName =
|
|
1869
|
-
!firstSuggestedName &&
|
|
1870
|
-
priorHb?.suggested_session_name &&
|
|
1871
|
-
priorHb.session_name_seen_for !== priorHb.suggested_session_name &&
|
|
1872
|
-
hb?.suggested_session_name === priorHb.suggested_session_name
|
|
1873
|
-
? hb.suggested_session_name
|
|
1874
|
-
: null;
|
|
1875
|
-
const suggestedName = firstSuggestedName ?? retriedSuggestedName;
|
|
1919
|
+
// A native generation restart may rebuild the current cache during the
|
|
1920
|
+
// mutation and carry an already-seen suggestion from the prior generation.
|
|
1921
|
+
// That title is continuity evidence, not a new display request.
|
|
1922
|
+
const pendingName = sessionNameDisplayPending(hb);
|
|
1923
|
+
const firstSuggestedName = !priorHb?.suggested_session_name && pendingName ? pendingName : null;
|
|
1924
|
+
|
|
1925
|
+
// Routine focus declarations stay title-silent. A pending display no longer
|
|
1926
|
+
// turns every repeated set-task into a fresh name instruction; explicit
|
|
1927
|
+
// recovery belongs to `agents suggest-name --json`.
|
|
1928
|
+
const suggestedName = firstSuggestedName;
|
|
1876
1929
|
const firstOfSession = firstSuggestedName !== null;
|
|
1877
|
-
const sessionNameRetry =
|
|
1930
|
+
const sessionNameRetry = false;
|
|
1878
1931
|
|
|
1879
1932
|
emit.data({
|
|
1880
1933
|
instance_id: myOwner,
|
|
@@ -1915,14 +1968,15 @@ function runLifecycle(rawState: string, opts: { reason?: string; sessionId?: str
|
|
|
1915
1968
|
process.exitCode = 1;
|
|
1916
1969
|
return;
|
|
1917
1970
|
}
|
|
1918
|
-
if (!opts.sessionId)
|
|
1971
|
+
if (!opts.sessionId) ensureAdapterSession(root);
|
|
1919
1972
|
const myOwner = opts.sessionId ?? resolveOwner() ?? sessionIdentityFromEnv();
|
|
1920
1973
|
if (!myOwner) {
|
|
1921
|
-
emit.error(
|
|
1922
|
-
|
|
1923
|
-
|
|
1974
|
+
emit.error(
|
|
1975
|
+
sessionResolutionFailure(
|
|
1976
|
+
root,
|
|
1924
1977
|
"not in an agent session; ppid walk found no pid-map entry (pass --session-id to bypass)",
|
|
1925
|
-
|
|
1978
|
+
),
|
|
1979
|
+
);
|
|
1926
1980
|
process.exitCode = 1;
|
|
1927
1981
|
return;
|
|
1928
1982
|
}
|
|
@@ -2097,25 +2151,30 @@ function runSuggestName(
|
|
|
2097
2151
|
|
|
2098
2152
|
// Identity: prefer explicit --session-id (ppid-walk-free escape hatch, mirrors
|
|
2099
2153
|
// `status`/`set-task`), fall back to the ppid walk for interactive shell use.
|
|
2100
|
-
if (!opts.sessionId)
|
|
2154
|
+
if (!opts.sessionId) ensureAdapterSession(root);
|
|
2101
2155
|
const myOwner = opts.sessionId ?? resolveOwner();
|
|
2102
2156
|
if (!myOwner) {
|
|
2103
|
-
emit.error(
|
|
2104
|
-
|
|
2105
|
-
|
|
2157
|
+
emit.error(
|
|
2158
|
+
sessionResolutionFailure(
|
|
2159
|
+
root,
|
|
2106
2160
|
"not in an agent session; ppid walk found no pid-map entry (pass --session-id to bypass)",
|
|
2107
|
-
|
|
2161
|
+
),
|
|
2162
|
+
);
|
|
2108
2163
|
process.exit(1);
|
|
2109
2164
|
}
|
|
2110
2165
|
|
|
2111
2166
|
const hb = readCurrentCoordinationRow(myOwner);
|
|
2112
2167
|
const agentName = hb?.name || "unknown";
|
|
2113
|
-
//
|
|
2114
|
-
//
|
|
2115
|
-
//
|
|
2116
|
-
//
|
|
2168
|
+
// A bare suggest-name must prefer the exact pending suggestion over rebuilding
|
|
2169
|
+
// from the current task. The task may have changed since the title was minted;
|
|
2170
|
+
// reconstructing it would produce a block the display latch can never accept.
|
|
2171
|
+
// An explicit description keeps the read-only re-suggest behavior.
|
|
2172
|
+
const pendingName = sessionNameDisplayPending(hb);
|
|
2117
2173
|
const parts = descriptionParts.length > 0 ? descriptionParts : hb?.task ? [hb.task] : [];
|
|
2118
|
-
const built =
|
|
2174
|
+
const built =
|
|
2175
|
+
descriptionParts.length === 0 && pendingName
|
|
2176
|
+
? { suggestedName: pendingName, description: hb?.task ?? pendingName }
|
|
2177
|
+
: buildSuggestedName(agentName, parts);
|
|
2119
2178
|
if (!built) {
|
|
2120
2179
|
emit.error({
|
|
2121
2180
|
code: "no_description",
|
|
@@ -2132,6 +2191,7 @@ function runSuggestName(
|
|
|
2132
2191
|
emit.data({
|
|
2133
2192
|
name: displayName,
|
|
2134
2193
|
suggested_session_name: suggestedName,
|
|
2194
|
+
session_name_retry: pendingName === suggestedName,
|
|
2135
2195
|
agent_name: agentName,
|
|
2136
2196
|
description,
|
|
2137
2197
|
});
|
|
@@ -2171,14 +2231,15 @@ function runStatus(opts: {
|
|
|
2171
2231
|
|
|
2172
2232
|
// Identity resolution: prefer explicit --session-id (hook-friendly), fall
|
|
2173
2233
|
// back to ppid walk for interactive shell usage.
|
|
2174
|
-
if (!opts.sessionId)
|
|
2234
|
+
if (!opts.sessionId) ensureAdapterSession(root);
|
|
2175
2235
|
const myOwner = opts.sessionId ?? resolveOwner();
|
|
2176
2236
|
if (!myOwner) {
|
|
2177
|
-
emit.error(
|
|
2178
|
-
|
|
2179
|
-
|
|
2237
|
+
emit.error(
|
|
2238
|
+
sessionResolutionFailure(
|
|
2239
|
+
root,
|
|
2180
2240
|
"not in an agent session; ppid walk found no pid-map entry (pass --session-id from a hook payload)",
|
|
2181
|
-
|
|
2241
|
+
),
|
|
2242
|
+
);
|
|
2182
2243
|
process.exit(1);
|
|
2183
2244
|
}
|
|
2184
2245
|
|
|
@@ -2220,7 +2281,7 @@ function runStatus(opts: {
|
|
|
2220
2281
|
}
|
|
2221
2282
|
}
|
|
2222
2283
|
|
|
2223
|
-
emitEventV3({
|
|
2284
|
+
const statusEmitted = emitEventV3({
|
|
2224
2285
|
owner: myOwner,
|
|
2225
2286
|
session: nativeSessionIdentity(hb, myOwner),
|
|
2226
2287
|
adapter: normalizeAdapter(hb.platform),
|
|
@@ -2229,6 +2290,16 @@ function runStatus(opts: {
|
|
|
2229
2290
|
status: opts.endTurn ? "end_turn_checked" : opts.json ? "json_checked" : "box_checked",
|
|
2230
2291
|
},
|
|
2231
2292
|
});
|
|
2293
|
+
// The closing box is a success artifact. Never print it when the exact
|
|
2294
|
+
// evidence enforced by the Stop hook did not reach the ledger.
|
|
2295
|
+
if (opts.endTurn && !statusEmitted) {
|
|
2296
|
+
emit.error({
|
|
2297
|
+
code: "status_event_failed",
|
|
2298
|
+
message: `the end-of-turn status event could not be recorded; no status box was issued; rerun \`${resolveBinName(root)} agents status --end-turn\``,
|
|
2299
|
+
});
|
|
2300
|
+
process.exitCode = 1;
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2232
2303
|
|
|
2233
2304
|
let sessionEnd:
|
|
2234
2305
|
| { state: "queued" | "already_requested"; request_id: string }
|
|
@@ -2298,10 +2369,7 @@ function runStatus(opts: {
|
|
|
2298
2369
|
if (!ctxUsage) {
|
|
2299
2370
|
ctxStr = "unavailable";
|
|
2300
2371
|
} else if (ctxUsage.percentOnly) {
|
|
2301
|
-
|
|
2302
|
-
// window. Show the percent + window estimate, marked as estimated.
|
|
2303
|
-
const pct = Math.round((ctxUsage.used / ctxUsage.window) * 100);
|
|
2304
|
-
ctxStr = `~${pct}% (Cursor; ${fmtTokens(ctxUsage.window)} window est.)`;
|
|
2372
|
+
ctxStr = `${Math.round(ctxUsage.usedPercent)}% (Cursor first-party)`;
|
|
2305
2373
|
} else {
|
|
2306
2374
|
ctxStr = `${fmtTokens(ctxUsage.used)} / ${fmtTokens(ctxUsage.window)} (${Math.round(
|
|
2307
2375
|
(ctxUsage.used / ctxUsage.window) * 100,
|
|
@@ -2309,7 +2377,7 @@ function runStatus(opts: {
|
|
|
2309
2377
|
}
|
|
2310
2378
|
|
|
2311
2379
|
const timeStr = formatLocalTime(new Date());
|
|
2312
|
-
const displayName =
|
|
2380
|
+
const displayName = agentDisplayName(hb.instance_id, hb.name);
|
|
2313
2381
|
|
|
2314
2382
|
// Council pending: list of council IDs where this agent is a member of an
|
|
2315
2383
|
// active council in `open` round_status without a contribution to that round.
|
|
@@ -2353,8 +2421,8 @@ function runStatus(opts: {
|
|
|
2353
2421
|
})),
|
|
2354
2422
|
})),
|
|
2355
2423
|
pending_councils: pendingCouncils,
|
|
2356
|
-
context_used: ctxUsage
|
|
2357
|
-
context_window: ctxUsage
|
|
2424
|
+
context_used: ctxUsage && "used" in ctxUsage ? ctxUsage.used : null,
|
|
2425
|
+
context_window: ctxUsage && "window" in ctxUsage ? ctxUsage.window : null,
|
|
2358
2426
|
timestamp_iso: new Date().toISOString(),
|
|
2359
2427
|
timestamp_local: timeStr,
|
|
2360
2428
|
...(quality ? { quality } : {}),
|
|
@@ -2498,15 +2566,17 @@ export function collectStatusPeerHealth(
|
|
|
2498
2566
|
root: string,
|
|
2499
2567
|
myOwner: string,
|
|
2500
2568
|
nowMs = Date.now(),
|
|
2569
|
+
readRows: (root: string) => StatusPeerHeartbeat[] = readLiveCoordinationRows,
|
|
2570
|
+
freshnessSeconds = freshnessCutoffSecs(),
|
|
2501
2571
|
): { livePeers: StatusPeerHeartbeat[]; stale: number } {
|
|
2502
2572
|
let rows: StatusPeerHeartbeat[] = [];
|
|
2503
2573
|
try {
|
|
2504
|
-
rows =
|
|
2574
|
+
rows = readRows(root);
|
|
2505
2575
|
} catch {
|
|
2506
2576
|
// V3 authority failures must not resurrect disposable cache rows.
|
|
2507
2577
|
}
|
|
2508
2578
|
|
|
2509
|
-
const cutoffMs = nowMs -
|
|
2579
|
+
const cutoffMs = nowMs - freshnessSeconds * 1000;
|
|
2510
2580
|
const livePeers: StatusPeerHeartbeat[] = [];
|
|
2511
2581
|
let stale = 0;
|
|
2512
2582
|
for (const peer of rows) {
|
|
@@ -2528,205 +2598,28 @@ function fmtTokens(n: number): string {
|
|
|
2528
2598
|
function readContextUsage(
|
|
2529
2599
|
sessionId: string,
|
|
2530
2600
|
platform?: string | null,
|
|
2531
|
-
):
|
|
2601
|
+
):
|
|
2602
|
+
| { used: number; window: number; percentOnly?: false }
|
|
2603
|
+
| { usedPercent: number; percentOnly: true }
|
|
2604
|
+
| null {
|
|
2532
2605
|
if (!sessionId) return null;
|
|
2533
2606
|
// Dispatch by platform: Codex's JSONL shape is different from Claude Code's
|
|
2534
2607
|
// (event_msg/response_item vs user/assistant), and Codex transcripts live
|
|
2535
2608
|
// under ~/.codex/sessions/YYYY/MM/DD/ rather than ~/.claude/projects/.
|
|
2536
|
-
// Cursor
|
|
2537
|
-
//
|
|
2538
|
-
|
|
2539
|
-
if (platform === "
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
// workspace's state.vscdb at ItemTable.composer.composerData. The value
|
|
2549
|
-
// is a JSON blob with .allComposers[].contextUsagePercent. We find the
|
|
2550
|
-
// composer matching the agent's session_id (Cursor uses session_id == composerId
|
|
2551
|
-
// for parent chats).
|
|
2552
|
-
//
|
|
2553
|
-
// Roots searched: ~/.config/Cursor/User/workspaceStorage (Linux native install)
|
|
2554
|
-
// and /mnt/c/Users/*/AppData/Roaming/Cursor/User/workspaceStorage (WSL).
|
|
2555
|
-
// We don't filter by workspace path; we scan every workspace's state.vscdb
|
|
2556
|
-
// looking for a composerId match. Cheap enough at status time (~60K per file,
|
|
2557
|
-
// <10ms typical). When a match is found, returns synthetic { used, window }
|
|
2558
|
-
// pair where the percent is what's real; absolutes are derived as
|
|
2559
|
-
// (window * percent / 100). Window hard-coded to a Cursor-typical 200K.
|
|
2560
|
-
const roots: string[] = [];
|
|
2561
|
-
const linuxRoot = resolve(homedir(), ".config", "Cursor", "User", "workspaceStorage");
|
|
2562
|
-
if (existsSync(linuxRoot)) roots.push(linuxRoot);
|
|
2563
|
-
try {
|
|
2564
|
-
if (existsSync("/mnt/c/Users")) {
|
|
2565
|
-
for (const u of readdirSync("/mnt/c/Users")) {
|
|
2566
|
-
const p = `/mnt/c/Users/${u}/AppData/Roaming/Cursor/User/workspaceStorage`;
|
|
2567
|
-
if (existsSync(p)) roots.push(p);
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
2570
|
-
} catch {
|
|
2571
|
-
// ignore
|
|
2572
|
-
}
|
|
2573
|
-
for (const root of roots) {
|
|
2574
|
-
let workspaces: string[];
|
|
2575
|
-
try {
|
|
2576
|
-
workspaces = readdirSync(root);
|
|
2577
|
-
} catch {
|
|
2578
|
-
continue;
|
|
2579
|
-
}
|
|
2580
|
-
for (const ws of workspaces) {
|
|
2581
|
-
const dbPath = `${root}/${ws}/state.vscdb`;
|
|
2582
|
-
if (!existsSync(dbPath)) continue;
|
|
2583
|
-
try {
|
|
2584
|
-
// Lazy-import bun:sqlite so non-Bun runtimes (if any) don't choke at load.
|
|
2585
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
2586
|
-
const { Database } = require("bun:sqlite");
|
|
2587
|
-
const db = new Database(dbPath, { readonly: true });
|
|
2588
|
-
const row = db
|
|
2589
|
-
.query("SELECT value FROM ItemTable WHERE key = 'composer.composerData'")
|
|
2590
|
-
.get() as { value: string | Uint8Array } | null;
|
|
2591
|
-
db.close();
|
|
2592
|
-
if (!row?.value) continue;
|
|
2593
|
-
const text =
|
|
2594
|
-
typeof row.value === "string" ? row.value : Buffer.from(row.value).toString("utf8");
|
|
2595
|
-
const parsed = JSON.parse(text);
|
|
2596
|
-
const composers = Array.isArray(parsed?.allComposers) ? parsed.allComposers : [];
|
|
2597
|
-
for (const c of composers) {
|
|
2598
|
-
if (c?.composerId === sessionId && typeof c?.contextUsagePercent === "number") {
|
|
2599
|
-
const window = 200000; // hard-coded Cursor-typical window; refine when Cursor exposes the actual ceiling
|
|
2600
|
-
const used = Math.round((window * c.contextUsagePercent) / 100);
|
|
2601
|
-
return { used, window, percentOnly: true };
|
|
2602
|
-
}
|
|
2603
|
-
}
|
|
2604
|
-
} catch {
|
|
2605
|
-
// ignore: DB locked, schema drift, etc. Move on to the next workspace.
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
return null;
|
|
2610
|
-
}
|
|
2611
|
-
|
|
2612
|
-
function readClaudeContextUsage(sessionId: string): { used: number; window: number } | null {
|
|
2613
|
-
const root = monorepoRoot();
|
|
2614
|
-
if (!root) return null;
|
|
2615
|
-
// Claude Code's project dir scheme: prepend "-", replace "/" → "-".
|
|
2616
|
-
const encoded = `-${root.replace(/^\//, "").replace(/\//g, "-")}`;
|
|
2617
|
-
const transcriptPath = resolve(homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
2618
|
-
if (!existsSync(transcriptPath)) return null;
|
|
2619
|
-
let raw: string;
|
|
2620
|
-
try {
|
|
2621
|
-
raw = readFileSync(transcriptPath, "utf8");
|
|
2622
|
-
} catch {
|
|
2623
|
-
return null;
|
|
2624
|
-
}
|
|
2625
|
-
const lines = raw.split("\n");
|
|
2626
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2627
|
-
const line = lines[i].trim();
|
|
2628
|
-
if (!line) continue;
|
|
2629
|
-
try {
|
|
2630
|
-
const entry = JSON.parse(line);
|
|
2631
|
-
const usage = entry?.message?.usage;
|
|
2632
|
-
if (entry?.type === "assistant" && usage) {
|
|
2633
|
-
const used =
|
|
2634
|
-
(usage.input_tokens ?? 0) +
|
|
2635
|
-
(usage.cache_creation_input_tokens ?? 0) +
|
|
2636
|
-
(usage.cache_read_input_tokens ?? 0);
|
|
2637
|
-
// Window: hardcode 1M for Opus 4.7 1M (current default). Refine to
|
|
2638
|
-
// model-aware lookup when other models become routine.
|
|
2639
|
-
return { used, window: 1000000 };
|
|
2640
|
-
}
|
|
2641
|
-
} catch {
|
|
2642
|
-
// skip malformed line
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2645
|
-
return null;
|
|
2646
|
-
}
|
|
2647
|
-
|
|
2648
|
-
function readCodexContextUsage(sessionId: string): { used: number; window: number } | null {
|
|
2649
|
-
// Codex transcripts: ~/.codex/sessions/YYYY/MM/DD/rollout-<TS>-<sessionId>.jsonl
|
|
2650
|
-
// On WSL the active install often lives on the Windows side under
|
|
2651
|
-
// /mnt/c/Users/<user>/.codex/sessions/; both are searched.
|
|
2652
|
-
const path = findCodexTranscript(sessionId);
|
|
2653
|
-
if (!path) return null;
|
|
2654
|
-
let raw: string;
|
|
2655
|
-
try {
|
|
2656
|
-
raw = readFileSync(path, "utf8");
|
|
2657
|
-
} catch {
|
|
2658
|
-
return null;
|
|
2659
|
-
}
|
|
2660
|
-
const lines = raw.split("\n");
|
|
2661
|
-
// Walk backwards: most recent token_count event wins.
|
|
2662
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2663
|
-
const line = lines[i].trim();
|
|
2664
|
-
if (!line) continue;
|
|
2665
|
-
try {
|
|
2666
|
-
const entry = JSON.parse(line);
|
|
2667
|
-
if (entry?.type === "event_msg" && entry?.payload?.type === "token_count") {
|
|
2668
|
-
const info = entry.payload.info;
|
|
2669
|
-
const used = info?.last_token_usage?.input_tokens;
|
|
2670
|
-
const window = info?.model_context_window;
|
|
2671
|
-
if (typeof used === "number" && typeof window === "number" && window > 0) {
|
|
2672
|
-
return { used, window };
|
|
2673
|
-
}
|
|
2674
|
-
}
|
|
2675
|
-
} catch {
|
|
2676
|
-
// skip malformed line
|
|
2677
|
-
}
|
|
2678
|
-
}
|
|
2679
|
-
return null;
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2682
|
-
function findCodexTranscript(sessionId: string): string | null {
|
|
2683
|
-
// Candidate roots in priority order.
|
|
2684
|
-
const homeRoot = resolve(homedir(), ".codex", "sessions");
|
|
2685
|
-
const wslRoots: string[] = [];
|
|
2686
|
-
try {
|
|
2687
|
-
if (existsSync("/mnt/c/Users")) {
|
|
2688
|
-
for (const entry of readdirSync("/mnt/c/Users")) {
|
|
2689
|
-
wslRoots.push(`/mnt/c/Users/${entry}/.codex/sessions`);
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
} catch {
|
|
2693
|
-
// ignore
|
|
2694
|
-
}
|
|
2695
|
-
const roots = [homeRoot, ...wslRoots];
|
|
2696
|
-
const suffix = `-${sessionId}.jsonl`;
|
|
2697
|
-
for (const root of roots) {
|
|
2698
|
-
if (!existsSync(root)) continue;
|
|
2699
|
-
try {
|
|
2700
|
-
// Recursive scan: sessions are partitioned by YYYY/MM/DD/, so depth
|
|
2701
|
-
// is bounded at 3 + one file per session. Cheap enough at status time.
|
|
2702
|
-
const stack: string[] = [root];
|
|
2703
|
-
while (stack.length) {
|
|
2704
|
-
const dir = stack.pop()!;
|
|
2705
|
-
let entries: string[];
|
|
2706
|
-
try {
|
|
2707
|
-
entries = readdirSync(dir);
|
|
2708
|
-
} catch {
|
|
2709
|
-
continue;
|
|
2710
|
-
}
|
|
2711
|
-
for (const name of entries) {
|
|
2712
|
-
const full = `${dir}/${name}`;
|
|
2713
|
-
try {
|
|
2714
|
-
const stat = statSync(full);
|
|
2715
|
-
if (stat.isDirectory()) {
|
|
2716
|
-
stack.push(full);
|
|
2717
|
-
} else if (name.endsWith(suffix)) {
|
|
2718
|
-
return full;
|
|
2719
|
-
}
|
|
2720
|
-
} catch {
|
|
2721
|
-
// ignore
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
}
|
|
2725
|
-
} catch {
|
|
2726
|
-
// ignore root scan failures
|
|
2727
|
-
}
|
|
2609
|
+
// Cursor stores a first-party percentage in workspaceStorage. The shared
|
|
2610
|
+
// runtime reader returns it without fabricating a token denominator.
|
|
2611
|
+
if (platform === "codex") return readRuntimeContextUsage("codex", sessionId);
|
|
2612
|
+
if (platform === "cursor") {
|
|
2613
|
+
const telemetry = readRuntimeContextTelemetry({
|
|
2614
|
+
adapter: "cursor",
|
|
2615
|
+
session_id: sessionId,
|
|
2616
|
+
mode: "status",
|
|
2617
|
+
});
|
|
2618
|
+
return telemetry.state === "observed" && "used_percent" in telemetry
|
|
2619
|
+
? { usedPercent: telemetry.used_percent, percentOnly: true }
|
|
2620
|
+
: null;
|
|
2728
2621
|
}
|
|
2729
|
-
return
|
|
2622
|
+
return readRuntimeContextUsage("claude-code", sessionId);
|
|
2730
2623
|
}
|
|
2731
2624
|
|
|
2732
2625
|
function formatLocalTime(d: Date): string {
|
|
@@ -3126,9 +3019,9 @@ interface HealthReport {
|
|
|
3126
3019
|
lines: number;
|
|
3127
3020
|
cursor_backlog: number;
|
|
3128
3021
|
};
|
|
3129
|
-
//
|
|
3130
|
-
//
|
|
3131
|
-
//
|
|
3022
|
+
// Heartbeat cache files present in active/ but stale or malformed. These are
|
|
3023
|
+
// files the sweep is not catching; a parseable old heartbeat is stale, not
|
|
3024
|
+
// evidence of an epoch-like or corrupt timestamp.
|
|
3132
3025
|
zombies: {
|
|
3133
3026
|
count: number;
|
|
3134
3027
|
samples: string[];
|
|
@@ -3136,6 +3029,15 @@ interface HealthReport {
|
|
|
3136
3029
|
// V3 event-ledger producer health: open tool spans, pending finalization
|
|
3137
3030
|
// requests, intake/diagnostics spool depth, span-count pressure.
|
|
3138
3031
|
event_ledger: EventLedgerHealthV3;
|
|
3032
|
+
// Stop-hook remediation cap exhaustions in the window: sessions whose
|
|
3033
|
+
// end-of-turn evidence never landed, so the hook stopped bouncing them and
|
|
3034
|
+
// let the turn end unenforced. Each one is a session that could not comply —
|
|
3035
|
+
// the signal that used to be visible only as a repeating notification sound.
|
|
3036
|
+
stop_remediation: {
|
|
3037
|
+
total: number;
|
|
3038
|
+
latest_at: string | null;
|
|
3039
|
+
sessions: string[];
|
|
3040
|
+
};
|
|
3139
3041
|
anomalies: string[];
|
|
3140
3042
|
}
|
|
3141
3043
|
|
|
@@ -3173,12 +3075,21 @@ export type EventLedgerHealthV3 =
|
|
|
3173
3075
|
groups: Array<{ adapter: string; session_hash: string; count: number }>;
|
|
3174
3076
|
};
|
|
3175
3077
|
diagnostics_spool: {
|
|
3078
|
+
/** Logical occurrences: loose files plus coalesced summary counts. */
|
|
3176
3079
|
total: number;
|
|
3080
|
+
/** Physical loose diagnostic files. */
|
|
3081
|
+
loose_total: number;
|
|
3082
|
+
/** Occurrences coalesced into summaries instead of loose files. */
|
|
3083
|
+
summarized_total: number;
|
|
3084
|
+
/** Physical summary files in `diagnostic-summaries/`. */
|
|
3085
|
+
summary_files: number;
|
|
3177
3086
|
last_24h: number;
|
|
3178
3087
|
last_1h: number;
|
|
3179
3088
|
latest_at: string | null;
|
|
3180
3089
|
by_category: Record<string, { total: number; last_24h: number }>;
|
|
3181
3090
|
recent_by_category: Record<string, { last_1h: number; latest_at: string | null }>;
|
|
3091
|
+
/** Times the summary gate failed open (loose write proceeded). */
|
|
3092
|
+
mitigation_fail_open: number;
|
|
3182
3093
|
};
|
|
3183
3094
|
span_pressure: Array<{ instance_id: string; generation_id: string; span_count: number }>;
|
|
3184
3095
|
collection_errors: string[];
|
|
@@ -3318,6 +3229,47 @@ export function collectEventLedgerHealthV3(root: string, nowMs = Date.now()): Ev
|
|
|
3318
3229
|
collectionErrors.push(`diagnostics spool unreadable: ${errorText(error)}`);
|
|
3319
3230
|
}
|
|
3320
3231
|
|
|
3232
|
+
// 4b) Coalesced diagnostic summaries: occurrences the producer bounded into
|
|
3233
|
+
// `diagnostic-summaries/` instead of loose files. Counted into the logical
|
|
3234
|
+
// totals so mitigation can never make an active fault look healthy; the
|
|
3235
|
+
// physical loose file count stays visible separately.
|
|
3236
|
+
let summarizedTotal = 0;
|
|
3237
|
+
let summaryFiles = 0;
|
|
3238
|
+
let mitigationFailOpen = 0;
|
|
3239
|
+
try {
|
|
3240
|
+
const listing = listDiagnosticSummariesV3(root);
|
|
3241
|
+
summaryFiles = listing.file_count;
|
|
3242
|
+
mitigationFailOpen = listing.mitigation_health?.fail_open_count ?? 0;
|
|
3243
|
+
if (listing.unreadable_count > 0) {
|
|
3244
|
+
collectionErrors.push(`${listing.unreadable_count} diagnostic summary file(s) unreadable`);
|
|
3245
|
+
}
|
|
3246
|
+
const dayAgoMs = nowMs - 24 * 60 * 60 * 1000;
|
|
3247
|
+
const hourAgoMs = nowMs - 60 * 60 * 1000;
|
|
3248
|
+
for (const summary of listing.summaries) {
|
|
3249
|
+
summarizedTotal += summary.summarized_count;
|
|
3250
|
+
const entry = byCategory[summary.category] ?? { total: 0, last_24h: 0 };
|
|
3251
|
+
const recent = recentByCategory[summary.category] ?? { last_1h: 0, latest_at: null };
|
|
3252
|
+
byCategory[summary.category] = entry;
|
|
3253
|
+
recentByCategory[summary.category] = recent;
|
|
3254
|
+
entry.total += summary.summarized_count;
|
|
3255
|
+
const day = countSummarizedSinceV3([summary], dayAgoMs, undefined, nowMs);
|
|
3256
|
+
const hour = countSummarizedSinceV3([summary], hourAgoMs, undefined, nowMs);
|
|
3257
|
+
entry.last_24h += day;
|
|
3258
|
+
diagnostics24h += day;
|
|
3259
|
+
recent.last_1h += hour;
|
|
3260
|
+
diagnostics1h += hour;
|
|
3261
|
+
const lastMs = summary.last_summarized_at ? Date.parse(summary.last_summarized_at) : NaN;
|
|
3262
|
+
if (Number.isFinite(lastMs)) {
|
|
3263
|
+
latestDiagnosticMs = Math.max(latestDiagnosticMs, lastMs);
|
|
3264
|
+
if (!recent.latest_at || lastMs > Date.parse(recent.latest_at)) {
|
|
3265
|
+
recent.latest_at = new Date(lastMs).toISOString();
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
} catch (error) {
|
|
3270
|
+
collectionErrors.push(`diagnostic summaries unreadable: ${errorText(error)}`);
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3321
3273
|
return {
|
|
3322
3274
|
state: "live",
|
|
3323
3275
|
mode: control.state,
|
|
@@ -3328,7 +3280,10 @@ export function collectEventLedgerHealthV3(root: string, nowMs = Date.now()): Ev
|
|
|
3328
3280
|
pending_finalizations: pending,
|
|
3329
3281
|
intake_spool: { total: intakeTotal, groups: intakeGroups },
|
|
3330
3282
|
diagnostics_spool: {
|
|
3331
|
-
total: diagnosticsTotal,
|
|
3283
|
+
total: diagnosticsTotal + summarizedTotal,
|
|
3284
|
+
loose_total: diagnosticsTotal,
|
|
3285
|
+
summarized_total: summarizedTotal,
|
|
3286
|
+
summary_files: summaryFiles,
|
|
3332
3287
|
last_24h: diagnostics24h,
|
|
3333
3288
|
last_1h: diagnostics1h,
|
|
3334
3289
|
latest_at: Number.isFinite(latestDiagnosticMs)
|
|
@@ -3336,12 +3291,57 @@ export function collectEventLedgerHealthV3(root: string, nowMs = Date.now()): Ev
|
|
|
3336
3291
|
: null,
|
|
3337
3292
|
by_category: byCategory,
|
|
3338
3293
|
recent_by_category: recentByCategory,
|
|
3294
|
+
mitigation_fail_open: mitigationFailOpen,
|
|
3339
3295
|
},
|
|
3340
3296
|
span_pressure: spanPressure,
|
|
3341
3297
|
collection_errors: collectionErrors,
|
|
3342
3298
|
};
|
|
3343
3299
|
}
|
|
3344
3300
|
|
|
3301
|
+
/**
|
|
3302
|
+
* Tally stop-hook remediation-cap exhaustions in the window from
|
|
3303
|
+
* `.harnery/debug/agent-hook.ndjson` (rows with
|
|
3304
|
+
* `skipped: "stop-remediation-cap-exhausted"`). Each one is a session whose
|
|
3305
|
+
* end-of-turn evidence never landed, so the Stop hook gave up bouncing it and
|
|
3306
|
+
* let the turn end unenforced — a compliance gap an operator should see
|
|
3307
|
+
* without grepping the debug ledger.
|
|
3308
|
+
*/
|
|
3309
|
+
export function readStopRemediationExhaustions(
|
|
3310
|
+
root: string,
|
|
3311
|
+
cutoffMs: number,
|
|
3312
|
+
): { total: number; latestAt: string | null; sessions: string[] } {
|
|
3313
|
+
const p = resolve(root, ".harnery", "debug", "agent-hook.ndjson");
|
|
3314
|
+
if (!existsSync(p)) return { total: 0, latestAt: null, sessions: [] };
|
|
3315
|
+
let raw: string;
|
|
3316
|
+
try {
|
|
3317
|
+
raw = readFileSync(p, "utf8");
|
|
3318
|
+
} catch {
|
|
3319
|
+
return { total: 0, latestAt: null, sessions: [] };
|
|
3320
|
+
}
|
|
3321
|
+
let total = 0;
|
|
3322
|
+
let latestMs = Number.NEGATIVE_INFINITY;
|
|
3323
|
+
const sessions = new Set<string>();
|
|
3324
|
+
for (const line of raw.split("\n")) {
|
|
3325
|
+
if (!line.includes("stop-remediation-cap-exhausted")) continue;
|
|
3326
|
+
try {
|
|
3327
|
+
const e = JSON.parse(line) as { ts?: string; skipped?: string; session_id?: string };
|
|
3328
|
+
if (e.skipped !== "stop-remediation-cap-exhausted") continue;
|
|
3329
|
+
const tsMs = e.ts ? Date.parse(e.ts) : Number.NaN;
|
|
3330
|
+
if (!Number.isFinite(tsMs) || tsMs < cutoffMs) continue;
|
|
3331
|
+
total++;
|
|
3332
|
+
latestMs = Math.max(latestMs, tsMs);
|
|
3333
|
+
if (e.session_id && sessions.size < 5) sessions.add(e.session_id);
|
|
3334
|
+
} catch {
|
|
3335
|
+
/* skip malformed */
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
return {
|
|
3339
|
+
total,
|
|
3340
|
+
latestAt: Number.isFinite(latestMs) ? new Date(latestMs).toISOString() : null,
|
|
3341
|
+
sessions: [...sessions],
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
3345
|
/** Tally agent-hook failures (.harnery/debug/agent-hook.errors.ndjson) in the
|
|
3346
3346
|
* window by exact error and phase, while separating current-hour failures from
|
|
3347
3347
|
* historical rows. Each line is {ts, error, phase, ...}. */
|
|
@@ -3797,13 +3797,15 @@ type ActiveHealthHeartbeat = {
|
|
|
3797
3797
|
export function collectActiveAgentHealth(
|
|
3798
3798
|
root: string,
|
|
3799
3799
|
nowMs = Date.now(),
|
|
3800
|
+
readRows: (root: string) => ActiveHealthHeartbeat[] = readLiveCoordinationRows,
|
|
3801
|
+
freshnessSeconds = freshnessCutoffSecs(),
|
|
3800
3802
|
): ActiveAgentHealthSummary {
|
|
3801
3803
|
const source: ActiveAgentHealthSummary["source"] = "event-ledger-v3";
|
|
3802
3804
|
let heartbeats: ActiveHealthHeartbeat[] = [];
|
|
3803
3805
|
try {
|
|
3804
3806
|
const control = readEventV3ControlState(root);
|
|
3805
3807
|
if (control.state === "candidate" || control.state === "active") {
|
|
3806
|
-
heartbeats =
|
|
3808
|
+
heartbeats = readRows(root);
|
|
3807
3809
|
}
|
|
3808
3810
|
} catch {
|
|
3809
3811
|
// The event-ledger section reports the authority read failure. Do not
|
|
@@ -3829,7 +3831,7 @@ export function collectActiveAgentHealth(
|
|
|
3829
3831
|
const ageMs = Number.isFinite(lastObservedMs)
|
|
3830
3832
|
? nowMs - lastObservedMs
|
|
3831
3833
|
: Number.POSITIVE_INFINITY;
|
|
3832
|
-
if (ageMs >
|
|
3834
|
+
if (ageMs > freshnessSeconds * 1000) stale += 1;
|
|
3833
3835
|
}
|
|
3834
3836
|
return {
|
|
3835
3837
|
source,
|
|
@@ -3841,6 +3843,22 @@ export function collectActiveAgentHealth(
|
|
|
3841
3843
|
};
|
|
3842
3844
|
}
|
|
3843
3845
|
|
|
3846
|
+
export type HeartbeatCacheIssue = "no-name" | "invalid-heartbeat" | "stale-age";
|
|
3847
|
+
|
|
3848
|
+
/** Classify a parseable heartbeat cache file without conflating age with corruption. */
|
|
3849
|
+
export function classifyHeartbeatCacheIssue(
|
|
3850
|
+
heartbeat: Pick<Heartbeat, "name" | "last_heartbeat">,
|
|
3851
|
+
nowMs: number,
|
|
3852
|
+
staleAgeMs: number,
|
|
3853
|
+
): HeartbeatCacheIssue | null {
|
|
3854
|
+
if (!heartbeat.name || heartbeat.name === "unknown") return "no-name";
|
|
3855
|
+
const lastHeartbeatMs = heartbeat.last_heartbeat
|
|
3856
|
+
? Date.parse(heartbeat.last_heartbeat)
|
|
3857
|
+
: Number.NaN;
|
|
3858
|
+
if (!Number.isFinite(lastHeartbeatMs)) return "invalid-heartbeat";
|
|
3859
|
+
return nowMs - lastHeartbeatMs > staleAgeMs ? "stale-age" : null;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3844
3862
|
function runHealth(opts: { since: string; json?: boolean }): void {
|
|
3845
3863
|
if (opts.json) emit.config({ format: "json" });
|
|
3846
3864
|
|
|
@@ -3902,6 +3920,7 @@ function runHealth(opts: { since: string; json?: boolean }): void {
|
|
|
3902
3920
|
}
|
|
3903
3921
|
|
|
3904
3922
|
const hookErrors = readHookErrors(root, cutoffMs, nowMs);
|
|
3923
|
+
const stopRemediation = readStopRemediationExhaustions(root, cutoffMs);
|
|
3905
3924
|
const stream = readStreamStats(root);
|
|
3906
3925
|
const eventLedger = collectEventLedgerHealthV3(root);
|
|
3907
3926
|
|
|
@@ -3924,12 +3943,12 @@ function runHealth(opts: { since: string; json?: boolean }): void {
|
|
|
3924
3943
|
.map(([agent, count]) => ({ agent, count }));
|
|
3925
3944
|
|
|
3926
3945
|
const activeAgents = collectActiveAgentHealth(root, nowMs);
|
|
3927
|
-
//
|
|
3928
|
-
//
|
|
3929
|
-
//
|
|
3946
|
+
// Heartbeat cache issues: files in active/ that are unparseable, nameless,
|
|
3947
|
+
// invalidly timestamped, or stale. Old parseable timestamps are ordinary
|
|
3948
|
+
// staleness, not evidence of epoch-like timestamp corruption.
|
|
3930
3949
|
let zombieCount = 0;
|
|
3931
3950
|
const zombieSamples: string[] = [];
|
|
3932
|
-
const
|
|
3951
|
+
const STALE_HEARTBEAT_AGE_MS = 24 * 60 * 60 * 1000;
|
|
3933
3952
|
if (existsSync(activeDir)) {
|
|
3934
3953
|
for (const file of readdirSync(activeDir)) {
|
|
3935
3954
|
if (!file.endsWith(".json")) continue;
|
|
@@ -3946,15 +3965,11 @@ function runHealth(opts: { since: string; json?: boolean }): void {
|
|
|
3946
3965
|
zombieSamples.push(`${idFromFile.slice(0, 12)} (unparseable/no-id)`);
|
|
3947
3966
|
continue;
|
|
3948
3967
|
}
|
|
3949
|
-
const
|
|
3950
|
-
|
|
3951
|
-
// Zombie heuristics on a parseable heartbeat: no name, or an age so large
|
|
3952
|
-
// it can only be a broken/epoch timestamp (a real agent would have healed).
|
|
3953
|
-
if (!hb.name || hb.name === "unknown" || ageMs > ABSURD_AGE_MS) {
|
|
3968
|
+
const cacheIssue = classifyHeartbeatCacheIssue(hb, nowMs, STALE_HEARTBEAT_AGE_MS);
|
|
3969
|
+
if (cacheIssue) {
|
|
3954
3970
|
zombieCount++;
|
|
3955
3971
|
if (zombieSamples.length < 5) {
|
|
3956
|
-
|
|
3957
|
-
zombieSamples.push(`${idFromFile.slice(0, 12)} (${why})`);
|
|
3972
|
+
zombieSamples.push(`${idFromFile.slice(0, 12)} (${cacheIssue})`);
|
|
3958
3973
|
}
|
|
3959
3974
|
}
|
|
3960
3975
|
}
|
|
@@ -4018,7 +4033,16 @@ function runHealth(opts: { since: string; json?: boolean }): void {
|
|
|
4018
4033
|
}
|
|
4019
4034
|
if (zombieCount > 0) {
|
|
4020
4035
|
anomalies.push(
|
|
4021
|
-
`${zombieCount}
|
|
4036
|
+
`${zombieCount} heartbeat cache issue(s) in active/ (${zombieSamples.join(", ")}); stale or malformed files the sweep isn't reaping`,
|
|
4037
|
+
);
|
|
4038
|
+
}
|
|
4039
|
+
if (stopRemediation.total > 0) {
|
|
4040
|
+
const who =
|
|
4041
|
+
stopRemediation.sessions.length > 0
|
|
4042
|
+
? ` (sessions: ${stopRemediation.sessions.map((s) => s.slice(0, 8)).join(", ")})`
|
|
4043
|
+
: "";
|
|
4044
|
+
anomalies.push(
|
|
4045
|
+
`stop-hook remediation cap exhausted ${stopRemediation.total}x in ${opts.since}${who}; these sessions ended turns without the end-of-turn ritual — check why their evidence never lands`,
|
|
4022
4046
|
);
|
|
4023
4047
|
}
|
|
4024
4048
|
if (eventLedger.state === "live") {
|
|
@@ -4089,6 +4113,11 @@ function runHealth(opts: { since: string; json?: boolean }): void {
|
|
|
4089
4113
|
stream,
|
|
4090
4114
|
zombies: { count: zombieCount, samples: zombieSamples },
|
|
4091
4115
|
event_ledger: eventLedger,
|
|
4116
|
+
stop_remediation: {
|
|
4117
|
+
total: stopRemediation.total,
|
|
4118
|
+
latest_at: stopRemediation.latestAt,
|
|
4119
|
+
sessions: stopRemediation.sessions,
|
|
4120
|
+
},
|
|
4092
4121
|
anomalies,
|
|
4093
4122
|
};
|
|
4094
4123
|
|
|
@@ -4160,7 +4189,7 @@ function renderHealthBox(report: HealthReport): void {
|
|
|
4160
4189
|
`open spans ${ledger.open_spans.total}${orphanGenerations > 0 ? ` (${orphanGenerations} gen turn-closed)` : ""}`,
|
|
4161
4190
|
`pending ends ${ledger.pending_finalizations.length}${pendingAges ? ` (${pendingAges})` : ""}`,
|
|
4162
4191
|
`intake ${ledger.intake_spool.total}`,
|
|
4163
|
-
`diagnostics ${ledger.diagnostics_spool.total} (${ledger.diagnostics_spool.last_1h} in 1h / ${ledger.diagnostics_spool.last_24h} in 24h)`,
|
|
4192
|
+
`diagnostics ${ledger.diagnostics_spool.total}${ledger.diagnostics_spool.summarized_total > 0 ? ` (${ledger.diagnostics_spool.loose_total} loose + ${ledger.diagnostics_spool.summarized_total} summarized)` : ""} (${ledger.diagnostics_spool.last_1h} in 1h / ${ledger.diagnostics_spool.last_24h} in 24h)`,
|
|
4164
4193
|
].join(" · ");
|
|
4165
4194
|
}
|
|
4166
4195
|
|
|
@@ -4181,7 +4210,7 @@ function renderHealthBox(report: HealthReport): void {
|
|
|
4181
4210
|
["stream", streamStr],
|
|
4182
4211
|
["event ledger", ledgerStr],
|
|
4183
4212
|
[
|
|
4184
|
-
"
|
|
4213
|
+
"cache issues",
|
|
4185
4214
|
report.zombies.count === 0
|
|
4186
4215
|
? "0"
|
|
4187
4216
|
: `${report.zombies.count} (${report.zombies.samples.join(", ")})`,
|
|
@@ -4617,10 +4646,18 @@ function runPing(name: string, message: string, opts: { json?: boolean }): void
|
|
|
4617
4646
|
}
|
|
4618
4647
|
const myOwner = resolveOwner();
|
|
4619
4648
|
if (!myOwner) {
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4649
|
+
const root = monorepoRoot();
|
|
4650
|
+
emit.error(
|
|
4651
|
+
root
|
|
4652
|
+
? sessionResolutionFailure(
|
|
4653
|
+
root,
|
|
4654
|
+
"not in an agent session; ppid walk found no pid-map entry",
|
|
4655
|
+
)
|
|
4656
|
+
: {
|
|
4657
|
+
code: "no_pidmap_entry",
|
|
4658
|
+
message: "not in an agent session; ppid walk found no pid-map entry",
|
|
4659
|
+
},
|
|
4660
|
+
);
|
|
4624
4661
|
process.exit(1);
|
|
4625
4662
|
}
|
|
4626
4663
|
const peerOwner = resolveOwnerByName(name);
|
|
@@ -4787,21 +4824,22 @@ function emitWaitResult(
|
|
|
4787
4824
|
}
|
|
4788
4825
|
|
|
4789
4826
|
function runHeal(opts: {
|
|
4790
|
-
owner
|
|
4791
|
-
kind
|
|
4827
|
+
owner?: string;
|
|
4828
|
+
kind?: string;
|
|
4792
4829
|
sessionId?: string;
|
|
4793
4830
|
adapter?: string;
|
|
4794
4831
|
pid?: string;
|
|
4832
|
+
quarantineTransaction?: string;
|
|
4833
|
+
approvalRecordId?: string;
|
|
4834
|
+
yes?: boolean;
|
|
4795
4835
|
json?: boolean;
|
|
4796
4836
|
}): void {
|
|
4797
4837
|
if (opts.json) emit.config({ format: "json" });
|
|
4798
4838
|
|
|
4799
|
-
const
|
|
4800
|
-
const
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
process.exit(1);
|
|
4804
|
-
}
|
|
4839
|
+
const requestedOwner = opts.owner?.trim() ?? "";
|
|
4840
|
+
const quarantineTransaction = opts.quarantineTransaction?.trim() ?? "";
|
|
4841
|
+
const approvalRecordId = opts.approvalRecordId?.trim() ?? "";
|
|
4842
|
+
const kind = opts.kind?.trim() || "cache";
|
|
4805
4843
|
if (kind !== "pidmap" && kind !== "cache") {
|
|
4806
4844
|
emit.error({
|
|
4807
4845
|
code: "bad_kind",
|
|
@@ -4809,6 +4847,30 @@ function runHeal(opts: {
|
|
|
4809
4847
|
});
|
|
4810
4848
|
process.exit(1);
|
|
4811
4849
|
}
|
|
4850
|
+
if (quarantineTransaction) {
|
|
4851
|
+
if (opts.kind || opts.pid) {
|
|
4852
|
+
emit.error({
|
|
4853
|
+
code: "conflicting_recovery_options",
|
|
4854
|
+
message: "--quarantine-transaction cannot be combined with --kind or --pid",
|
|
4855
|
+
});
|
|
4856
|
+
process.exit(1);
|
|
4857
|
+
}
|
|
4858
|
+
if (!opts.yes || !approvalRecordId) {
|
|
4859
|
+
emit.error({
|
|
4860
|
+
code: "transaction_quarantine_confirmation_required",
|
|
4861
|
+
message:
|
|
4862
|
+
"--quarantine-transaction requires --approval-record-id <id> and --yes; " +
|
|
4863
|
+
"the command abandons one uncommitted authority mutation while preserving its record",
|
|
4864
|
+
});
|
|
4865
|
+
process.exit(1);
|
|
4866
|
+
}
|
|
4867
|
+
} else if (approvalRecordId || opts.yes) {
|
|
4868
|
+
emit.error({
|
|
4869
|
+
code: "transaction_quarantine_target_required",
|
|
4870
|
+
message: "--approval-record-id and --yes require --quarantine-transaction <id>",
|
|
4871
|
+
});
|
|
4872
|
+
process.exit(1);
|
|
4873
|
+
}
|
|
4812
4874
|
if (
|
|
4813
4875
|
opts.adapter &&
|
|
4814
4876
|
opts.adapter !== "claude-code" &&
|
|
@@ -4831,7 +4893,52 @@ function runHeal(opts: {
|
|
|
4831
4893
|
process.exit(1);
|
|
4832
4894
|
}
|
|
4833
4895
|
|
|
4834
|
-
const
|
|
4896
|
+
const currentSessionRepair =
|
|
4897
|
+
kind === "cache" &&
|
|
4898
|
+
opts.owner === undefined &&
|
|
4899
|
+
opts.sessionId === undefined &&
|
|
4900
|
+
opts.adapter === undefined &&
|
|
4901
|
+
!quarantineTransaction;
|
|
4902
|
+
|
|
4903
|
+
const ownerResolution = resolveOwnerWithSource();
|
|
4904
|
+
const bootstrapIdentity = currentSessionRepair ? commandSessionBootstrap() : null;
|
|
4905
|
+
const resolvedCurrentOwner =
|
|
4906
|
+
bootstrapIdentity &&
|
|
4907
|
+
(ownerResolution.source === "none" || ownerResolution.source === "active_singleton")
|
|
4908
|
+
? bootstrapIdentity.sessionId
|
|
4909
|
+
: ownerResolution.owner;
|
|
4910
|
+
const owner = requestedOwner || resolvedCurrentOwner || sessionIdentityFromEnv() || "";
|
|
4911
|
+
if (!owner) {
|
|
4912
|
+
emit.error({
|
|
4913
|
+
code: "session_identity_missing",
|
|
4914
|
+
message:
|
|
4915
|
+
`could not resolve the current session; run ${resolveBinName()} doctor, or pass ` +
|
|
4916
|
+
"--owner <instance-id> --session-id <native-session-id> --adapter <adapter>",
|
|
4917
|
+
});
|
|
4918
|
+
process.exit(1);
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
const currentRow = readLiveCoordinationRow(root, owner);
|
|
4922
|
+
const inferredAdapter = normalizeAdapter(
|
|
4923
|
+
opts.adapter?.trim() || currentRow?.platform || commandSessionBootstrap()?.adapter,
|
|
4924
|
+
);
|
|
4925
|
+
const sessionId =
|
|
4926
|
+
opts.sessionId?.trim() ||
|
|
4927
|
+
(currentSessionRepair ? sessionIdentityFromEnv() : null) ||
|
|
4928
|
+
nativeSessionIdentity(currentRow, owner);
|
|
4929
|
+
|
|
4930
|
+
if (kind === "cache") {
|
|
4931
|
+
const refusal = cacheHealAuthorityRefusal(root, owner, sessionId, inferredAdapter, currentRow);
|
|
4932
|
+
if (refusal) {
|
|
4933
|
+
emitHealFailure(refusal.reason, refusal.message);
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
|
|
4937
|
+
const action = quarantineTransaction
|
|
4938
|
+
? "quarantine-authority-transaction"
|
|
4939
|
+
: kind === "pidmap"
|
|
4940
|
+
? "heal-pidmap"
|
|
4941
|
+
: "repair-coordination-cache";
|
|
4835
4942
|
|
|
4836
4943
|
// Refuse to materialize a V3 cache at a truncated owner id.
|
|
4837
4944
|
//
|
|
@@ -4874,23 +4981,72 @@ function runHeal(opts: {
|
|
|
4874
4981
|
// heal-pidmap <instance_id> [<pid>]
|
|
4875
4982
|
// repair-coordination-cache <instance_id> [<session_id>]
|
|
4876
4983
|
const helperArgs: string[] = [action, owner];
|
|
4877
|
-
if (
|
|
4878
|
-
|
|
4879
|
-
|
|
4984
|
+
if (quarantineTransaction) {
|
|
4985
|
+
helperArgs.push(
|
|
4986
|
+
quarantineTransaction,
|
|
4987
|
+
approvalRecordId,
|
|
4988
|
+
sessionId,
|
|
4989
|
+
`--adapter=${inferredAdapter}`,
|
|
4990
|
+
);
|
|
4991
|
+
} else {
|
|
4992
|
+
if (kind === "pidmap" && opts.pid) helperArgs.push(opts.pid);
|
|
4993
|
+
if (kind === "cache") helperArgs.push(sessionId, `--adapter=${inferredAdapter}`);
|
|
4994
|
+
}
|
|
4880
4995
|
|
|
4881
4996
|
// Both recovery actions are handled by the bundled agent-coord binary.
|
|
4882
4997
|
const helper = agentCoordOrExit(root);
|
|
4883
|
-
|
|
4998
|
+
let proc = spawnSync(helper, helperArgs, {
|
|
4884
4999
|
encoding: "utf8",
|
|
4885
5000
|
...coordHelperOpts(root),
|
|
4886
5001
|
});
|
|
4887
5002
|
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
5003
|
+
let bootstrapState: "created" | "reused" | undefined;
|
|
5004
|
+
if (
|
|
5005
|
+
proc.status !== 0 &&
|
|
5006
|
+
currentSessionRepair &&
|
|
5007
|
+
missingAuthorityForCurrentSessionBootstrap(root, owner, sessionId)
|
|
5008
|
+
) {
|
|
5009
|
+
const bootstrap = commandSessionBootstrap();
|
|
5010
|
+
if (
|
|
5011
|
+
!bootstrap ||
|
|
5012
|
+
bootstrap.sessionId !== sessionId ||
|
|
5013
|
+
bootstrap.sessionId !== owner ||
|
|
5014
|
+
bootstrap.adapter !== inferredAdapter
|
|
5015
|
+
) {
|
|
5016
|
+
emitHealFailure(
|
|
5017
|
+
"native_identity_unverified",
|
|
5018
|
+
"the current adapter did not provide one matching native session id and adapter",
|
|
5019
|
+
);
|
|
5020
|
+
}
|
|
5021
|
+
try {
|
|
5022
|
+
const bootstrapped = bootstrapLiveCoordinationAuthorityV3({
|
|
5023
|
+
coordRoot: root,
|
|
5024
|
+
owner,
|
|
5025
|
+
nativeSessionId: bootstrap.sessionId,
|
|
5026
|
+
adapter: bootstrap.adapter,
|
|
5027
|
+
});
|
|
5028
|
+
bootstrapState = bootstrapped.state;
|
|
5029
|
+
} catch (error) {
|
|
5030
|
+
const reason =
|
|
5031
|
+
error instanceof LiveCoordinationAuthorityV3Error
|
|
5032
|
+
? error.reason
|
|
5033
|
+
: "authority_bootstrap_failed";
|
|
5034
|
+
emitHealFailure(
|
|
5035
|
+
reason,
|
|
5036
|
+
"the current native session could not establish authority; no fallback cache was accepted",
|
|
5037
|
+
);
|
|
5038
|
+
}
|
|
5039
|
+
proc = spawnSync(helper, helperArgs, {
|
|
5040
|
+
encoding: "utf8",
|
|
5041
|
+
...coordHelperOpts(root),
|
|
4892
5042
|
});
|
|
4893
|
-
|
|
5043
|
+
}
|
|
5044
|
+
|
|
5045
|
+
if (proc.status !== 0) {
|
|
5046
|
+
emitHealFailure(
|
|
5047
|
+
healFailureReason(root, owner, sessionId, inferredAdapter),
|
|
5048
|
+
spawnFailureMessage(proc, `agent-coord ${action}`),
|
|
5049
|
+
);
|
|
4894
5050
|
}
|
|
4895
5051
|
|
|
4896
5052
|
// Read the derived cache to surface post-action state.
|
|
@@ -4901,8 +5057,9 @@ function runHeal(opts: {
|
|
|
4901
5057
|
after = null;
|
|
4902
5058
|
}
|
|
4903
5059
|
|
|
4904
|
-
const outcome =
|
|
4905
|
-
|
|
5060
|
+
const outcome = quarantineTransaction
|
|
5061
|
+
? "transaction_quarantined"
|
|
5062
|
+
: kind === "pidmap"
|
|
4906
5063
|
? proc.status === 0
|
|
4907
5064
|
? "ok"
|
|
4908
5065
|
: "failed"
|
|
@@ -4917,10 +5074,15 @@ function runHeal(opts: {
|
|
|
4917
5074
|
action,
|
|
4918
5075
|
outcome,
|
|
4919
5076
|
after,
|
|
5077
|
+
...(quarantineTransaction ? { recovery: JSON.parse(proc.stdout.trim()) as unknown } : {}),
|
|
4920
5078
|
},
|
|
4921
5079
|
],
|
|
4922
5080
|
meta: {
|
|
4923
5081
|
kind,
|
|
5082
|
+
automatic: currentSessionRepair && !quarantineTransaction,
|
|
5083
|
+
authority: "event-ledger-v3",
|
|
5084
|
+
adapter: kind === "cache" ? inferredAdapter : undefined,
|
|
5085
|
+
bootstrap: bootstrapState,
|
|
4924
5086
|
// The path actually spawned, not a guess at the layout: the helper is
|
|
4925
5087
|
// resolved from harnery's own package location, which differs between a
|
|
4926
5088
|
// submodule, an installed dependency, and a standalone checkout.
|
|
@@ -4928,10 +5090,18 @@ function runHeal(opts: {
|
|
|
4928
5090
|
},
|
|
4929
5091
|
});
|
|
4930
5092
|
if (!opts.json) {
|
|
4931
|
-
if (
|
|
5093
|
+
if (quarantineTransaction) {
|
|
5094
|
+
emit.text(
|
|
5095
|
+
`coordination recovered: ${quarantineTransaction} quarantined with approval ${approvalRecordId}; ` +
|
|
5096
|
+
`cache ${after ? "present" : "absent"} (${inferredAdapter})\n`,
|
|
5097
|
+
);
|
|
5098
|
+
} else if (kind === "pidmap") {
|
|
4932
5099
|
emit.text(`agent-coord ${action} ok\n`);
|
|
4933
5100
|
} else {
|
|
4934
|
-
emit.text(
|
|
5101
|
+
emit.text(
|
|
5102
|
+
`coordination repaired: V3 authority live; ${currentSessionRepair ? "current-session " : ""}` +
|
|
5103
|
+
`cache ${after ? "present" : "absent"} (${inferredAdapter})\n`,
|
|
5104
|
+
);
|
|
4935
5105
|
}
|
|
4936
5106
|
}
|
|
4937
5107
|
|
|
@@ -4942,6 +5112,98 @@ function runHeal(opts: {
|
|
|
4942
5112
|
// unconditionally on every `harn agents heal`, which over-counted no-op heals.)
|
|
4943
5113
|
}
|
|
4944
5114
|
|
|
5115
|
+
type HealAuthorityRefusal = {
|
|
5116
|
+
reason: "adapter_mismatch" | "owner_mismatch" | "session_mismatch" | "terminal_generation";
|
|
5117
|
+
message: string;
|
|
5118
|
+
};
|
|
5119
|
+
|
|
5120
|
+
function cacheHealAuthorityRefusal(
|
|
5121
|
+
root: string,
|
|
5122
|
+
owner: string,
|
|
5123
|
+
sessionId: string,
|
|
5124
|
+
adapter: "claude-code" | "cursor" | "codex",
|
|
5125
|
+
currentRow: LiveCoordinationRow | null,
|
|
5126
|
+
): HealAuthorityRefusal | null {
|
|
5127
|
+
if (currentRow && normalizeAdapter(currentRow.platform) !== adapter) {
|
|
5128
|
+
return {
|
|
5129
|
+
reason: "adapter_mismatch",
|
|
5130
|
+
message: `the live generation belongs to adapter ${normalizeAdapter(currentRow.platform)}, not ${adapter}`,
|
|
5131
|
+
};
|
|
5132
|
+
}
|
|
5133
|
+
let producer: ReturnType<typeof readHookProducerStateV3>;
|
|
5134
|
+
try {
|
|
5135
|
+
producer = readHookProducerStateV3(root, adapter, sessionId);
|
|
5136
|
+
} catch {
|
|
5137
|
+
return {
|
|
5138
|
+
reason: "session_mismatch",
|
|
5139
|
+
message: "the native session producer state could not be read safely",
|
|
5140
|
+
};
|
|
5141
|
+
}
|
|
5142
|
+
if (producer?.terminal) {
|
|
5143
|
+
return {
|
|
5144
|
+
reason: "terminal_generation",
|
|
5145
|
+
message: "the matching generation is terminal and cannot be repaired or reopened by heal",
|
|
5146
|
+
};
|
|
5147
|
+
}
|
|
5148
|
+
if (producer && producer.instance_id !== liveInstanceIdV3(owner)) {
|
|
5149
|
+
return {
|
|
5150
|
+
reason: "owner_mismatch",
|
|
5151
|
+
message: "the native session is bound to a different instance owner",
|
|
5152
|
+
};
|
|
5153
|
+
}
|
|
5154
|
+
if (currentRow && (!producer || producer.generation_id !== currentRow.v3_generation_id)) {
|
|
5155
|
+
return {
|
|
5156
|
+
reason: "session_mismatch",
|
|
5157
|
+
message: "the native session does not bind to the requested live generation",
|
|
5158
|
+
};
|
|
5159
|
+
}
|
|
5160
|
+
return null;
|
|
5161
|
+
}
|
|
5162
|
+
|
|
5163
|
+
function missingAuthorityForCurrentSessionBootstrap(
|
|
5164
|
+
root: string,
|
|
5165
|
+
owner: string,
|
|
5166
|
+
sessionId: string,
|
|
5167
|
+
): boolean {
|
|
5168
|
+
if (readLiveCoordinationRow(root, owner)) return false;
|
|
5169
|
+
try {
|
|
5170
|
+
const nativeSessionHasProducer = (["claude-code", "codex", "cursor"] as const).some(
|
|
5171
|
+
(adapter) => readHookProducerStateV3(root, adapter, sessionId) !== undefined,
|
|
5172
|
+
);
|
|
5173
|
+
if (nativeSessionHasProducer) return false;
|
|
5174
|
+
const instanceId = liveInstanceIdV3(owner);
|
|
5175
|
+
return !listHookProducerStateRecordsV3(root, { includeTerminal: true }).some(
|
|
5176
|
+
({ state }) => state.instance_id === instanceId,
|
|
5177
|
+
);
|
|
5178
|
+
} catch {
|
|
5179
|
+
return false;
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5182
|
+
|
|
5183
|
+
function healFailureReason(
|
|
5184
|
+
root: string,
|
|
5185
|
+
owner: string,
|
|
5186
|
+
sessionId: string,
|
|
5187
|
+
adapter: "claude-code" | "cursor" | "codex",
|
|
5188
|
+
): string {
|
|
5189
|
+
const refusal = cacheHealAuthorityRefusal(
|
|
5190
|
+
root,
|
|
5191
|
+
owner,
|
|
5192
|
+
sessionId,
|
|
5193
|
+
adapter,
|
|
5194
|
+
readLiveCoordinationRow(root, owner),
|
|
5195
|
+
);
|
|
5196
|
+
return refusal?.reason ?? "authority_missing";
|
|
5197
|
+
}
|
|
5198
|
+
|
|
5199
|
+
function emitHealFailure(reason: string, message: string): never {
|
|
5200
|
+
emit.error({
|
|
5201
|
+
code: "heal_failed",
|
|
5202
|
+
message: `coordination heal refused (reason=${reason}): ${message}`,
|
|
5203
|
+
});
|
|
5204
|
+
process.exit(1);
|
|
5205
|
+
}
|
|
5206
|
+
|
|
4945
5207
|
/**
|
|
4946
5208
|
* Emit one V3 council audit transition keyed to the exact durable manifest.
|
|
4947
5209
|
* Falls through silently when no live session can attest the observation.
|