harnery 0.40.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commander.js +1 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +25 -19
- package/dist/commands/browse-ai.d.ts.map +1 -1
- package/dist/commands/browse-ai.js +4 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +141 -10
- package/dist/commands/checkpoint.js +8 -12
- package/dist/commands/deinit.d.ts +1 -1
- package/dist/commands/deinit.d.ts.map +1 -1
- package/dist/commands/deinit.js +8 -4
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +2 -0
- package/dist/commands/doctor.js +10 -2
- package/dist/commands/fetch.d.ts.map +1 -1
- package/dist/commands/fetch.js +10 -0
- package/dist/commands/init.d.ts +19 -3
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +232 -104
- package/dist/commands/instructions.js +5 -4
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +2 -1
- package/dist/commands/webview.d.ts +4 -0
- package/dist/commands/webview.d.ts.map +1 -0
- package/dist/commands/webview.js +94 -0
- package/dist/core/adapter.d.ts +50 -1
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +59 -1
- package/dist/core/adapters/attestation.d.ts.map +1 -1
- package/dist/core/adapters/attestation.js +3 -2
- package/dist/core/adapters/profiles.d.ts +20 -3
- package/dist/core/adapters/profiles.d.ts.map +1 -1
- package/dist/core/adapters/profiles.js +32 -1
- package/dist/core/adapters/registry.d.ts.map +1 -1
- package/dist/core/adapters/registry.js +33 -0
- package/dist/core/agents/canonical-emit.d.ts +1 -1
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +2 -7
- package/dist/core/agents/cli.js +18 -16
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +7 -2
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +3 -9
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +2 -8
- 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 +1 -1
- package/dist/core/agents/session-events.d.ts +6 -2
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +105 -26
- package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -1
- package/dist/core/agents/session-finalization-state-v3.js +2 -1
- package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
- package/dist/core/agents/session-finalizer-v3.js +5 -4
- package/dist/core/agents/session-name-display.d.ts +28 -0
- package/dist/core/agents/session-name-display.d.ts.map +1 -1
- package/dist/core/agents/session-name-display.js +43 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +10 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +21 -2
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +1 -0
- package/dist/core/agents/state/live-coordination-writer.js +3 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +2 -10
- package/dist/core/agents/turn-changes.d.ts +17 -0
- package/dist/core/agents/turn-changes.d.ts.map +1 -0
- package/dist/core/agents/turn-changes.js +76 -0
- package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
- package/dist/core/artifacts/delivery-card.js +2 -1
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +4 -3
- package/dist/core/config.d.ts +5 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +14 -0
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +2 -1
- package/dist/core/conversations/archive.d.ts.map +1 -1
- package/dist/core/conversations/archive.js +4 -3
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +3 -2
- package/dist/core/events/legacy-storage/canary.d.ts.map +1 -1
- package/dist/core/events/legacy-storage/canary.js +5 -4
- package/dist/core/events/legacy-storage/compression.d.ts.map +1 -1
- package/dist/core/events/legacy-storage/compression.js +2 -1
- package/dist/core/events/v3/adapter-id.d.ts +10 -2
- package/dist/core/events/v3/adapter-id.d.ts.map +1 -1
- package/dist/core/events/v3/adapter-id.js +16 -0
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +2 -1
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +5 -4
- package/dist/core/events/v3/capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/capabilities.js +17 -0
- package/dist/core/events/v3/contract.d.ts +5 -0
- package/dist/core/events/v3/contract.d.ts.map +1 -1
- package/dist/core/events/v3/contract.js +7 -1
- package/dist/core/events/v3/control-witness.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.js +3 -2
- package/dist/core/events/v3/coordination-transaction-recovery.d.ts.map +1 -1
- package/dist/core/events/v3/coordination-transaction-recovery.js +4 -3
- package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -1
- package/dist/core/events/v3/fingerprint-keys.js +7 -6
- package/dist/core/events/v3/generated.d.ts +1 -1
- package/dist/core/events/v3/generated.js +1 -1
- package/dist/core/events/v3/live-feed.d.ts.map +1 -1
- package/dist/core/events/v3/live-feed.js +5 -4
- package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/command-recorder.js +8 -7
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +8 -7
- package/dist/core/events/v3/producers/diagnostic-summaries.d.ts.map +1 -1
- package/dist/core/events/v3/producers/diagnostic-summaries.js +5 -4
- package/dist/core/events/v3/producers/hook-base.d.ts +1 -0
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook-base.js +10 -4
- package/dist/core/events/v3/producers/hook.d.ts +2 -0
- package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook.js +16 -1
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +16 -6
- package/dist/core/events/v3/producers/recorder.d.ts +2 -4
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +109 -101
- package/dist/core/events/v3/recovery.d.ts.map +1 -1
- package/dist/core/events/v3/recovery.js +4 -3
- package/dist/core/events/v3/support-storage/inventory.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/inventory.js +2 -1
- package/dist/core/events/v3/support-storage/maintenance.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/maintenance.js +4 -3
- package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/pack-reader.js +4 -3
- package/dist/core/events/v3/support-storage/pack-writer.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/pack-writer.js +6 -5
- package/dist/core/events/v3/tool-line-changes.d.ts +8 -0
- package/dist/core/events/v3/tool-line-changes.d.ts.map +1 -0
- package/dist/core/events/v3/tool-line-changes.js +135 -0
- package/dist/core/events/v3/writer.d.ts.map +1 -1
- package/dist/core/events/v3/writer.js +8 -7
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +6 -5
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +12 -11
- package/dist/core/governor/state.d.ts.map +1 -1
- package/dist/core/governor/state.js +7 -6
- package/dist/core/guard/evaluator.js +1 -0
- package/dist/core/guard/storage.d.ts.map +1 -1
- package/dist/core/guard/storage.js +3 -2
- package/dist/core/hooks/adapter/behaviors/claude-code.d.ts +4 -0
- package/dist/core/hooks/adapter/behaviors/claude-code.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/claude-code.js +31 -0
- package/dist/core/hooks/adapter/behaviors/codex.d.ts +15 -0
- package/dist/core/hooks/adapter/behaviors/codex.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/codex.js +82 -0
- package/dist/core/hooks/adapter/behaviors/cursor.d.ts +7 -0
- package/dist/core/hooks/adapter/behaviors/cursor.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/cursor.js +35 -0
- package/dist/core/hooks/adapter/behaviors/index.d.ts +11 -0
- package/dist/core/hooks/adapter/behaviors/index.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/index.js +20 -0
- package/dist/core/hooks/adapter/behaviors/openclaw.d.ts +7 -0
- package/dist/core/hooks/adapter/behaviors/openclaw.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/openclaw.js +34 -0
- package/dist/core/hooks/adapter/behaviors/opencode.d.ts +7 -0
- package/dist/core/hooks/adapter/behaviors/opencode.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/opencode.js +34 -0
- package/dist/core/hooks/adapter/behaviors/types.d.ts +89 -0
- package/dist/core/hooks/adapter/behaviors/types.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/types.js +1 -0
- package/dist/core/hooks/adapter/detect.d.ts +1 -1
- package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
- package/dist/core/hooks/adapter/detect.js +2 -3
- package/dist/core/hooks/adapter/events.d.ts +29 -1
- package/dist/core/hooks/adapter/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +31 -0
- package/dist/core/hooks/adapter/opencode-plugin.d.ts +60 -0
- package/dist/core/hooks/adapter/opencode-plugin.d.ts.map +1 -0
- package/dist/core/hooks/adapter/opencode-plugin.js +129 -0
- package/dist/core/hooks/adapter/output.d.ts +7 -0
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +8 -1
- package/dist/core/hooks/adapter/wiring.d.ts +15 -1
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +74 -11
- package/dist/core/hooks/cli.js +635 -529
- package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
- package/dist/core/hooks/effects/image-capture.js +2 -1
- package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -1
- package/dist/core/hooks/prompt-context/contract.js +1 -0
- package/dist/core/hooks/resolve/transcript.d.ts +1 -1
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +35 -4
- package/dist/core/hooks/session-name-presence.d.ts +3 -1
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
- package/dist/core/hooks/session-name-presence.js +7 -2
- package/dist/core/hooks/shell-waiter.d.ts +2 -0
- package/dist/core/hooks/shell-waiter.d.ts.map +1 -0
- package/dist/core/hooks/shell-waiter.js +173 -0
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +5 -4
- package/dist/core/semantic/review.d.ts.map +1 -1
- package/dist/core/semantic/review.js +5 -4
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +15 -14
- package/dist/core/semantic/storage.d.ts.map +1 -1
- package/dist/core/semantic/storage.js +5 -4
- package/dist/core/storage/atomic-json.d.ts.map +1 -1
- package/dist/core/storage/atomic-json.js +7 -3
- package/dist/core/storage/durable-history.d.ts.map +1 -1
- package/dist/core/storage/durable-history.js +6 -5
- package/dist/core/storage/maintenance-providers.d.ts.map +1 -1
- package/dist/core/storage/maintenance-providers.js +2 -1
- package/dist/core/storage/maintenance.d.ts.map +1 -1
- package/dist/core/storage/maintenance.js +7 -6
- package/dist/core/storage/metrics.d.ts.map +1 -1
- package/dist/core/storage/metrics.js +5 -1
- package/dist/core/storage/modes.d.ts +17 -0
- package/dist/core/storage/modes.d.ts.map +1 -0
- package/dist/core/storage/modes.js +47 -0
- package/dist/core/storage/process-log.d.ts.map +1 -1
- package/dist/core/storage/process-log.js +3 -2
- package/dist/core/storage/receipts.d.ts.map +1 -1
- package/dist/core/storage/receipts.js +4 -3
- package/dist/core/storage/segments.d.ts.map +1 -1
- package/dist/core/storage/segments.js +7 -6
- package/dist/core/supervisor/hook-health.d.ts +2 -1
- package/dist/core/supervisor/hook-health.d.ts.map +1 -1
- package/dist/core/supervisor/hook-health.js +4 -3
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +7 -6
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +8 -7
- package/dist/core/workflow/adapters.d.ts +7 -0
- package/dist/core/workflow/adapters.d.ts.map +1 -1
- package/dist/core/workflow/adapters.js +9 -0
- package/dist/core/workflow/approvals.d.ts.map +1 -1
- package/dist/core/workflow/approvals.js +6 -5
- package/dist/core/workflow/billing.d.ts.map +1 -1
- package/dist/core/workflow/billing.js +33 -4
- package/dist/core/workflow/durable-record.d.ts.map +1 -1
- package/dist/core/workflow/durable-record.js +4 -3
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +2 -3
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +3 -2
- package/dist/core/workflow/spawn-opencode.d.ts +36 -0
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -0
- package/dist/core/workflow/spawn-opencode.js +132 -0
- package/dist/core/workflow/transcript.d.ts.map +1 -1
- package/dist/core/workflow/transcript.js +3 -2
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +5 -4
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +3 -2
- package/dist/core/workflow/workspaces/paths.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/paths.js +2 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +10 -9
- package/dist/lazy-commands.js +8 -1
- package/dist/lib/agent-browser/client.d.ts +9 -0
- package/dist/lib/agent-browser/client.d.ts.map +1 -1
- package/dist/lib/agent-browser/client.js +13 -0
- package/dist/lib/browser/client.d.ts +35 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +117 -8
- package/dist/lib/browser/identity.d.ts +25 -0
- package/dist/lib/browser/identity.d.ts.map +1 -0
- package/dist/lib/browser/identity.js +66 -0
- package/dist/lib/browser/index.d.ts +2 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -1
- package/dist/lib/browser/launch-args.d.ts +26 -0
- package/dist/lib/browser/launch-args.d.ts.map +1 -1
- package/dist/lib/browser/launch-args.js +41 -1
- package/dist/lib/browser/user-agent.d.ts +53 -0
- package/dist/lib/browser/user-agent.d.ts.map +1 -0
- package/dist/lib/browser/user-agent.js +79 -0
- package/dist/lib/docs-metadata-managed.d.ts +1 -0
- package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
- package/dist/lib/docs-metadata-managed.js +17 -1
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +4 -9
- package/dist/lib/instructions/apply.d.ts.map +1 -1
- package/dist/lib/instructions/apply.js +2 -0
- package/dist/lib/pace/index.d.ts +2 -0
- package/dist/lib/pace/index.d.ts.map +1 -0
- package/dist/lib/pace/index.js +1 -0
- package/dist/lib/pace/pace.d.ts +95 -0
- package/dist/lib/pace/pace.d.ts.map +1 -0
- package/dist/lib/pace/pace.js +313 -0
- package/dist/lib/tunnel/forward-headers.d.ts +11 -0
- package/dist/lib/tunnel/forward-headers.d.ts.map +1 -0
- package/dist/lib/tunnel/forward-headers.js +17 -0
- package/dist/lib/tunnel/gate.js +15 -10
- package/dist/lib/webview/index.d.ts +2 -0
- package/dist/lib/webview/index.d.ts.map +1 -0
- package/dist/lib/webview/index.js +1 -0
- package/dist/lib/webview/webview.d.ts +116 -0
- package/dist/lib/webview/webview.d.ts.map +1 -0
- package/dist/lib/webview/webview.js +289 -0
- package/opencode-plugin/package.json +19 -0
- package/opencode-plugin/src/index.ts +791 -0
- package/package.json +11 -4
- package/schemas/config.schema.json +13 -0
- package/schemas/event-v3.schema.json +164 -0
- package/src/commander.ts +7 -0
- package/src/commands/agents.ts +33 -18
- package/src/commands/browse-ai.ts +8 -0
- package/src/commands/browse.ts +189 -10
- package/src/commands/checkpoint.ts +7 -9
- package/src/commands/deinit.ts +9 -4
- package/src/commands/docs.ts +2 -0
- package/src/commands/doctor.ts +11 -2
- package/src/commands/fetch.ts +14 -0
- package/src/commands/init.ts +281 -119
- package/src/commands/instructions.ts +8 -4
- package/src/commands/ledger-v3.ts +2 -1
- package/src/commands/webview.ts +159 -0
- package/src/core/adapter.ts +102 -1
- package/src/core/adapters/attestation.ts +3 -2
- package/src/core/adapters/profiles.ts +42 -1
- package/src/core/adapters/registry.ts +37 -0
- package/src/core/agents/canonical-emit.ts +2 -5
- package/src/core/agents/cli.ts +18 -17
- package/src/core/agents/coord-client.ts +9 -3
- package/src/core/agents/rules/claim-conflict.ts +3 -8
- package/src/core/agents/rules/commit-conflict.ts +2 -7
- package/src/core/agents/rules/stop-hook.ts +3 -1
- package/src/core/agents/session-events.ts +120 -26
- package/src/core/agents/session-finalization-state-v3.ts +2 -1
- package/src/core/agents/session-finalizer-v3.ts +5 -4
- package/src/core/agents/session-name-display.ts +48 -0
- package/src/core/agents/state/heartbeat-reader.ts +7 -0
- package/src/core/agents/state/heartbeat-writer.ts +24 -2
- package/src/core/agents/state/live-coordination-view.ts +1 -0
- package/src/core/agents/state/live-coordination-writer.ts +3 -0
- package/src/core/agents/state/stale-sweep.ts +2 -9
- package/src/core/agents/turn-changes.ts +97 -0
- package/src/core/artifacts/delivery-card.ts +2 -1
- package/src/core/artifacts/index.ts +4 -3
- package/src/core/config.ts +23 -0
- package/src/core/context/index.ts +2 -1
- package/src/core/conversations/archive.ts +4 -3
- package/src/core/diagnostics/bundle.ts +3 -2
- package/src/core/events/legacy-storage/canary.ts +5 -4
- package/src/core/events/legacy-storage/compression.ts +2 -1
- package/src/core/events/v3/adapter-id.ts +29 -1
- package/src/core/events/v3/authority-outbox.ts +2 -1
- package/src/core/events/v3/bootstrap.ts +5 -4
- package/src/core/events/v3/capabilities.ts +17 -0
- package/src/core/events/v3/contract.ts +9 -1
- package/src/core/events/v3/control-witness.ts +3 -2
- package/src/core/events/v3/coordination-transaction-recovery.ts +4 -3
- package/src/core/events/v3/fingerprint-keys.ts +7 -6
- package/src/core/events/v3/generated.ts +1 -1
- package/src/core/events/v3/live-feed.ts +5 -4
- package/src/core/events/v3/producers/command-recorder.ts +8 -6
- package/src/core/events/v3/producers/coordination-recorder.ts +8 -7
- package/src/core/events/v3/producers/diagnostic-summaries.ts +5 -4
- package/src/core/events/v3/producers/hook-base.ts +11 -4
- package/src/core/events/v3/producers/hook.ts +19 -1
- package/src/core/events/v3/producers/intake.ts +18 -6
- package/src/core/events/v3/producers/recorder.ts +123 -114
- package/src/core/events/v3/recovery.ts +4 -3
- package/src/core/events/v3/support-storage/inventory.ts +2 -1
- package/src/core/events/v3/support-storage/maintenance.ts +8 -3
- package/src/core/events/v3/support-storage/pack-reader.ts +4 -3
- package/src/core/events/v3/support-storage/pack-writer.ts +6 -5
- package/src/core/events/v3/tool-line-changes.ts +139 -0
- package/src/core/events/v3/writer.ts +8 -7
- package/src/core/governor/planning.ts +6 -5
- package/src/core/governor/service.ts +12 -11
- package/src/core/governor/state.ts +7 -6
- package/src/core/guard/evaluator.ts +1 -0
- package/src/core/guard/storage.ts +3 -2
- package/src/core/hooks/adapter/behaviors/claude-code.ts +33 -0
- package/src/core/hooks/adapter/behaviors/codex.ts +96 -0
- package/src/core/hooks/adapter/behaviors/cursor.ts +37 -0
- package/src/core/hooks/adapter/behaviors/index.ts +31 -0
- package/src/core/hooks/adapter/behaviors/openclaw.ts +36 -0
- package/src/core/hooks/adapter/behaviors/opencode.ts +36 -0
- package/src/core/hooks/adapter/behaviors/types.ts +110 -0
- package/src/core/hooks/adapter/detect.ts +2 -3
- package/src/core/hooks/adapter/events.ts +53 -1
- package/src/core/hooks/adapter/opencode-plugin.ts +177 -0
- package/src/core/hooks/adapter/output.ts +8 -1
- package/src/core/hooks/adapter/wiring.ts +85 -11
- package/src/core/hooks/cli.ts +733 -537
- package/src/core/hooks/effects/image-capture.ts +2 -1
- package/src/core/hooks/prompt-context/contract.ts +1 -0
- package/src/core/hooks/resolve/transcript.ts +42 -5
- package/src/core/hooks/session-name-presence.ts +10 -3
- package/src/core/hooks/shell-waiter.ts +166 -0
- package/src/core/resources/service.ts +5 -4
- package/src/core/semantic/review.ts +5 -4
- package/src/core/semantic/service.ts +15 -14
- package/src/core/semantic/storage.ts +5 -4
- package/src/core/storage/atomic-json.ts +7 -3
- package/src/core/storage/durable-history.ts +6 -5
- package/src/core/storage/maintenance-providers.ts +2 -1
- package/src/core/storage/maintenance.ts +7 -6
- package/src/core/storage/metrics.ts +5 -1
- package/src/core/storage/modes.ts +53 -0
- package/src/core/storage/process-log.ts +3 -2
- package/src/core/storage/receipts.ts +4 -3
- package/src/core/storage/segments.ts +7 -6
- package/src/core/supervisor/hook-health.ts +4 -4
- package/src/core/supervisor/service.ts +7 -6
- package/src/core/work/state.ts +8 -7
- package/src/core/workflow/adapters.ts +15 -0
- package/src/core/workflow/approvals.ts +6 -5
- package/src/core/workflow/billing.ts +34 -4
- package/src/core/workflow/durable-record.ts +4 -3
- package/src/core/workflow/live-session-v3.ts +2 -3
- package/src/core/workflow/run-state.ts +3 -2
- package/src/core/workflow/spawn-opencode.ts +150 -0
- package/src/core/workflow/transcript.ts +3 -2
- package/src/core/workflow/workspaces/leases.ts +5 -4
- package/src/core/workflow/workspaces/local-git.ts +3 -2
- package/src/core/workflow/workspaces/paths.ts +2 -1
- package/src/core/workflow/workspaces/state.ts +10 -9
- package/src/lazy-commands.ts +11 -1
- package/src/lib/agent-browser/client.ts +18 -0
- package/src/lib/browser/client.ts +147 -9
- package/src/lib/browser/identity.ts +64 -0
- package/src/lib/browser/index.ts +15 -1
- package/src/lib/browser/launch-args.ts +53 -1
- package/src/lib/browser/user-agent.ts +130 -0
- package/src/lib/docs-metadata-managed.ts +21 -0
- package/src/lib/identities/assume.ts +7 -7
- package/src/lib/instructions/apply.ts +2 -0
- package/src/lib/pace/index.ts +14 -0
- package/src/lib/pace/pace.ts +370 -0
- package/src/lib/tunnel/forward-headers.ts +16 -0
- package/src/lib/tunnel/gate.ts +18 -10
- package/src/lib/webview/index.ts +13 -0
- package/src/lib/webview/webview.ts +457 -0
package/dist/core/hooks/cli.js
CHANGED
|
@@ -21,12 +21,13 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
|
21
21
|
import { dirname, join } from "node:path";
|
|
22
22
|
import { coordEnv } from "../../lib/env.js";
|
|
23
23
|
import { buildInstructionBundle } from "../../lib/instructions/bundle.js";
|
|
24
|
+
import { adapterFromPlatform as sharedAdapterFromPlatform } from "../adapter.js";
|
|
24
25
|
import { resolveCoordRoot } from "../agents/coord-client.js";
|
|
25
26
|
import { classifyWriteClaimFinalization, formatWriteClaimFinalizationDenial, } from "../agents/finalization.js";
|
|
26
27
|
import { recordLiveClaimChangeV3, restoreLiveCoordinationStateAfterEpochV3, } from "../agents/live-authority-v3.js";
|
|
27
28
|
import { evaluateStopHook, STOP_REMEDIATION_MARKER } from "../agents/rules/stop-hook.js";
|
|
28
|
-
import { assistantTextStartsWithSessionNameBlock, isSessionNameRemediationCommand, matchSessionNameDisplay, sessionNameDisplayAcceptedNames, sessionNameDisplayInstruction, sessionNameDisplayPending, sessionNameDisplayRecoveryInstruction, toolResponseMintedSessionName, } from "../agents/session-name-display.js";
|
|
29
|
-
import { readHeartbeat, setAssignedNameCache, stampSessionNameRequested, stampSessionNameSeen, } from "../agents/state/heartbeat-writer.js";
|
|
29
|
+
import { assistantTextStartsWithSessionNameBlock, isSessionNameRemediationCommand, matchSessionNameDisplay, sessionNameDisplayAcceptedNames, sessionNameDisplayInstruction, sessionNameDisplayPending, sessionNameDisplayRecoveryInstruction, sessionNameDisplayReminder, sessionNameDisplayReminderDue, toolResponseMintedSessionName, } from "../agents/session-name-display.js";
|
|
30
|
+
import { readHeartbeat, setAssignedNameCache, stampSessionNameReminded, stampSessionNameRequested, stampSessionNameSeen, } from "../agents/state/heartbeat-writer.js";
|
|
30
31
|
import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
|
|
31
32
|
import { ensureLiveCoordinationHeartbeat } from "../agents/state/live-coordination-writer.js";
|
|
32
33
|
import { assignName } from "../agents/state/names.js";
|
|
@@ -39,11 +40,10 @@ import { captureSpanClockV3 } from "../events/v3/span-state.js";
|
|
|
39
40
|
import { ensureRelayDaemon, fetchPresence, publishPresence } from "../presence/index.js";
|
|
40
41
|
import { closeProcessLoggers, legacyLogFields, processLogger } from "../storage/logger.js";
|
|
41
42
|
import { stableScopeId } from "../workflow/scope-id.js";
|
|
43
|
+
import { adapterBehavior } from "./adapter/behaviors/index.js";
|
|
42
44
|
import { detectAdapter, shouldSkipHookAdapter } from "./adapter/detect.js";
|
|
43
45
|
import { extractBashCommand, extractToolDescription, normalizeEventName, parsePayload, } from "./adapter/parse.js";
|
|
44
|
-
import { discoverCodexSessionTranscript } from "./adapter/runtime-telemetry.js";
|
|
45
46
|
import { scheduleBackupSnapshot } from "./backup-schedule.js";
|
|
46
|
-
import { codexWslFileLinkTelemetry, inspectCodexWslBridge, isWslUncPath, renderCodexWslFileLinkContext, } from "./codex-wsl-bridge.js";
|
|
47
47
|
import { captureImages, detectPresence, imageJanitor, journalArchive, journalJanitor, journalRecoveryCue, playSound, recordImageArtifactsV3, resetSoundCounters, runSessionSyncExtension, soundForEvent, } from "./effects/index.js";
|
|
48
48
|
import { toolInputHash, toolTargetHash } from "./events/input-hash.js";
|
|
49
49
|
import { canonicalize } from "./guard-path.js";
|
|
@@ -51,9 +51,9 @@ import { beginHookHealth, observeHookDebug, observeHookError, writeHookHealthCom
|
|
|
51
51
|
import { adapterPidFromEnv, parsePsChainLine, selectAnchorPid } from "./resolve/anchor.js";
|
|
52
52
|
import { extractIntentComment, resolveIntent } from "./resolve/intent.js";
|
|
53
53
|
import { resolveOwner } from "./resolve/owner.js";
|
|
54
|
-
import { inspectSessionNameDisplayImmediately, scanAssistantStatusBoxPresent,
|
|
54
|
+
import { inspectSessionNameDisplayImmediately, scanAssistantStatusBoxPresent, scanStatusBoxPresent, scanTranscriptRuntime, } from "./resolve/transcript.js";
|
|
55
55
|
import { sessionNamePresence } from "./session-name-presence.js";
|
|
56
|
-
import {
|
|
56
|
+
import { shellWaiterReason } from "./shell-waiter.js";
|
|
57
57
|
let hookHealthState;
|
|
58
58
|
function parseArgv(argv) {
|
|
59
59
|
const out = { eventName: null, extra: [] };
|
|
@@ -223,11 +223,9 @@ function buildEventData(eventType, ctx) {
|
|
|
223
223
|
const p = ctx.payload;
|
|
224
224
|
switch (eventType) {
|
|
225
225
|
case "session.started": {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
? "cursor"
|
|
230
|
-
: "codex";
|
|
226
|
+
// The platform label is the adapter id itself; enumerating and defaulting
|
|
227
|
+
// to codex silently mislabels any adapter not in the list.
|
|
228
|
+
const adapterPlatform = ctx.adapter;
|
|
231
229
|
// Recorded fork lineage is NOT detected here. On claude-code a fork
|
|
232
230
|
// never fires its own session.started — SessionStart fires under the
|
|
233
231
|
// PARENT's session id (source=resume) before the fork id is minted
|
|
@@ -310,9 +308,7 @@ function buildEventData(eventType, ctx) {
|
|
|
310
308
|
}
|
|
311
309
|
case "turn.completed": {
|
|
312
310
|
const lastAssistantMessage = p?.raw.last_assistant_message ?? "";
|
|
313
|
-
const fileLinkTelemetry = ctx.adapter
|
|
314
|
-
? codexWslFileLinkTelemetry(ctx.coordRoot, p?.cwd, lastAssistantMessage)
|
|
315
|
-
: null;
|
|
311
|
+
const fileLinkTelemetry = adapterBehavior(ctx.adapter).fileLinkTelemetry?.(ctx.coordRoot, p?.cwd, lastAssistantMessage) ?? null;
|
|
316
312
|
return {
|
|
317
313
|
// Backfill the model for adapters that omit it at session.started
|
|
318
314
|
// (Claude Code). The transcript is populated with assistant turns by
|
|
@@ -343,13 +339,12 @@ function buildEventData(eventType, ctx) {
|
|
|
343
339
|
// and which name that refers to. Reported on every stop while a name
|
|
344
340
|
// exists (the stop-hook.session_name verdict reads it per turn); the
|
|
345
341
|
// transcript scan itself stops once the name has been sighted.
|
|
346
|
-
...sessionNamePresence(ctx.coordRoot, ctx.instanceId, (name) =>
|
|
347
|
-
//
|
|
348
|
-
// session id so the naming ritual can
|
|
342
|
+
...sessionNamePresence(ctx.coordRoot, ctx.instanceId, (name) => inspectSessionNameDisplayImmediately(
|
|
343
|
+
// Some adapters omit transcript_path on stop; the behavior may
|
|
344
|
+
// discover the transcript by session id so the naming ritual can
|
|
345
|
+
// verify and stamp there too.
|
|
349
346
|
p?.transcript_path ??
|
|
350
|
-
(ctx.adapter
|
|
351
|
-
? discoverCodexSessionTranscript(p?.session_id ?? ctx.instanceId)
|
|
352
|
-
: undefined), name, assistantTextStartsWithSessionNameBlock)),
|
|
347
|
+
adapterBehavior(ctx.adapter).discoverTranscript?.(p?.session_id ?? ctx.instanceId), name, assistantTextStartsWithSessionNameBlock)),
|
|
353
348
|
...(fileLinkTelemetry ?? {}),
|
|
354
349
|
stop_hook_active: p?.stop_hook_active,
|
|
355
350
|
};
|
|
@@ -479,6 +474,7 @@ async function main() {
|
|
|
479
474
|
const hookClock = captureSpanClockV3();
|
|
480
475
|
const { eventName, extra } = parseArgv(process.argv.slice(2));
|
|
481
476
|
const adapter = detectAdapter(process.argv.slice(2));
|
|
477
|
+
const behavior = adapter ? adapterBehavior(adapter) : null;
|
|
482
478
|
const raw = await readStdin();
|
|
483
479
|
hookHealthState = beginHookHealth({
|
|
484
480
|
started_at_ms: hookStartedAt,
|
|
@@ -516,8 +512,8 @@ async function main() {
|
|
|
516
512
|
// Kill-switch-INDEPENDENT effects: notification sounds fire BEFORE the
|
|
517
513
|
// HARNERY_AGENT_COORD_OFF gate so audible feedback survives incident-triage
|
|
518
514
|
// bypass: sound playback happens before the kill-switch bailout.
|
|
519
|
-
//
|
|
520
|
-
if (
|
|
515
|
+
// Behavior-gated; stop-failure → error, sub-agent-start → subagent-start.
|
|
516
|
+
if (behavior?.soundEffects && eventName) {
|
|
521
517
|
const s = soundForEvent(eventName);
|
|
522
518
|
if (s) {
|
|
523
519
|
const repoRoot = resolveCoordRoot(process.cwd());
|
|
@@ -568,7 +564,7 @@ async function main() {
|
|
|
568
564
|
pid: process.pid,
|
|
569
565
|
ppid: process.ppid,
|
|
570
566
|
};
|
|
571
|
-
if (!eventName || !adapter) {
|
|
567
|
+
if (!eventName || !adapter || !behavior) {
|
|
572
568
|
appendDebug(coordRoot, { ...debugBase, skipped: "missing-event-or-adapter" });
|
|
573
569
|
return 0;
|
|
574
570
|
}
|
|
@@ -579,90 +575,17 @@ async function main() {
|
|
|
579
575
|
// assistant text. Preserve only privacy-safe ritual booleans in the V3
|
|
580
576
|
// producer's open-turn state; Stop consumes them into the authoritative
|
|
581
577
|
// turn.completed event. The response body never enters V3 or heartbeat.
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
const owner = resolveOwner({ payload: payload?.raw ?? null, coordRoot });
|
|
585
|
-
if (!owner) {
|
|
586
|
-
appendDebug(coordRoot, { ...debugBase, skipped: "no-owner-resolved" });
|
|
587
|
-
return 0;
|
|
588
|
-
}
|
|
589
|
-
const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
|
|
590
|
-
const name = sessionNameDisplayPending(row);
|
|
591
|
-
const text = typeof payload?.raw.text === "string" ? payload.raw.text : "";
|
|
592
|
-
const sighting = matchSessionNameDisplay(row, text);
|
|
593
|
-
if (sighting) {
|
|
594
|
-
stampSessionNameSeen(coordRoot, owner.instance_id, sighting.pending);
|
|
595
|
-
appendDebug(coordRoot, {
|
|
596
|
-
...debugBase,
|
|
597
|
-
effect: "session-name-display-stamped",
|
|
598
|
-
session_name_pending: sighting.pending,
|
|
599
|
-
...(sighting.displayed === sighting.pending
|
|
600
|
-
? {}
|
|
601
|
-
: { session_name_displayed: sighting.displayed, session_name_drift: true }),
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
else if (!name) {
|
|
605
|
-
// Nothing was owed. Distinct from a miss: conflating the two is what made
|
|
606
|
-
// a latched Cursor session undiagnosable from this log.
|
|
607
|
-
appendDebug(coordRoot, { ...debugBase, skipped: "no-pending-session-name" });
|
|
608
|
-
}
|
|
609
|
-
else {
|
|
610
|
-
appendDebug(coordRoot, {
|
|
611
|
-
...debugBase,
|
|
612
|
-
skipped: "session-name-block-absent",
|
|
613
|
-
session_name_pending: name,
|
|
614
|
-
reply_bytes: text.length,
|
|
615
|
-
reply_leads_with_fence: /^\s*`{3,}/.test(text),
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
if (ledgerRoute.state === "blocked") {
|
|
619
|
-
appendDebug(coordRoot, {
|
|
620
|
-
...debugBase,
|
|
621
|
-
skipped: "v3-control-blocked",
|
|
622
|
-
reason: ledgerRoute.reason,
|
|
623
|
-
owner_source: owner.source,
|
|
624
|
-
});
|
|
625
|
-
return 0;
|
|
626
|
-
}
|
|
627
|
-
const statusBoxPresentStrict = scanAssistantStatusBoxPresent(undefined, text);
|
|
628
|
-
// The hook parser needs the completed reply long enough to scan it, but
|
|
629
|
-
// durable hook intake must never retain that body. Keep every other native
|
|
630
|
-
// field so turn correlation remains available to the producer.
|
|
631
|
-
const { text: _completedReply, ...privacySafeRaw } = payload?.raw ?? {};
|
|
632
|
-
const privacySafePayload = payload ? { ...payload, raw: privacySafeRaw } : null;
|
|
633
|
-
const v3Result = recordLiveHookSignalV3({
|
|
578
|
+
if (behavior && eventName && eventName === behavior.completedResponseEvent) {
|
|
579
|
+
return handleCompletedResponse({
|
|
634
580
|
coordRoot,
|
|
635
|
-
route: ledgerRoute,
|
|
636
|
-
eventName,
|
|
637
|
-
payload: privacySafePayload,
|
|
638
581
|
adapter,
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
status_box_present_strict: statusBoxPresentStrict,
|
|
646
|
-
session_name_required: name !== undefined,
|
|
647
|
-
session_name_present: name ? assistantTextStartsWithSessionNameBlock(text, name) : false,
|
|
648
|
-
},
|
|
649
|
-
});
|
|
650
|
-
observeHookDebug(hookHealthState, { event_v3_state: v3Result.state });
|
|
651
|
-
appendDebug(coordRoot, {
|
|
652
|
-
...debugBase,
|
|
653
|
-
effect: "cursor-response-ritual-observed",
|
|
654
|
-
owner_source: owner.source,
|
|
655
|
-
event_v3_state: v3Result.state,
|
|
656
|
-
...(v3Result.state === "observed"
|
|
657
|
-
? {
|
|
658
|
-
generation_id: v3Result.generation_id,
|
|
659
|
-
turn_id: v3Result.turn_id,
|
|
660
|
-
response_observed_at: v3Result.observed_at,
|
|
661
|
-
status_box_present_strict: statusBoxPresentStrict,
|
|
662
|
-
}
|
|
663
|
-
: {}),
|
|
582
|
+
eventName,
|
|
583
|
+
raw,
|
|
584
|
+
debugBase,
|
|
585
|
+
ledgerRoute,
|
|
586
|
+
hookStartedAt,
|
|
587
|
+
hookClock,
|
|
664
588
|
});
|
|
665
|
-
return 0;
|
|
666
589
|
}
|
|
667
590
|
const norm = normalizeEventName(eventName);
|
|
668
591
|
if (!norm) {
|
|
@@ -670,6 +593,7 @@ async function main() {
|
|
|
670
593
|
return 0;
|
|
671
594
|
}
|
|
672
595
|
const payload = parsePayload(raw, adapter);
|
|
596
|
+
const bridge = behavior.bridgeFor(payload?.cwd);
|
|
673
597
|
const owner = resolveOwner({
|
|
674
598
|
payload: payload?.raw ?? null,
|
|
675
599
|
coordRoot,
|
|
@@ -711,8 +635,14 @@ async function main() {
|
|
|
711
635
|
status_box_present_strict: data.status_box_present_strict === true,
|
|
712
636
|
session_name_required: typeof data.session_name_present_for === "string",
|
|
713
637
|
session_name_present: data.session_name_present === true,
|
|
638
|
+
...(typeof data.session_name_unavailable_reason === "string"
|
|
639
|
+
? { session_name_unavailable_reason: data.session_name_unavailable_reason }
|
|
640
|
+
: {}),
|
|
714
641
|
}
|
|
715
642
|
: undefined;
|
|
643
|
+
if (typeof data.session_name_unavailable_reason === "string") {
|
|
644
|
+
reportSessionNameObservationUnavailable(coordRoot, owner.instance_id, adapter, eventName, data.session_name_unavailable_reason);
|
|
645
|
+
}
|
|
716
646
|
const stopRemediation = norm.event_type === "turn.started" &&
|
|
717
647
|
typeof data.prompt_text === "string" &&
|
|
718
648
|
data.prompt_text.includes(STOP_REMEDIATION_MARKER);
|
|
@@ -726,6 +656,86 @@ async function main() {
|
|
|
726
656
|
});
|
|
727
657
|
return 0;
|
|
728
658
|
}
|
|
659
|
+
const run = {
|
|
660
|
+
coordRoot,
|
|
661
|
+
adapter,
|
|
662
|
+
behavior,
|
|
663
|
+
eventName,
|
|
664
|
+
norm,
|
|
665
|
+
payload,
|
|
666
|
+
raw,
|
|
667
|
+
owner,
|
|
668
|
+
sessionId,
|
|
669
|
+
data,
|
|
670
|
+
debugBase,
|
|
671
|
+
ledgerRoute,
|
|
672
|
+
hookStartedAt,
|
|
673
|
+
hookClock,
|
|
674
|
+
priorCoordination,
|
|
675
|
+
topLevelSession,
|
|
676
|
+
stopRemediation,
|
|
677
|
+
bridge,
|
|
678
|
+
turnRitual,
|
|
679
|
+
};
|
|
680
|
+
recordSignal(run);
|
|
681
|
+
recordContextTelemetry(run);
|
|
682
|
+
// Phase 8: SessionStart post-emit: project the event so the heartbeat
|
|
683
|
+
// lands synchronously, run stale-sweep, and emit the adapter-shaped
|
|
684
|
+
// systemMessage JSON (peer table + wiring check + council invites).
|
|
685
|
+
// Adapter-agnostic since v0.5.0; replaces the previous bash UX layer
|
|
686
|
+
// and the equivalent per-adapter bash session_start handlers.
|
|
687
|
+
if (norm.event_type === "session.started")
|
|
688
|
+
await handleSessionStarted(run);
|
|
689
|
+
// Phase 8: SessionEnd cleanup: delete heartbeat + pid-map rows. Adapter-
|
|
690
|
+
// agnostic since v0.5.0.
|
|
691
|
+
if (norm.event_type === "session.ended")
|
|
692
|
+
handleSessionEnded(run);
|
|
693
|
+
// Phase 8: SubagentStart: the child generation was opened above from the
|
|
694
|
+
// native delegation identity. Refresh its disposable projection, log the
|
|
695
|
+
// lifecycle event, and emit a context message announcing the subagent.
|
|
696
|
+
if (norm.event_type === "agent.started")
|
|
697
|
+
await handleAgentStarted(run);
|
|
698
|
+
// Phase 8: SubagentStop: delete subagent heartbeat + log.
|
|
699
|
+
if (norm.event_type === "agent.completed")
|
|
700
|
+
await handleAgentCompleted(run);
|
|
701
|
+
// Phase 8: UserPromptSubmit: render dedup'd peer table + council pending
|
|
702
|
+
// and emit the adapter-shaped systemMessage JSON. Adapter-agnostic since v0.5.0.
|
|
703
|
+
if (norm.event_type === "turn.started")
|
|
704
|
+
await handleTurnStarted(run);
|
|
705
|
+
// turn.completed: telemetry, then the stop verdict. The
|
|
706
|
+
// verdict + codex-replay previously lived in the per-adapter shell adapters;
|
|
707
|
+
// agent-hook owns them now. Runs on the normal "stop" event only;
|
|
708
|
+
// "stop-failure" (API error) gets no gate, matching the previous
|
|
709
|
+
// stop vs stop-failure split.
|
|
710
|
+
if (norm.event_type === "turn.completed" && eventName === "stop") {
|
|
711
|
+
const exit = await handleStop(run);
|
|
712
|
+
if (exit !== undefined)
|
|
713
|
+
return exit;
|
|
714
|
+
}
|
|
715
|
+
// Phase 7: PreToolUse: heartbeat + pid-map self-heal on every tool call.
|
|
716
|
+
// Adapter-agnostic: both writes have the same shape regardless of who fired.
|
|
717
|
+
// Cursor/Codex bash dispatchers still fire their own G-guard logic, but the
|
|
718
|
+
// heals here keep the agent-coord layer's view of liveness fresh.
|
|
719
|
+
//
|
|
720
|
+
// The heartbeat + pid-map heals are paired by design; they were wired
|
|
721
|
+
// side-by-side in the previous pre-tool-use adapter. The Phase 4-6 refactor
|
|
722
|
+
// preserved the heartbeat half but dropped the pid-map half; the pid-map
|
|
723
|
+
// call was restored here afterward.
|
|
724
|
+
if (norm.event_type === "tool.requested") {
|
|
725
|
+
const exit = await handleToolRequested(run);
|
|
726
|
+
if (exit !== undefined)
|
|
727
|
+
return exit;
|
|
728
|
+
}
|
|
729
|
+
if (norm.event_type === "tool.completed" && eventName === "post-tool-use")
|
|
730
|
+
await handlePostToolUse(run);
|
|
731
|
+
// Phase 7: PostToolUseFailure: release claim on failed Edit (the file
|
|
732
|
+
// never landed; the claim is stale). Adapter-agnostic.
|
|
733
|
+
if (norm.event_type === "tool.completed" && eventName === "post-tool-use-failure")
|
|
734
|
+
handlePostToolUseFailure(run);
|
|
735
|
+
return 0;
|
|
736
|
+
}
|
|
737
|
+
function recordSignal(run) {
|
|
738
|
+
const { coordRoot, adapter, eventName, norm, payload, owner, sessionId, data, debugBase, ledgerRoute, hookStartedAt, hookClock, priorCoordination, stopRemediation, bridge, turnRitual, } = run;
|
|
729
739
|
let capturedImages = [];
|
|
730
740
|
if (norm.event_type === "tool.requested" ||
|
|
731
741
|
(norm.event_type === "tool.completed" && eventName === "post-tool-use")) {
|
|
@@ -753,7 +763,7 @@ async function main() {
|
|
|
753
763
|
: {}),
|
|
754
764
|
}
|
|
755
765
|
: {}),
|
|
756
|
-
...(
|
|
766
|
+
...(bridge ? { bridge } : {}),
|
|
757
767
|
hook_name: eventName,
|
|
758
768
|
hook_duration_ms: Math.max(0, Math.floor(performance.now() - hookStartedAt)),
|
|
759
769
|
monotonic_ns: hookClock.monotonic_ns,
|
|
@@ -806,9 +816,7 @@ async function main() {
|
|
|
806
816
|
parentEvent: v3Result.event,
|
|
807
817
|
payload,
|
|
808
818
|
adapter,
|
|
809
|
-
...(
|
|
810
|
-
? { bridge: "codex-wsl" }
|
|
811
|
-
: {}),
|
|
819
|
+
...(bridge ? { bridge } : {}),
|
|
812
820
|
monotonic_ns: hookClock.monotonic_ns,
|
|
813
821
|
});
|
|
814
822
|
const nativeChild = payload.subagent_id ?? payload.agent_id;
|
|
@@ -851,6 +859,12 @@ async function main() {
|
|
|
851
859
|
...(v3Result ? { event_v3_state: v3Result.state } : {}),
|
|
852
860
|
...(v3Result && "reason" in v3Result ? { event_v3_reason: v3Result.reason } : {}),
|
|
853
861
|
});
|
|
862
|
+
run.v3Result = v3Result;
|
|
863
|
+
run.v3EventId = v3EventId;
|
|
864
|
+
run.recordedGenerationId = recordedGenerationId;
|
|
865
|
+
}
|
|
866
|
+
function recordContextTelemetry(run) {
|
|
867
|
+
const { coordRoot, adapter, norm, payload, owner, sessionId } = run;
|
|
854
868
|
// Context telemetry is opportunistic and truthful: only persist a sample
|
|
855
869
|
// when the adapter payload actually exposes usage/window data. Identical
|
|
856
870
|
// measurements are de-duplicated before they reach the canonical stream.
|
|
@@ -902,413 +916,515 @@ async function main() {
|
|
|
902
916
|
logError(coordRoot, err, { phase: "context-compaction-completed" });
|
|
903
917
|
}
|
|
904
918
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
//
|
|
909
|
-
//
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
let backupCue = "";
|
|
916
|
-
try {
|
|
917
|
-
backupCue = scheduleBackupSnapshot(coordRoot).cue ?? "";
|
|
918
|
-
}
|
|
919
|
-
catch (err) {
|
|
920
|
-
logError(coordRoot, err, { phase: "backup-schedule" });
|
|
921
|
-
}
|
|
922
|
-
// Effect (claude-code): prune stale journal archives + sweep orphans.
|
|
923
|
-
// The recovery-cue is merged into the
|
|
924
|
-
// session-start additionalContext inside emitSessionStartSystemMessage.
|
|
925
|
-
if (adapter === "claude-code")
|
|
926
|
-
journalJanitor(coordRoot);
|
|
927
|
-
imageJanitor(coordRoot);
|
|
928
|
-
// Session start is the only caller of the artifact janitor and the storage
|
|
929
|
-
// maintenance pass, so both load here instead of on every tool call.
|
|
930
|
-
const { autoCleanArtifacts } = await import("../artifacts/index.js");
|
|
931
|
-
const { autoCleanEventV3Archives } = await import("../events/v3/archive-retention.js");
|
|
932
|
-
const { createAutomaticMaintenanceComposition, runAutomaticMaintenancePass } = await import("../storage/maintenance-providers.js");
|
|
933
|
-
// Effect: throttled daily sweep of expired artifact workspaces (guarded,
|
|
934
|
-
// managed-expired only; see autoCleanArtifacts). Best-effort like every
|
|
935
|
-
// janitor here: a failure logs and never blocks session start.
|
|
936
|
-
try {
|
|
937
|
-
autoCleanArtifacts(coordRoot);
|
|
938
|
-
}
|
|
939
|
-
catch (err) {
|
|
940
|
-
logError(coordRoot, err, { phase: "artifact-auto-clean" });
|
|
941
|
-
}
|
|
942
|
-
// Effect: apply the same daily throttle to closed V3 epochs. The newest
|
|
943
|
-
// recovery epochs remain protected by the archive retention policy.
|
|
944
|
-
try {
|
|
945
|
-
autoCleanEventV3Archives(coordRoot);
|
|
946
|
-
}
|
|
947
|
-
catch (err) {
|
|
948
|
-
logError(coordRoot, err, { phase: "event-v3-archive-auto-clean" });
|
|
949
|
-
}
|
|
950
|
-
// Plan one bounded, claim-first global slice from cached pressure only.
|
|
951
|
-
// Existing owner janitors above remain the only active executors until
|
|
952
|
-
// each provider receives a separate production activation.
|
|
953
|
-
try {
|
|
954
|
-
await runAutomaticMaintenancePass(createAutomaticMaintenanceComposition(coordRoot, {
|
|
955
|
-
journal: () => {
|
|
956
|
-
if (adapter === "claude-code")
|
|
957
|
-
journalJanitor(coordRoot);
|
|
958
|
-
},
|
|
959
|
-
images: () => imageJanitor(coordRoot),
|
|
960
|
-
artifacts: () => autoCleanArtifacts(coordRoot),
|
|
961
|
-
}));
|
|
962
|
-
}
|
|
963
|
-
catch (err) {
|
|
964
|
-
logError(coordRoot, err, { phase: "storage-maintenance-auto" });
|
|
965
|
-
}
|
|
966
|
-
let recovery = null;
|
|
967
|
-
if (payload?.source === "compact") {
|
|
968
|
-
try {
|
|
969
|
-
markContextCompactionCompleted(coordRoot, {
|
|
970
|
-
sessionId,
|
|
971
|
-
instanceId: owner.instance_id,
|
|
972
|
-
});
|
|
973
|
-
recovery = prepareContextRecovery(coordRoot, {
|
|
974
|
-
instanceId: owner.instance_id,
|
|
975
|
-
sessionId,
|
|
976
|
-
cwd: payload?.cwd ?? process.cwd(),
|
|
977
|
-
});
|
|
978
|
-
}
|
|
979
|
-
catch (err) {
|
|
980
|
-
logError(coordRoot, err, { phase: "context-recovery-session-start" });
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
try {
|
|
984
|
-
const injected = await emitSessionStartSystemMessage(coordRoot, owner.instance_id, sessionId, data, adapter, recovery?.briefing ?? "", backupCue);
|
|
985
|
-
if (injected && recovery) {
|
|
986
|
-
completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
catch (err) {
|
|
990
|
-
logError(coordRoot, err, { phase: "session-start-systemMessage" });
|
|
991
|
-
}
|
|
992
|
-
// Cross-machine presence is derived from the authority-safe V3 projection,
|
|
993
|
-
// not the disposable heartbeat cache. Announce this session, pull peers,
|
|
994
|
-
// and keep the optional live relay connected.
|
|
995
|
-
try {
|
|
996
|
-
publishPresence(coordRoot);
|
|
997
|
-
fetchPresence(coordRoot);
|
|
998
|
-
ensureRelayDaemon(coordRoot);
|
|
999
|
-
}
|
|
1000
|
-
catch (err) {
|
|
1001
|
-
logError(coordRoot, err, { phase: "session-start-presence" });
|
|
1002
|
-
}
|
|
919
|
+
}
|
|
920
|
+
async function handleSessionStarted(run) {
|
|
921
|
+
const { coordRoot, adapter, behavior, payload, owner, sessionId, data } = run;
|
|
922
|
+
// A configured backup runs out of process and is freshness-gated per host.
|
|
923
|
+
// Startup never waits for restic or a remote provider; the child owns no
|
|
924
|
+
// hook stdio, so the harness is not held open until it finishes. A failed
|
|
925
|
+
// previous run surfaces as one line in this session's context.
|
|
926
|
+
let backupCue = "";
|
|
927
|
+
try {
|
|
928
|
+
backupCue = scheduleBackupSnapshot(coordRoot).cue ?? "";
|
|
1003
929
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
}
|
|
1024
|
-
catch (err) {
|
|
1025
|
-
logError(coordRoot, err, { phase: "session-end-presence" });
|
|
1026
|
-
}
|
|
930
|
+
catch (err) {
|
|
931
|
+
logError(coordRoot, err, { phase: "backup-schedule" });
|
|
932
|
+
}
|
|
933
|
+
// Effect (journal-maintaining adapters): prune stale journal archives +
|
|
934
|
+
// sweep orphans. The recovery-cue is merged into the session-start
|
|
935
|
+
// additionalContext inside emitSessionStartSystemMessage.
|
|
936
|
+
if (behavior.journalMaintenance)
|
|
937
|
+
journalJanitor(coordRoot);
|
|
938
|
+
imageJanitor(coordRoot);
|
|
939
|
+
// Session start is the only caller of the artifact janitor and the storage
|
|
940
|
+
// maintenance pass, so both load here instead of on every tool call.
|
|
941
|
+
const { autoCleanArtifacts } = await import("../artifacts/index.js");
|
|
942
|
+
const { autoCleanEventV3Archives } = await import("../events/v3/archive-retention.js");
|
|
943
|
+
const { createAutomaticMaintenanceComposition, runAutomaticMaintenancePass } = await import("../storage/maintenance-providers.js");
|
|
944
|
+
// Effect: throttled daily sweep of expired artifact workspaces (guarded,
|
|
945
|
+
// managed-expired only; see autoCleanArtifacts). Best-effort like every
|
|
946
|
+
// janitor here: a failure logs and never blocks session start.
|
|
947
|
+
try {
|
|
948
|
+
autoCleanArtifacts(coordRoot);
|
|
1027
949
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
950
|
+
catch (err) {
|
|
951
|
+
logError(coordRoot, err, { phase: "artifact-auto-clean" });
|
|
952
|
+
}
|
|
953
|
+
// Effect: apply the same daily throttle to closed V3 epochs. The newest
|
|
954
|
+
// recovery epochs remain protected by the archive retention policy.
|
|
955
|
+
try {
|
|
956
|
+
autoCleanEventV3Archives(coordRoot);
|
|
957
|
+
}
|
|
958
|
+
catch (err) {
|
|
959
|
+
logError(coordRoot, err, { phase: "event-v3-archive-auto-clean" });
|
|
960
|
+
}
|
|
961
|
+
// Plan one bounded, claim-first global slice from cached pressure only.
|
|
962
|
+
// Existing owner janitors above remain the only active executors until
|
|
963
|
+
// each provider receives a separate production activation.
|
|
964
|
+
try {
|
|
965
|
+
await runAutomaticMaintenancePass(createAutomaticMaintenanceComposition(coordRoot, {
|
|
966
|
+
journal: () => {
|
|
967
|
+
if (behavior.journalMaintenance)
|
|
968
|
+
journalJanitor(coordRoot);
|
|
969
|
+
},
|
|
970
|
+
images: () => imageJanitor(coordRoot),
|
|
971
|
+
artifacts: () => autoCleanArtifacts(coordRoot),
|
|
972
|
+
}));
|
|
973
|
+
}
|
|
974
|
+
catch (err) {
|
|
975
|
+
logError(coordRoot, err, { phase: "storage-maintenance-auto" });
|
|
976
|
+
}
|
|
977
|
+
let recovery = null;
|
|
978
|
+
if (payload?.source === "compact") {
|
|
1032
979
|
try {
|
|
1033
|
-
|
|
1034
|
-
|
|
980
|
+
markContextCompactionCompleted(coordRoot, {
|
|
981
|
+
sessionId,
|
|
982
|
+
instanceId: owner.instance_id,
|
|
983
|
+
});
|
|
984
|
+
recovery = prepareContextRecovery(coordRoot, {
|
|
985
|
+
instanceId: owner.instance_id,
|
|
986
|
+
sessionId,
|
|
987
|
+
cwd: payload?.cwd ?? process.cwd(),
|
|
988
|
+
});
|
|
1035
989
|
}
|
|
1036
990
|
catch (err) {
|
|
1037
|
-
logError(coordRoot, err, { phase: "
|
|
991
|
+
logError(coordRoot, err, { phase: "context-recovery-session-start" });
|
|
1038
992
|
}
|
|
1039
993
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
cleanupSessionEnd(coordRoot, childInstanceId);
|
|
1045
|
-
const { reconcileSessionFinalizationV3 } = await import("../agents/session-finalizer-v3.js");
|
|
1046
|
-
reconcileSessionFinalizationV3(coordRoot, { archive_observations: [] });
|
|
1047
|
-
}
|
|
1048
|
-
catch (err) {
|
|
1049
|
-
logError(coordRoot, err, { phase: "subagent-stop-cleanup" });
|
|
994
|
+
try {
|
|
995
|
+
const injected = await emitSessionStartSystemMessage(coordRoot, owner.instance_id, sessionId, data, adapter, recovery?.briefing ?? "", backupCue);
|
|
996
|
+
if (injected && recovery) {
|
|
997
|
+
completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
|
|
1050
998
|
}
|
|
1051
999
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1000
|
+
catch (err) {
|
|
1001
|
+
logError(coordRoot, err, { phase: "session-start-systemMessage" });
|
|
1002
|
+
}
|
|
1003
|
+
// Cross-machine presence is derived from the authority-safe V3 projection,
|
|
1004
|
+
// not the disposable heartbeat cache. Announce this session, pull peers,
|
|
1005
|
+
// and keep the optional live relay connected.
|
|
1006
|
+
try {
|
|
1007
|
+
publishPresence(coordRoot);
|
|
1008
|
+
fetchPresence(coordRoot);
|
|
1009
|
+
ensureRelayDaemon(coordRoot);
|
|
1010
|
+
}
|
|
1011
|
+
catch (err) {
|
|
1012
|
+
logError(coordRoot, err, { phase: "session-start-presence" });
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function handleSessionEnded(run) {
|
|
1016
|
+
const { coordRoot, behavior, owner } = run;
|
|
1017
|
+
try {
|
|
1018
|
+
cleanupSessionEnd(coordRoot, owner.instance_id);
|
|
1019
|
+
}
|
|
1020
|
+
catch (err) {
|
|
1021
|
+
logError(coordRoot, err, { phase: "session-end-cleanup" });
|
|
1022
|
+
}
|
|
1023
|
+
// Effects: archive the ending agent's journal + force a session-telemetry
|
|
1024
|
+
// sync (via HARNERY_CLAUDE_SESSIONS_FORCE=1), each gated by the behavior.
|
|
1025
|
+
if (behavior.journalMaintenance)
|
|
1026
|
+
journalArchive(coordRoot, owner.instance_id);
|
|
1027
|
+
if (behavior.sessionTelemetrySync)
|
|
1028
|
+
runSessionSyncExtension(coordRoot, true);
|
|
1029
|
+
// Publish the post-cleanup V3 projection so remote machines see this
|
|
1030
|
+
// session disappear without waiting for the stale window.
|
|
1031
|
+
try {
|
|
1032
|
+
publishPresence(coordRoot);
|
|
1033
|
+
}
|
|
1034
|
+
catch (err) {
|
|
1035
|
+
logError(coordRoot, err, { phase: "session-end-presence" });
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
async function handleAgentStarted(run) {
|
|
1039
|
+
const { coordRoot, adapter, owner, sessionId, data } = run;
|
|
1040
|
+
try {
|
|
1041
|
+
const childInstanceId = data.agent_id ?? owner.instance_id;
|
|
1042
|
+
await emitSubagentStartContext(coordRoot, childInstanceId, sessionId, data, adapter);
|
|
1043
|
+
}
|
|
1044
|
+
catch (err) {
|
|
1045
|
+
logError(coordRoot, err, { phase: "subagent-start-context" });
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
async function handleAgentCompleted(run) {
|
|
1049
|
+
const { coordRoot, payload, owner } = run;
|
|
1050
|
+
try {
|
|
1051
|
+
const childInstanceId = payload?.subagent_id ?? payload?.agent_id ?? owner.instance_id;
|
|
1052
|
+
cleanupSessionEnd(coordRoot, childInstanceId);
|
|
1053
|
+
const { reconcileSessionFinalizationV3 } = await import("../agents/session-finalizer-v3.js");
|
|
1054
|
+
reconcileSessionFinalizationV3(coordRoot, { archive_observations: [] });
|
|
1055
|
+
}
|
|
1056
|
+
catch (err) {
|
|
1057
|
+
logError(coordRoot, err, { phase: "subagent-stop-cleanup" });
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async function handleTurnStarted(run) {
|
|
1061
|
+
const { coordRoot, adapter, behavior, payload, owner, sessionId, debugBase, topLevelSession, stopRemediation, v3EventId, } = run;
|
|
1062
|
+
// Effects: reset per-turn sound rate-limit counters + run presence
|
|
1063
|
+
// detection on the prompt, each gated by the behavior.
|
|
1064
|
+
if (behavior.soundEffects)
|
|
1065
|
+
resetSoundCounters(sessionId);
|
|
1066
|
+
if (behavior.promptPresenceDetection) {
|
|
1067
|
+
const prompt = payload?.raw?.prompt ?? "";
|
|
1068
|
+
if (prompt)
|
|
1069
|
+
detectPresence(prompt);
|
|
1070
|
+
}
|
|
1071
|
+
// Presence: throttled pull of peer machines' presence refs (default 60s
|
|
1072
|
+
// interval; the systemMessage below reads whatever is locally known).
|
|
1073
|
+
try {
|
|
1074
|
+
fetchPresence(coordRoot);
|
|
1075
|
+
}
|
|
1076
|
+
catch (err) {
|
|
1077
|
+
logError(coordRoot, err, { phase: "user-prompt-submit-presence" });
|
|
1078
|
+
}
|
|
1079
|
+
let recovery = null;
|
|
1080
|
+
const continuityState = readContextState(coordRoot, sessionId);
|
|
1081
|
+
if (continuityState?.phase === "checkpointed" &&
|
|
1082
|
+
continuityState.compaction_completed_at !== undefined) {
|
|
1065
1083
|
try {
|
|
1066
|
-
|
|
1084
|
+
recovery = prepareContextRecovery(coordRoot, {
|
|
1085
|
+
instanceId: owner.instance_id,
|
|
1086
|
+
sessionId,
|
|
1087
|
+
cwd: payload?.cwd ?? process.cwd(),
|
|
1088
|
+
});
|
|
1067
1089
|
}
|
|
1068
1090
|
catch (err) {
|
|
1069
|
-
logError(coordRoot, err, { phase: "user-prompt
|
|
1070
|
-
}
|
|
1071
|
-
let recovery = null;
|
|
1072
|
-
const continuityState = readContextState(coordRoot, sessionId);
|
|
1073
|
-
if (continuityState?.phase === "checkpointed" &&
|
|
1074
|
-
continuityState.compaction_completed_at !== undefined) {
|
|
1075
|
-
try {
|
|
1076
|
-
recovery = prepareContextRecovery(coordRoot, {
|
|
1077
|
-
instanceId: owner.instance_id,
|
|
1078
|
-
sessionId,
|
|
1079
|
-
cwd: payload?.cwd ?? process.cwd(),
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
catch (err) {
|
|
1083
|
-
logError(coordRoot, err, { phase: "context-recovery-user-prompt" });
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
let hostPromptContext = "";
|
|
1087
|
-
const hostPromptEnabled = hostPromptContextConfig(coordRoot)?.enabled === true;
|
|
1088
|
-
if (hostPromptEnabled && topLevelSession && !stopRemediation) {
|
|
1089
|
-
try {
|
|
1090
|
-
const { runPromptContext } = await import("./prompt-context/runner.js");
|
|
1091
|
-
const result = await runPromptContext({
|
|
1092
|
-
coordRoot,
|
|
1093
|
-
adapter,
|
|
1094
|
-
sessionId,
|
|
1095
|
-
turnId: payload?.turn_id ?? v3EventId ?? sessionId,
|
|
1096
|
-
cwd: payload?.cwd ?? process.cwd(),
|
|
1097
|
-
prompt: payload?.prompt ?? "",
|
|
1098
|
-
});
|
|
1099
|
-
appendDebug(coordRoot, {
|
|
1100
|
-
...debugBase,
|
|
1101
|
-
effect: "prompt-context",
|
|
1102
|
-
...result.audit,
|
|
1103
|
-
});
|
|
1104
|
-
hostPromptContext = result.context ?? "";
|
|
1105
|
-
}
|
|
1106
|
-
catch (err) {
|
|
1107
|
-
logError(coordRoot, err, { phase: "prompt-context-user-prompt" });
|
|
1108
|
-
}
|
|
1091
|
+
logError(coordRoot, err, { phase: "context-recovery-user-prompt" });
|
|
1109
1092
|
}
|
|
1093
|
+
}
|
|
1094
|
+
let hostPromptContext = "";
|
|
1095
|
+
const hostPromptEnabled = hostPromptContextConfig(coordRoot)?.enabled === true;
|
|
1096
|
+
if (hostPromptEnabled && topLevelSession && !stopRemediation) {
|
|
1110
1097
|
try {
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1098
|
+
const { runPromptContext } = await import("./prompt-context/runner.js");
|
|
1099
|
+
const result = await runPromptContext({
|
|
1100
|
+
coordRoot,
|
|
1101
|
+
adapter,
|
|
1102
|
+
sessionId,
|
|
1103
|
+
turnId: payload?.turn_id ?? v3EventId ?? sessionId,
|
|
1104
|
+
cwd: payload?.cwd ?? process.cwd(),
|
|
1105
|
+
prompt: payload?.prompt ?? "",
|
|
1106
|
+
});
|
|
1107
|
+
appendDebug(coordRoot, {
|
|
1108
|
+
...debugBase,
|
|
1109
|
+
effect: "prompt-context",
|
|
1110
|
+
...result.audit,
|
|
1111
|
+
});
|
|
1112
|
+
hostPromptContext = result.context ?? "";
|
|
1115
1113
|
}
|
|
1116
1114
|
catch (err) {
|
|
1117
|
-
logError(coordRoot, err, { phase: "user-prompt
|
|
1115
|
+
logError(coordRoot, err, { phase: "prompt-context-user-prompt" });
|
|
1118
1116
|
}
|
|
1119
1117
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
// stop vs stop-failure split.
|
|
1125
|
-
if (norm.event_type === "turn.completed" && eventName === "stop") {
|
|
1126
|
-
// Claude Code session telemetry sync remains an independent side effect.
|
|
1127
|
-
if (adapter === "claude-code") {
|
|
1128
|
-
runSessionSyncExtension(coordRoot, false);
|
|
1129
|
-
}
|
|
1130
|
-
// Publish after the canonical turn event has landed so the blob carries
|
|
1131
|
-
// current task, activity, and claim state. Re-ensure the relay here because
|
|
1132
|
-
// its daemon intentionally exits when a machine has no live sessions.
|
|
1133
|
-
try {
|
|
1134
|
-
publishPresence(coordRoot);
|
|
1135
|
-
ensureRelayDaemon(coordRoot);
|
|
1118
|
+
try {
|
|
1119
|
+
const injected = await emitUserPromptSubmitSystemMessage(coordRoot, owner.instance_id, sessionId, adapter, payload?.cwd, recovery?.briefing ?? "", hostPromptContext);
|
|
1120
|
+
if (injected && recovery) {
|
|
1121
|
+
completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
|
|
1136
1122
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1123
|
+
}
|
|
1124
|
+
catch (err) {
|
|
1125
|
+
logError(coordRoot, err, { phase: "user-prompt-submit-systemMessage" });
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
async function handleStop(run) {
|
|
1129
|
+
const { coordRoot, adapter, behavior, payload, owner, sessionId, data, debugBase } = run;
|
|
1130
|
+
// Session telemetry sync remains an independent side effect.
|
|
1131
|
+
if (behavior.sessionTelemetrySync)
|
|
1132
|
+
runSessionSyncExtension(coordRoot, false);
|
|
1133
|
+
// Publish after the canonical turn event has landed so the blob carries
|
|
1134
|
+
// current task, activity, and claim state. Re-ensure the relay here because
|
|
1135
|
+
// its daemon intentionally exits when a machine has no live sessions.
|
|
1136
|
+
try {
|
|
1137
|
+
publishPresence(coordRoot);
|
|
1138
|
+
ensureRelayDaemon(coordRoot);
|
|
1139
|
+
}
|
|
1140
|
+
catch (err) {
|
|
1141
|
+
logError(coordRoot, err, { phase: "stop-presence" });
|
|
1142
|
+
}
|
|
1143
|
+
// Stop verdict (V3 ritual + task/status gate). Direct in-process call: the
|
|
1144
|
+
// rule lives in Harnery. agent-hook already emitted this turn.completed
|
|
1145
|
+
// with privacy-safe ritual observations, so the evidence is in the stream.
|
|
1146
|
+
const verdict = evaluateStopHook(coordRoot, {
|
|
1147
|
+
rule: "stop-hook",
|
|
1148
|
+
instance_id: owner.instance_id,
|
|
1149
|
+
session_id: sessionId,
|
|
1150
|
+
adapter,
|
|
1151
|
+
stop_hook_active: payload?.stop_hook_active === true,
|
|
1152
|
+
status_box_present_strict: data.status_box_present_strict === true,
|
|
1153
|
+
session_name_observation_unavailable: typeof data.session_name_unavailable_reason === "string",
|
|
1154
|
+
bypass: coordEnv("AGENT_COORD_BYPASS_STOP") === "1",
|
|
1155
|
+
workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
|
|
1156
|
+
});
|
|
1157
|
+
if (!verdict.allow) {
|
|
1158
|
+
// Backstop for sessions whose evidence can never land (e.g. a headless
|
|
1159
|
+
// child whose status command resolves to a different owner): after
|
|
1160
|
+
// `cap` consecutive blocked stops in one cycle, allow the stop instead
|
|
1161
|
+
// of bouncing the model forever. Cycle boundaries come from the
|
|
1162
|
+
// adapter's continuation flag, so adapters without one are unaffected.
|
|
1163
|
+
const { recordRemediationBlock } = await import("./remediation-cap.js");
|
|
1164
|
+
const remediation = recordRemediationBlock(sessionId, payload?.stop_hook_active === true);
|
|
1165
|
+
if (remediation.exceeded) {
|
|
1166
|
+
appendDebug(coordRoot, {
|
|
1167
|
+
...debugBase,
|
|
1168
|
+
skipped: "stop-remediation-cap-exhausted",
|
|
1169
|
+
blocked_rule: verdict.rule,
|
|
1170
|
+
blocked_count: remediation.count,
|
|
1171
|
+
session_id: sessionId,
|
|
1172
|
+
remediation_cycle_anchor: verdict.remediation_cycle_anchor,
|
|
1173
|
+
});
|
|
1178
1174
|
}
|
|
1179
1175
|
else {
|
|
1180
|
-
|
|
1181
|
-
|
|
1176
|
+
// Adapter-aware enforcement channel: Claude Code honors exit-2 + stderr
|
|
1177
|
+
// as a turn block; Cursor ignores exit codes and re-prompts only via a
|
|
1178
|
+
// `followup_message` it auto-submits.
|
|
1179
|
+
const { emitStopOutcome } = await import("./adapter/output.js");
|
|
1180
|
+
return emitStopOutcome(adapter, { verdict }, coordRoot);
|
|
1182
1181
|
}
|
|
1183
|
-
// An explicit end requested from inside this turn cannot be authoritative
|
|
1184
|
-
// until the adapter has committed turn.completed above. Reconcile only
|
|
1185
|
-
// when such a request exists; any later real work cancels it in the
|
|
1186
|
-
// finalizer instead of being terminated underneath the agent.
|
|
1187
|
-
try {
|
|
1188
|
-
const { hasPendingExplicitSessionEndV3, reconcileSessionFinalizationV3 } = await import("../agents/session-finalizer-v3.js");
|
|
1189
|
-
if (hasPendingExplicitSessionEndV3(coordRoot)) {
|
|
1190
|
-
reconcileSessionFinalizationV3(coordRoot);
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
catch (err) {
|
|
1194
|
-
logError(coordRoot, err, { phase: "stop-explicit-session-finalization" });
|
|
1195
|
-
}
|
|
1196
|
-
if (adapter === "codex")
|
|
1197
|
-
scheduleCodexRuntimeContextRetry(coordRoot, sessionId);
|
|
1198
1182
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
//
|
|
1204
|
-
//
|
|
1205
|
-
//
|
|
1206
|
-
//
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
// never fires its own session.started (SessionStart fires under the
|
|
1212
|
-
// PARENT's session id with source=resume, before the fork id is
|
|
1213
|
-
// minted; verified 2026-08-05), so the fork's new instance first
|
|
1214
|
-
// materializes right here. Gate detection on "no heartbeat yet" so the
|
|
1215
|
-
// transcript scan runs once per instance lifetime, not per tool call.
|
|
1216
|
-
refreshPidmap(coordRoot, owner.instance_id, adapter, payload?.pid);
|
|
1217
|
-
}
|
|
1218
|
-
catch (err) {
|
|
1219
|
-
logError(coordRoot, err, { phase: "pre-tool-use-heal" });
|
|
1220
|
-
}
|
|
1221
|
-
// The suggested name is a pending display latch. The set-task call itself
|
|
1222
|
-
// runs before a name exists; every later tool waits until the exact block
|
|
1223
|
-
// is the first assistant text after the mint result. Cursor supplies
|
|
1224
|
-
// agent_message directly; Claude Code and Codex are resolved from their
|
|
1225
|
-
// JSONL transcripts. Cursor's current narration can provide positive
|
|
1226
|
-
// evidence, but cannot disprove an earlier response; afterAgentResponse
|
|
1227
|
-
// stamps that durable sighting. Later commentary must not erase an
|
|
1228
|
-
// already-correct display, and unavailable evidence must not deadlock
|
|
1229
|
-
// every tool.
|
|
1230
|
-
try {
|
|
1231
|
-
const displayAllowed = await enforcePendingSessionNameDisplay(coordRoot, owner.instance_id, adapter, payload, generationBoundHeartbeat(readHeartbeat(coordRoot, owner.instance_id), recordedGenerationId));
|
|
1232
|
-
if (!displayAllowed)
|
|
1233
|
-
return 0;
|
|
1234
|
-
}
|
|
1235
|
-
catch (err) {
|
|
1236
|
-
logError(coordRoot, err, { phase: "pre-tool-use-session-name" });
|
|
1237
|
-
}
|
|
1238
|
-
// Windows-native Codex + WSL UNC only: block the one cross-shell shape
|
|
1239
|
-
// proven to corrupt argument boundaries. Normal WSL argv calls, literal
|
|
1240
|
-
// bash -s scripts, native Linux/macOS sessions, and every other adapter
|
|
1241
|
-
// pass through. The host instructions name its concrete safe bridge.
|
|
1242
|
-
const unsafeShellReason = unsafeCrossShellReason({
|
|
1243
|
-
adapter,
|
|
1244
|
-
cwd: payload?.cwd,
|
|
1245
|
-
toolName: payload?.tool_name,
|
|
1246
|
-
toolInput: payload?.tool_input,
|
|
1247
|
-
});
|
|
1248
|
-
if (unsafeShellReason) {
|
|
1249
|
-
const { emitDeny } = await import("./adapter/output.js");
|
|
1250
|
-
emitDeny(adapter, unsafeShellReason);
|
|
1251
|
-
return 0;
|
|
1252
|
-
}
|
|
1253
|
-
// G-guard for ALL adapters. Claude Code previously ran this via a
|
|
1254
|
-
// pre-tool-use bash adapter (which called `agent-coord verdict --rule=claim`);
|
|
1255
|
-
// that adapter is now deleted, so agent-hook owns the deny for every adapter.
|
|
1256
|
-
// emitDeny() inside emits the adapter-shaped permission JSON (claude-code +
|
|
1257
|
-
// codex use hookSpecificOutput.permissionDecision; cursor uses .permission).
|
|
1258
|
-
// apply_patch (codex) parses paths from the patch body and runs verdict
|
|
1259
|
-
// per-path; Edit/Write/NotebookEdit resolve a single target. Non-write tools
|
|
1260
|
-
// (incl. Agent) yield no targets and pass through with no deny.
|
|
1261
|
-
let guardAllowed = true;
|
|
1262
|
-
try {
|
|
1263
|
-
guardAllowed = await runPreToolUseGuard(coordRoot, owner.instance_id, sessionId, data, adapter);
|
|
1264
|
-
}
|
|
1265
|
-
catch (err) {
|
|
1266
|
-
logError(coordRoot, err, { phase: "pre-tool-use-guard" });
|
|
1183
|
+
else {
|
|
1184
|
+
const { clearRemediationCount } = await import("./remediation-cap.js");
|
|
1185
|
+
clearRemediationCount(sessionId);
|
|
1186
|
+
}
|
|
1187
|
+
// An explicit end requested from inside this turn cannot be authoritative
|
|
1188
|
+
// until the adapter has committed turn.completed above. Reconcile only
|
|
1189
|
+
// when such a request exists; any later real work cancels it in the
|
|
1190
|
+
// finalizer instead of being terminated underneath the agent.
|
|
1191
|
+
try {
|
|
1192
|
+
const { hasPendingExplicitSessionEndV3, reconcileSessionFinalizationV3 } = await import("../agents/session-finalizer-v3.js");
|
|
1193
|
+
if (hasPendingExplicitSessionEndV3(coordRoot)) {
|
|
1194
|
+
reconcileSessionFinalizationV3(coordRoot);
|
|
1267
1195
|
}
|
|
1268
|
-
|
|
1196
|
+
}
|
|
1197
|
+
catch (err) {
|
|
1198
|
+
logError(coordRoot, err, { phase: "stop-explicit-session-finalization" });
|
|
1199
|
+
}
|
|
1200
|
+
if (behavior.runtimeContextRetry)
|
|
1201
|
+
scheduleRuntimeContextRetry(coordRoot, adapter, sessionId);
|
|
1202
|
+
return undefined;
|
|
1203
|
+
}
|
|
1204
|
+
async function handleToolRequested(run) {
|
|
1205
|
+
const { coordRoot, adapter, behavior, payload, owner, sessionId, data, recordedGenerationId } = run;
|
|
1206
|
+
try {
|
|
1207
|
+
// Recorded fork lineage, heal-path flavor. A forked CC conversation
|
|
1208
|
+
// never fires its own session.started (SessionStart fires under the
|
|
1209
|
+
// PARENT's session id with source=resume, before the fork id is
|
|
1210
|
+
// minted; verified 2026-08-05), so the fork's new instance first
|
|
1211
|
+
// materializes right here. Gate detection on "no heartbeat yet" so the
|
|
1212
|
+
// transcript scan runs once per instance lifetime, not per tool call.
|
|
1213
|
+
refreshPidmap(coordRoot, owner.instance_id, adapter, payload?.pid);
|
|
1214
|
+
}
|
|
1215
|
+
catch (err) {
|
|
1216
|
+
logError(coordRoot, err, { phase: "pre-tool-use-heal" });
|
|
1217
|
+
}
|
|
1218
|
+
// The suggested name is a pending display latch. The set-task call itself
|
|
1219
|
+
// runs before a name exists; every later tool waits until the exact block
|
|
1220
|
+
// is the first assistant text after the mint result. Cursor supplies
|
|
1221
|
+
// agent_message directly; Claude Code and Codex are resolved from their
|
|
1222
|
+
// JSONL transcripts. Cursor's current narration can provide positive
|
|
1223
|
+
// evidence, but cannot disprove an earlier response; afterAgentResponse
|
|
1224
|
+
// stamps that durable sighting. Later commentary must not erase an
|
|
1225
|
+
// already-correct display, and unavailable evidence must not deadlock
|
|
1226
|
+
// every tool.
|
|
1227
|
+
try {
|
|
1228
|
+
const displayAllowed = await enforcePendingSessionNameDisplay(coordRoot, owner.instance_id, adapter, payload, generationBoundHeartbeat(readHeartbeat(coordRoot, owner.instance_id), recordedGenerationId));
|
|
1229
|
+
if (!displayAllowed)
|
|
1269
1230
|
return 0;
|
|
1270
1231
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1232
|
+
catch (err) {
|
|
1233
|
+
logError(coordRoot, err, { phase: "pre-tool-use-session-name" });
|
|
1234
|
+
}
|
|
1235
|
+
// Windows-native Codex + WSL UNC only: block the one cross-shell shape
|
|
1236
|
+
// proven to corrupt argument boundaries. Normal WSL argv calls, literal
|
|
1237
|
+
// bash -s scripts, native Linux/macOS sessions, and every other adapter
|
|
1238
|
+
// pass through. The host instructions name its concrete safe bridge.
|
|
1239
|
+
const unsafeShellReason = behavior.unsafeCrossShellReason?.({
|
|
1240
|
+
cwd: payload?.cwd,
|
|
1241
|
+
toolName: payload?.tool_name,
|
|
1242
|
+
toolInput: payload?.tool_input,
|
|
1243
|
+
}) ?? null;
|
|
1244
|
+
if (unsafeShellReason) {
|
|
1245
|
+
const { emitDeny } = await import("./adapter/output.js");
|
|
1246
|
+
emitDeny(adapter, unsafeShellReason);
|
|
1247
|
+
return 0;
|
|
1248
|
+
}
|
|
1249
|
+
const waiterReason = shellWaiterReason(payload?.tool_name, payload?.tool_input);
|
|
1250
|
+
if (waiterReason) {
|
|
1251
|
+
const { emitDeny } = await import("./adapter/output.js");
|
|
1252
|
+
emitDeny(adapter, waiterReason);
|
|
1253
|
+
return 0;
|
|
1254
|
+
}
|
|
1255
|
+
// G-guard for ALL adapters. Claude Code previously ran this via a
|
|
1256
|
+
// pre-tool-use bash adapter (which called `agent-coord verdict --rule=claim`);
|
|
1257
|
+
// that adapter is now deleted, so agent-hook owns the deny for every adapter.
|
|
1258
|
+
// emitDeny() inside emits the adapter-shaped permission JSON (claude-code +
|
|
1259
|
+
// codex use hookSpecificOutput.permissionDecision; cursor uses .permission).
|
|
1260
|
+
// apply_patch (codex) parses paths from the patch body and runs verdict
|
|
1261
|
+
// per-path; Edit/Write/NotebookEdit resolve a single target. Non-write tools
|
|
1262
|
+
// (incl. Agent) yield no targets and pass through with no deny.
|
|
1263
|
+
let guardAllowed = true;
|
|
1264
|
+
try {
|
|
1265
|
+
guardAllowed = await runPreToolUseGuard(coordRoot, owner.instance_id, sessionId, data, adapter);
|
|
1266
|
+
}
|
|
1267
|
+
catch (err) {
|
|
1268
|
+
logError(coordRoot, err, { phase: "pre-tool-use-guard" });
|
|
1269
|
+
}
|
|
1270
|
+
if (!guardAllowed)
|
|
1271
|
+
return 0;
|
|
1272
|
+
return undefined;
|
|
1273
|
+
}
|
|
1274
|
+
async function handlePostToolUse(run) {
|
|
1275
|
+
const { coordRoot, adapter, eventName, payload, owner } = run;
|
|
1276
|
+
// Inject the full instruction only at the successful tool boundary that
|
|
1277
|
+
// actually minted or retried a name: the first non-empty set-task, a
|
|
1278
|
+
// pending-name set-task retry, or the transition to lifecycle done. The
|
|
1279
|
+
// coordination row intentionally remains pending until transcript evidence
|
|
1280
|
+
// catches up, so the latch alone cannot drive injection: that would print
|
|
1281
|
+
// the same block after every later tool.
|
|
1282
|
+
//
|
|
1283
|
+
// Between those two extremes sits a bounded reminder. Since ADR 0182 the
|
|
1284
|
+
// PreToolUse gate allows a tool when transcript evidence is unavailable
|
|
1285
|
+
// and says so only on stderr, which the model never reads. A model that
|
|
1286
|
+
// skipped the block after the mint was therefore never asked again
|
|
1287
|
+
// (ADR 0183). While the requested title is still pending, ordinary tool
|
|
1288
|
+
// results carry an idempotent reminder up to a fixed count; coordination
|
|
1289
|
+
// chores (set-task, status, suggest-name) are skipped because a mint or
|
|
1290
|
+
// retry response already carries the full instruction and an end-turn
|
|
1291
|
+
// status is the wrong place to ask.
|
|
1292
|
+
try {
|
|
1293
|
+
const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
|
|
1294
|
+
const name = sessionNameDisplayPending(row);
|
|
1295
|
+
if (name && toolResponseMintedSessionName(payload?.tool_response, name)) {
|
|
1296
|
+
// Record the title before asking for it. The suggestion can still
|
|
1297
|
+
// change afterwards (an assigned-name rewrite, a lifecycle re-mint, a
|
|
1298
|
+
// rebuilt cache), and the agent must not be stranded for displaying
|
|
1299
|
+
// exactly what it was handed.
|
|
1300
|
+
stampSessionNameRequested(coordRoot, owner.instance_id, name);
|
|
1301
|
+
const { emitContext } = await import("./adapter/output.js");
|
|
1302
|
+
emitContext(adapter, "PostToolUse", sessionNameDisplayInstruction(name));
|
|
1303
|
+
}
|
|
1304
|
+
else if (name) {
|
|
1305
|
+
const due = sessionNameDisplayReminderDue(row);
|
|
1306
|
+
const command = extractBashCommand(payload?.tool_name, payload?.tool_input);
|
|
1307
|
+
if (due && !isSessionNameRemediationCommand(command, resolveBinName(coordRoot))) {
|
|
1308
|
+
stampSessionNameReminded(coordRoot, owner.instance_id, due);
|
|
1309
|
+
appendDebug(coordRoot, {
|
|
1310
|
+
ts: new Date().toISOString(),
|
|
1311
|
+
event_name: eventName,
|
|
1312
|
+
adapter,
|
|
1313
|
+
instance_id: owner.instance_id,
|
|
1314
|
+
effect: "session-name-display-reminded",
|
|
1315
|
+
session_name_pending: due,
|
|
1316
|
+
reminders: (row?.session_name_display_reminders ?? 0) + 1,
|
|
1317
|
+
});
|
|
1286
1318
|
const { emitContext } = await import("./adapter/output.js");
|
|
1287
|
-
emitContext(adapter, "PostToolUse",
|
|
1319
|
+
emitContext(adapter, "PostToolUse", sessionNameDisplayReminder(due));
|
|
1288
1320
|
}
|
|
1289
1321
|
}
|
|
1290
|
-
catch (err) {
|
|
1291
|
-
logError(coordRoot, err, { phase: "post-tool-use-session-name" });
|
|
1292
|
-
}
|
|
1293
1322
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1323
|
+
catch (err) {
|
|
1324
|
+
logError(coordRoot, err, { phase: "post-tool-use-session-name" });
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function handlePostToolUseFailure(run) {
|
|
1328
|
+
const { coordRoot, payload, owner, data } = run;
|
|
1329
|
+
try {
|
|
1330
|
+
releaseClaimOnFailure(coordRoot, owner.instance_id, data, payload?.raw);
|
|
1331
|
+
}
|
|
1332
|
+
catch (err) {
|
|
1333
|
+
logError(coordRoot, err, { phase: "post-tool-use-failure-release" });
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
async function handleCompletedResponse(run) {
|
|
1337
|
+
const { coordRoot, adapter, eventName, raw, debugBase, ledgerRoute, hookStartedAt, hookClock } = run;
|
|
1338
|
+
const payload = parsePayload(raw, adapter);
|
|
1339
|
+
const owner = resolveOwner({ payload: payload?.raw ?? null, coordRoot });
|
|
1340
|
+
if (!owner) {
|
|
1341
|
+
appendDebug(coordRoot, { ...debugBase, skipped: "no-owner-resolved" });
|
|
1342
|
+
return 0;
|
|
1303
1343
|
}
|
|
1344
|
+
const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
|
|
1345
|
+
const name = sessionNameDisplayPending(row);
|
|
1346
|
+
const text = typeof payload?.raw.text === "string" ? payload.raw.text : "";
|
|
1347
|
+
const sighting = matchSessionNameDisplay(row, text);
|
|
1348
|
+
if (sighting) {
|
|
1349
|
+
stampSessionNameSeen(coordRoot, owner.instance_id, sighting.pending);
|
|
1350
|
+
appendDebug(coordRoot, {
|
|
1351
|
+
...debugBase,
|
|
1352
|
+
effect: "session-name-display-stamped",
|
|
1353
|
+
session_name_pending: sighting.pending,
|
|
1354
|
+
...(sighting.displayed === sighting.pending
|
|
1355
|
+
? {}
|
|
1356
|
+
: { session_name_displayed: sighting.displayed, session_name_drift: true }),
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
else if (!name) {
|
|
1360
|
+
// Nothing was owed. Distinct from a miss: conflating the two is what made
|
|
1361
|
+
// a latched Cursor session undiagnosable from this log.
|
|
1362
|
+
appendDebug(coordRoot, { ...debugBase, skipped: "no-pending-session-name" });
|
|
1363
|
+
}
|
|
1364
|
+
else {
|
|
1365
|
+
appendDebug(coordRoot, {
|
|
1366
|
+
...debugBase,
|
|
1367
|
+
skipped: "session-name-block-absent",
|
|
1368
|
+
session_name_pending: name,
|
|
1369
|
+
reply_bytes: text.length,
|
|
1370
|
+
reply_leads_with_fence: /^\s*`{3,}/.test(text),
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
if (ledgerRoute.state === "blocked") {
|
|
1374
|
+
appendDebug(coordRoot, {
|
|
1375
|
+
...debugBase,
|
|
1376
|
+
skipped: "v3-control-blocked",
|
|
1377
|
+
reason: ledgerRoute.reason,
|
|
1378
|
+
owner_source: owner.source,
|
|
1379
|
+
});
|
|
1380
|
+
return 0;
|
|
1381
|
+
}
|
|
1382
|
+
const statusBoxPresentStrict = scanAssistantStatusBoxPresent(undefined, text);
|
|
1383
|
+
// The hook parser needs the completed reply long enough to scan it, but
|
|
1384
|
+
// durable hook intake must never retain that body. Keep every other native
|
|
1385
|
+
// field so turn correlation remains available to the producer.
|
|
1386
|
+
const { text: _completedReply, ...privacySafeRaw } = payload?.raw ?? {};
|
|
1387
|
+
const privacySafePayload = payload ? { ...payload, raw: privacySafeRaw } : null;
|
|
1388
|
+
const v3Result = recordLiveHookSignalV3({
|
|
1389
|
+
coordRoot,
|
|
1390
|
+
route: ledgerRoute,
|
|
1391
|
+
eventName,
|
|
1392
|
+
payload: privacySafePayload,
|
|
1393
|
+
adapter,
|
|
1394
|
+
instanceId: owner.instance_id,
|
|
1395
|
+
hook_name: eventName,
|
|
1396
|
+
hook_duration_ms: Math.max(0, Math.floor(performance.now() - hookStartedAt)),
|
|
1397
|
+
monotonic_ns: hookClock.monotonic_ns,
|
|
1398
|
+
turn_ritual: {
|
|
1399
|
+
status_box_present: statusBoxPresentStrict,
|
|
1400
|
+
status_box_present_strict: statusBoxPresentStrict,
|
|
1401
|
+
session_name_required: name !== undefined,
|
|
1402
|
+
session_name_present: name ? assistantTextStartsWithSessionNameBlock(text, name) : false,
|
|
1403
|
+
},
|
|
1404
|
+
});
|
|
1405
|
+
observeHookDebug(hookHealthState, { event_v3_state: v3Result.state });
|
|
1406
|
+
appendDebug(coordRoot, {
|
|
1407
|
+
...debugBase,
|
|
1408
|
+
effect: "cursor-response-ritual-observed",
|
|
1409
|
+
owner_source: owner.source,
|
|
1410
|
+
event_v3_state: v3Result.state,
|
|
1411
|
+
status_box_present_strict: statusBoxPresentStrict,
|
|
1412
|
+
...(v3Result.state === "observed"
|
|
1413
|
+
? {
|
|
1414
|
+
generation_id: v3Result.generation_id,
|
|
1415
|
+
turn_id: v3Result.turn_id,
|
|
1416
|
+
response_observed_at: v3Result.observed_at,
|
|
1417
|
+
}
|
|
1418
|
+
: {}),
|
|
1419
|
+
});
|
|
1304
1420
|
return 0;
|
|
1305
1421
|
}
|
|
1306
|
-
function
|
|
1422
|
+
function scheduleRuntimeContextRetry(coordRoot, adapter, nativeSessionId) {
|
|
1307
1423
|
const modulePath = process.argv[1];
|
|
1308
1424
|
if (!modulePath)
|
|
1309
1425
|
return;
|
|
1310
1426
|
try {
|
|
1311
|
-
const child = spawn(process.execPath, [modulePath, "runtime-context-retry", "--adapter",
|
|
1427
|
+
const child = spawn(process.execPath, [modulePath, "runtime-context-retry", "--adapter", adapter, "--session-id", nativeSessionId], {
|
|
1312
1428
|
cwd: coordRoot,
|
|
1313
1429
|
detached: true,
|
|
1314
1430
|
stdio: "ignore",
|
|
@@ -1322,7 +1438,7 @@ function scheduleCodexRuntimeContextRetry(coordRoot, nativeSessionId) {
|
|
|
1322
1438
|
}
|
|
1323
1439
|
async function runRuntimeContextRetryWorker(coordRoot, adapter, extra) {
|
|
1324
1440
|
const nativeSessionId = extraArg(extra, "--session-id");
|
|
1325
|
-
if (adapter
|
|
1441
|
+
if (!adapter || !adapterBehavior(adapter).runtimeContextRetry || !nativeSessionId)
|
|
1326
1442
|
return 0;
|
|
1327
1443
|
for (const [index, delayMs] of [400, 1_000].entries()) {
|
|
1328
1444
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
@@ -1340,6 +1456,18 @@ async function runRuntimeContextRetryWorker(coordRoot, adapter, extra) {
|
|
|
1340
1456
|
}
|
|
1341
1457
|
return 0;
|
|
1342
1458
|
}
|
|
1459
|
+
function reportSessionNameObservationUnavailable(coordRoot, instanceId, adapter, eventName, reason) {
|
|
1460
|
+
appendDebug(coordRoot, {
|
|
1461
|
+
ts: new Date().toISOString(),
|
|
1462
|
+
event_name: eventName,
|
|
1463
|
+
adapter,
|
|
1464
|
+
instance_id: instanceId,
|
|
1465
|
+
effect: "session-name-observation-unavailable",
|
|
1466
|
+
reason,
|
|
1467
|
+
action: "fail-open",
|
|
1468
|
+
});
|
|
1469
|
+
process.stderr.write(`Harnery: session-name observation unavailable (${reason}); allowing this ${eventName} without display enforcement. The name remains pending.\n`);
|
|
1470
|
+
}
|
|
1343
1471
|
async function enforcePendingSessionNameDisplay(coordRoot, instanceId, adapter, payload, coordination) {
|
|
1344
1472
|
const name = sessionNameDisplayPending(coordination);
|
|
1345
1473
|
if (!name)
|
|
@@ -1353,11 +1481,7 @@ async function enforcePendingSessionNameDisplay(coordRoot, instanceId, adapter,
|
|
|
1353
1481
|
return true;
|
|
1354
1482
|
}
|
|
1355
1483
|
if (inspection.state === "unavailable") {
|
|
1356
|
-
|
|
1357
|
-
// unavailable transcript cannot become evidence, and repeating a generic
|
|
1358
|
-
// reminder on every tool creates an unbounded Codex loop. Keep the latch
|
|
1359
|
-
// honestly pending and fail open without another instruction. A later
|
|
1360
|
-
// readable transcript can still verify it.
|
|
1484
|
+
reportSessionNameObservationUnavailable(coordRoot, instanceId, adapter, "pre-tool-use", inspection.reason);
|
|
1361
1485
|
return true;
|
|
1362
1486
|
}
|
|
1363
1487
|
// Record the refusal. Without this the gate was the one enforcement path
|
|
@@ -1384,19 +1508,18 @@ async function enforcePendingSessionNameDisplay(coordRoot, instanceId, adapter,
|
|
|
1384
1508
|
* other opening text for every accepted title.
|
|
1385
1509
|
*/
|
|
1386
1510
|
function inspectDisplayEvidence(instanceId, adapter, payload, coordination) {
|
|
1387
|
-
|
|
1388
|
-
|
|
1511
|
+
const behavior = adapterBehavior(adapter);
|
|
1512
|
+
const inlineText = behavior.inlineAssistantText?.(payload);
|
|
1513
|
+
if (inlineText !== undefined) {
|
|
1514
|
+
return matchSessionNameDisplay(coordination, inlineText)
|
|
1389
1515
|
? { state: "present" }
|
|
1390
1516
|
: { state: "unavailable", reason: "transcript_not_ready" };
|
|
1391
1517
|
}
|
|
1392
|
-
//
|
|
1393
|
-
// inspection permanently unavailable and the latch never stamped.
|
|
1394
|
-
// the
|
|
1395
|
-
// the latch closes after one success.
|
|
1396
|
-
const transcriptPath = payload?.transcript_path ??
|
|
1397
|
-
(adapter === "codex"
|
|
1398
|
-
? discoverCodexSessionTranscript(payload?.session_id ?? instanceId)
|
|
1399
|
-
: undefined);
|
|
1518
|
+
// Some adapters omit transcript_path on every event, which left this
|
|
1519
|
+
// inspection permanently unavailable and the latch never stamped. The
|
|
1520
|
+
// behavior may discover the transcript by session id; the scan only runs
|
|
1521
|
+
// while a name is pending, so the latch closes after one success.
|
|
1522
|
+
const transcriptPath = payload?.transcript_path ?? behavior.discoverTranscript?.(payload?.session_id ?? instanceId);
|
|
1400
1523
|
let verdict = { state: "absent" };
|
|
1401
1524
|
for (const candidate of sessionNameDisplayAcceptedNames(coordination)) {
|
|
1402
1525
|
const inspection = inspectSessionNameDisplayImmediately(transcriptPath, candidate, assistantTextStartsWithSessionNameBlock);
|
|
@@ -1671,7 +1794,7 @@ function releaseClaimOnFailure(coordRoot, instanceId, data, rawPayload) {
|
|
|
1671
1794
|
coordRoot,
|
|
1672
1795
|
owner: instanceId,
|
|
1673
1796
|
nativeSessionId: before?.session_id ?? instanceId,
|
|
1674
|
-
adapter:
|
|
1797
|
+
adapter: sharedAdapterFromPlatform(before?.platform, { context: "hook-claim-release" }),
|
|
1675
1798
|
operation: "released",
|
|
1676
1799
|
path: canonical,
|
|
1677
1800
|
access: "write",
|
|
@@ -1707,22 +1830,18 @@ function cleanupSessionEnd(coordRoot, instanceId) {
|
|
|
1707
1830
|
}
|
|
1708
1831
|
}
|
|
1709
1832
|
async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionId, adapter, workspaceCwd, recoveryBriefing = "", hostPromptContext = "") {
|
|
1833
|
+
const behavior = adapterBehavior(adapter);
|
|
1710
1834
|
const { renderPromptContext } = await import("../agents/render/prompt-context.js");
|
|
1711
1835
|
let additionalContext = renderPromptContext({
|
|
1712
1836
|
coordRoot,
|
|
1713
1837
|
instanceId,
|
|
1714
1838
|
sessionId,
|
|
1715
1839
|
sessionNameNudge: true,
|
|
1716
|
-
|
|
1717
|
-
hostPromptReminder: adapter === "codex" || adapter === "cursor",
|
|
1718
|
-
statusFooterNudge: adapter === "codex",
|
|
1719
|
-
turnRitualNudge: adapter === "claude-code" ? adapter : undefined,
|
|
1840
|
+
...behavior.promptContextNudges,
|
|
1720
1841
|
}).trim();
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
|
|
1725
|
-
}
|
|
1842
|
+
const fileLinkContext = behavior.fileLinkContext?.(coordRoot, workspaceCwd) ?? "";
|
|
1843
|
+
if (fileLinkContext) {
|
|
1844
|
+
additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
|
|
1726
1845
|
}
|
|
1727
1846
|
if (recoveryBriefing) {
|
|
1728
1847
|
additionalContext = [additionalContext, recoveryBriefing].filter(Boolean).join("\n\n");
|
|
@@ -1764,18 +1883,10 @@ async function emitSubagentStartContext(coordRoot, instanceId, sessionId, data,
|
|
|
1764
1883
|
emitContext(adapter, "SubagentStart", combined);
|
|
1765
1884
|
}
|
|
1766
1885
|
function adapterPlatform(adapter) {
|
|
1767
|
-
if (adapter === "claude-code")
|
|
1768
|
-
return "claude-code";
|
|
1769
1886
|
return adapter;
|
|
1770
1887
|
}
|
|
1771
|
-
function adapterFromPlatform(platform) {
|
|
1772
|
-
if (platform === "cursor")
|
|
1773
|
-
return "cursor";
|
|
1774
|
-
if (platform === "codex")
|
|
1775
|
-
return "codex";
|
|
1776
|
-
return "claude-code";
|
|
1777
|
-
}
|
|
1778
1888
|
async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, emittedData, adapter, recoveryBriefing = "", backupCue = "") {
|
|
1889
|
+
const behavior = adapterBehavior(adapter);
|
|
1779
1890
|
const workflowChild = coordEnv("WORKFLOW_CHILD") === "1";
|
|
1780
1891
|
let additionalContext = "";
|
|
1781
1892
|
// Opportunistic reconciliation makes normal session starts a failsafe for
|
|
@@ -1795,14 +1906,14 @@ async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, e
|
|
|
1795
1906
|
instanceId,
|
|
1796
1907
|
sessionId,
|
|
1797
1908
|
agentName: agentName || undefined,
|
|
1798
|
-
platformLabel:
|
|
1909
|
+
platformLabel: behavior.sessionStartPlatformLabel,
|
|
1799
1910
|
}).trim();
|
|
1800
1911
|
}
|
|
1801
|
-
// Effect (
|
|
1802
|
-
// context. Was a standalone additionalContext emission
|
|
1803
|
-
// journal-on-start adapter; now that agent-hook is the
|
|
1804
|
-
// entry, it folds in here.
|
|
1805
|
-
if (
|
|
1912
|
+
// Effect (journal-maintaining adapters): merge the journal recovery cue into
|
|
1913
|
+
// the session-start context. Was a standalone additionalContext emission
|
|
1914
|
+
// from the previous journal-on-start adapter; now that agent-hook is the
|
|
1915
|
+
// single SessionStart entry, it folds in here.
|
|
1916
|
+
if (behavior.journalMaintenance && !workflowChild) {
|
|
1806
1917
|
const cue = journalRecoveryCue(coordRoot);
|
|
1807
1918
|
if (cue)
|
|
1808
1919
|
additionalContext = [additionalContext, cue].filter(Boolean).join("\n\n");
|
|
@@ -1813,19 +1924,14 @@ async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, e
|
|
|
1813
1924
|
if (backupCue && !workflowChild) {
|
|
1814
1925
|
additionalContext = [additionalContext, backupCue].filter(Boolean).join("\n\n");
|
|
1815
1926
|
}
|
|
1816
|
-
if (
|
|
1817
|
-
const fileLinkContext =
|
|
1927
|
+
if (!workflowChild && behavior.bridgeFor(emittedData.cwd)) {
|
|
1928
|
+
const fileLinkContext = behavior.fileLinkContext?.(coordRoot, emittedData.cwd) ?? "";
|
|
1818
1929
|
if (fileLinkContext) {
|
|
1819
1930
|
additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
|
|
1820
1931
|
}
|
|
1821
|
-
const
|
|
1822
|
-
if (
|
|
1823
|
-
additionalContext = [
|
|
1824
|
-
additionalContext,
|
|
1825
|
-
`Harnery hybrid warning: ${bridge.detail}. Run \`${resolveBinName(coordRoot)} doctor\` for the repair hint.`,
|
|
1826
|
-
]
|
|
1827
|
-
.filter(Boolean)
|
|
1828
|
-
.join("\n\n");
|
|
1932
|
+
const warning = behavior.bridgeWarning?.(coordRoot, emittedData.cwd);
|
|
1933
|
+
if (warning) {
|
|
1934
|
+
additionalContext = [additionalContext, warning].filter(Boolean).join("\n\n");
|
|
1829
1935
|
}
|
|
1830
1936
|
}
|
|
1831
1937
|
const { emitContext } = await import("./adapter/output.js");
|