harnery 0.40.0 → 0.42.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 +4 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +34 -20
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +113 -12
- package/dist/commands/browse-ai.d.ts.map +1 -1
- package/dist/commands/browse-ai.js +4 -0
- package/dist/commands/browse.d.ts +164 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +388 -45
- package/dist/commands/checkpoint.js +8 -12
- package/dist/commands/claude-desktop.d.ts.map +1 -1
- package/dist/commands/claude-desktop.js +52 -0
- 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/disk.d.ts +6 -0
- package/dist/commands/disk.d.ts.map +1 -0
- package/dist/commands/disk.js +80 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +2 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +58 -3
- package/dist/commands/fetch.d.ts.map +1 -1
- package/dist/commands/fetch.js +10 -0
- package/dist/commands/files.d.ts +16 -0
- package/dist/commands/files.d.ts.map +1 -1
- package/dist/commands/files.js +54 -2
- package/dist/commands/init.d.ts +21 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +271 -109
- 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/rm.d.ts +4 -0
- package/dist/commands/rm.d.ts.map +1 -0
- package/dist/commands/rm.js +87 -0
- package/dist/commands/servers.d.ts +7 -0
- package/dist/commands/servers.d.ts.map +1 -0
- package/dist/commands/servers.js +394 -0
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +82 -2
- 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 +29 -3
- package/dist/core/adapters/profiles.d.ts.map +1 -1
- package/dist/core/adapters/profiles.js +53 -1
- package/dist/core/adapters/registry.d.ts.map +1 -1
- package/dist/core/adapters/registry.js +33 -0
- package/dist/core/adapters/types.d.ts +9 -0
- package/dist/core/adapters/types.d.ts.map +1 -1
- 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 +25 -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/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +2 -0
- 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 +20 -0
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +61 -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 +11 -3
- package/dist/core/artifacts/index.d.ts +140 -4
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +526 -52
- package/dist/core/config.d.ts +73 -11
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +142 -3
- 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 +5 -4
- 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 +125 -101
- package/dist/core/events/v3/projection.js +9 -1
- 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 +46 -1
- package/dist/core/hooks/adapter/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +36 -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/runtime-telemetry.js +3 -1
- package/dist/core/hooks/adapter/wiring.d.ts +52 -8
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +133 -12
- package/dist/core/hooks/cli.js +676 -531
- 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/run-markers.d.ts +37 -0
- package/dist/core/hooks/run-markers.d.ts.map +1 -0
- package/dist/core/hooks/run-markers.js +109 -0
- 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/servers/index.d.ts +211 -0
- package/dist/core/servers/index.d.ts.map +1 -0
- package/dist/core/servers/index.js +560 -0
- package/dist/core/servers/net.d.ts +58 -0
- package/dist/core/servers/net.d.ts.map +1 -0
- package/dist/core/servers/net.js +270 -0
- package/dist/core/servers/tunnels.d.ts +14 -0
- package/dist/core/servers/tunnels.d.ts.map +1 -0
- package/dist/core/servers/tunnels.js +33 -0
- package/dist/core/storage/atomic-json.d.ts.map +1 -1
- package/dist/core/storage/atomic-json.js +7 -3
- package/dist/core/storage/builtins.js +1 -0
- 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/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +30 -1
- 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/sandbox-projection.d.ts +13 -2
- package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
- package/dist/core/workflow/sandbox-projection.js +23 -0
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +6 -1
- package/dist/core/workflow/spawn-codex.d.ts +2 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +20 -1
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +6 -1
- 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 +135 -0
- package/dist/core/workflow/transcript.d.ts.map +1 -1
- package/dist/core/workflow/transcript.js +3 -2
- package/dist/core/workflow/types.d.ts +47 -2
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/worker-access.d.ts +92 -0
- package/dist/core/workflow/worker-access.d.ts.map +1 -0
- package/dist/core/workflow/worker-access.js +450 -0
- 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/batch-steps.d.ts +18 -0
- package/dist/lib/browser/batch-steps.d.ts.map +1 -0
- package/dist/lib/browser/batch-steps.js +82 -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/collect/collector.d.ts +99 -0
- package/dist/lib/browser/collect/collector.d.ts.map +1 -0
- package/dist/lib/browser/collect/collector.js +202 -0
- package/dist/lib/browser/collect/fields.d.ts +31 -0
- package/dist/lib/browser/collect/fields.d.ts.map +1 -0
- package/dist/lib/browser/collect/fields.js +75 -0
- package/dist/lib/browser/collect/index.d.ts +6 -0
- package/dist/lib/browser/collect/index.d.ts.map +1 -0
- package/dist/lib/browser/collect/index.js +5 -0
- package/dist/lib/browser/collect/page-driver.d.ts +14 -0
- package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
- package/dist/lib/browser/collect/page-driver.js +306 -0
- package/dist/lib/browser/collect/presets.d.ts +52 -0
- package/dist/lib/browser/collect/presets.d.ts.map +1 -0
- package/dist/lib/browser/collect/presets.js +176 -0
- package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
- package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
- package/dist/lib/browser/collect/scroll-plan.js +118 -0
- 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 +3 -2
- 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/page-review-judge.d.ts +2 -0
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +13 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +1 -0
- 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/browser/windows-chrome.d.ts +59 -0
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
- package/dist/lib/browser/windows-chrome.js +179 -0
- package/dist/lib/claude-desktop-share.d.ts +136 -0
- package/dist/lib/claude-desktop-share.d.ts.map +1 -0
- package/dist/lib/claude-desktop-share.js +394 -0
- package/dist/lib/disk-usage.d.ts +50 -0
- package/dist/lib/disk-usage.d.ts.map +1 -0
- package/dist/lib/disk-usage.js +303 -0
- package/dist/lib/docs-lint.d.ts +6 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +8 -3
- 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/guarded-remove.d.ts +34 -0
- package/dist/lib/guarded-remove.d.ts.map +1 -0
- package/dist/lib/guarded-remove.js +310 -0
- 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/instructions/templates.js +8 -8
- 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/error-page.d.ts +1 -1
- package/dist/lib/tunnel/error-page.d.ts.map +1 -1
- package/dist/lib/tunnel/error-page.js +19 -8
- 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 +34 -10
- package/dist/lib/tunnel/path-scope.d.ts +22 -0
- package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
- package/dist/lib/tunnel/path-scope.js +62 -0
- package/dist/lib/tunnel/state.d.ts +5 -0
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +17 -3
- 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 +16 -4
- package/schemas/config.schema.json +104 -0
- package/schemas/event-v3.schema.json +164 -0
- package/src/commander.ts +26 -0
- package/src/commands/agents.ts +47 -18
- package/src/commands/artifacts.ts +171 -19
- package/src/commands/browse-ai.ts +8 -0
- package/src/commands/browse.ts +570 -48
- package/src/commands/checkpoint.ts +7 -9
- package/src/commands/claude-desktop.ts +61 -0
- package/src/commands/deinit.ts +9 -4
- package/src/commands/disk.ts +114 -0
- package/src/commands/docs.ts +2 -0
- package/src/commands/doctor.ts +63 -3
- package/src/commands/fetch.ts +14 -0
- package/src/commands/files.ts +93 -2
- package/src/commands/init.ts +334 -122
- package/src/commands/instructions.ts +8 -4
- package/src/commands/ledger-v3.ts +2 -1
- package/src/commands/rm.ts +115 -0
- package/src/commands/servers.ts +506 -0
- package/src/commands/tunnel.ts +102 -2
- 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 +63 -1
- package/src/core/adapters/registry.ts +37 -0
- package/src/core/adapters/types.ts +10 -0
- package/src/core/agents/canonical-emit.ts +2 -5
- package/src/core/agents/cli.ts +26 -17
- package/src/core/agents/coord-client.ts +9 -3
- package/src/core/agents/render/session-context.ts +1 -0
- 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 +74 -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 +12 -3
- package/src/core/artifacts/index.ts +701 -47
- package/src/core/config.ts +206 -4
- package/src/core/context/index.ts +2 -1
- package/src/core/conversations/archive.ts +4 -3
- package/src/core/diagnostics/bundle.ts +5 -3
- 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 +139 -114
- package/src/core/events/v3/projection.ts +11 -1
- 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 +72 -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/runtime-telemetry.ts +3 -1
- package/src/core/hooks/adapter/wiring.ts +198 -16
- package/src/core/hooks/cli.ts +776 -539
- 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/run-markers.ts +132 -0
- 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/servers/index.ts +712 -0
- package/src/core/servers/net.ts +283 -0
- package/src/core/servers/tunnels.ts +39 -0
- package/src/core/storage/atomic-json.ts +7 -3
- package/src/core/storage/builtins.ts +1 -0
- 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/engine.ts +38 -1
- package/src/core/workflow/live-session-v3.ts +2 -3
- package/src/core/workflow/run-state.ts +3 -2
- package/src/core/workflow/sandbox-projection.ts +38 -2
- package/src/core/workflow/spawn-claude.ts +5 -1
- package/src/core/workflow/spawn-codex.ts +18 -1
- package/src/core/workflow/spawn-cursor.ts +5 -1
- package/src/core/workflow/spawn-opencode.ts +153 -0
- package/src/core/workflow/transcript.ts +3 -2
- package/src/core/workflow/types.ts +62 -2
- package/src/core/workflow/worker-access.ts +525 -0
- 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/batch-steps.ts +79 -0
- package/src/lib/browser/client.ts +147 -9
- package/src/lib/browser/collect/collector.ts +306 -0
- package/src/lib/browser/collect/fields.ts +87 -0
- package/src/lib/browser/collect/index.ts +47 -0
- package/src/lib/browser/collect/page-driver.ts +327 -0
- package/src/lib/browser/collect/presets.ts +237 -0
- package/src/lib/browser/collect/scroll-plan.ts +179 -0
- package/src/lib/browser/identity.ts +64 -0
- package/src/lib/browser/index.ts +16 -1
- package/src/lib/browser/launch-args.ts +53 -1
- package/src/lib/browser/page-review-judge.ts +16 -0
- package/src/lib/browser/qa-run-contracts.ts +11 -0
- package/src/lib/browser/qa-run.ts +1 -0
- package/src/lib/browser/user-agent.ts +130 -0
- package/src/lib/browser/windows-chrome.ts +227 -0
- package/src/lib/claude-desktop-share.ts +511 -0
- package/src/lib/disk-usage.ts +347 -0
- package/src/lib/docs-lint.ts +8 -3
- package/src/lib/docs-metadata-managed.ts +21 -0
- package/src/lib/guarded-remove.ts +376 -0
- package/src/lib/identities/assume.ts +7 -7
- package/src/lib/instructions/apply.ts +2 -0
- package/src/lib/instructions/templates.ts +8 -8
- package/src/lib/pace/index.ts +14 -0
- package/src/lib/pace/pace.ts +370 -0
- package/src/lib/tunnel/error-page.ts +25 -9
- package/src/lib/tunnel/forward-headers.ts +16 -0
- package/src/lib/tunnel/gate.ts +39 -10
- package/src/lib/tunnel/path-scope.ts +73 -0
- package/src/lib/tunnel/state.ts +20 -3
- package/src/lib/webview/index.ts +13 -0
- package/src/lib/webview/webview.ts +457 -0
package/src/core/hooks/cli.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
|
22
22
|
import { dirname, join } from "node:path";
|
|
23
23
|
import { coordEnv } from "../../lib/env.ts";
|
|
24
24
|
import { buildInstructionBundle } from "../../lib/instructions/bundle.ts";
|
|
25
|
-
import type
|
|
25
|
+
import { type Adapter, adapterFromPlatform as sharedAdapterFromPlatform } from "../adapter.ts";
|
|
26
26
|
import { resolveCoordRoot } from "../agents/coord-client.ts";
|
|
27
27
|
import {
|
|
28
28
|
type ClaimFinalizationDecision,
|
|
@@ -43,12 +43,15 @@ import {
|
|
|
43
43
|
sessionNameDisplayInstruction,
|
|
44
44
|
sessionNameDisplayPending,
|
|
45
45
|
sessionNameDisplayRecoveryInstruction,
|
|
46
|
+
sessionNameDisplayReminder,
|
|
47
|
+
sessionNameDisplayReminderDue,
|
|
46
48
|
toolResponseMintedSessionName,
|
|
47
49
|
} from "../agents/session-name-display.ts";
|
|
48
50
|
import type { Heartbeat } from "../agents/state/heartbeat-reader.ts";
|
|
49
51
|
import {
|
|
50
52
|
readHeartbeat,
|
|
51
53
|
setAssignedNameCache,
|
|
54
|
+
stampSessionNameReminded,
|
|
52
55
|
stampSessionNameRequested,
|
|
53
56
|
stampSessionNameSeen,
|
|
54
57
|
} from "../agents/state/heartbeat-writer.ts";
|
|
@@ -80,11 +83,14 @@ import {
|
|
|
80
83
|
recordLiveHookSignalV3,
|
|
81
84
|
resolveLiveEventLedgerRouteV3,
|
|
82
85
|
} from "../events/v3/live-routing.ts";
|
|
86
|
+
import type { TurnRitualEvidenceV3 } from "../events/v3/producers/hook-base.ts";
|
|
83
87
|
import { captureSpanClockV3 } from "../events/v3/span-state.ts";
|
|
84
88
|
import { ensureRelayDaemon, fetchPresence, publishPresence } from "../presence/index.ts";
|
|
85
89
|
import { closeProcessLoggers, legacyLogFields, processLogger } from "../storage/logger.ts";
|
|
86
90
|
import { stableScopeId } from "../workflow/scope-id.ts";
|
|
91
|
+
import { type AdapterBehavior, adapterBehavior } from "./adapter/behaviors/index.ts";
|
|
87
92
|
import { detectAdapter, shouldSkipHookAdapter } from "./adapter/detect.ts";
|
|
93
|
+
import { ADAPTER_SPECS } from "./adapter/events.ts";
|
|
88
94
|
import {
|
|
89
95
|
extractBashCommand,
|
|
90
96
|
extractToolDescription,
|
|
@@ -93,14 +99,8 @@ import {
|
|
|
93
99
|
type ParsedPayload,
|
|
94
100
|
parsePayload,
|
|
95
101
|
} from "./adapter/parse.ts";
|
|
96
|
-
import {
|
|
102
|
+
import { hookTimeoutFor } from "./adapter/wiring.ts";
|
|
97
103
|
import { scheduleBackupSnapshot } from "./backup-schedule.ts";
|
|
98
|
-
import {
|
|
99
|
-
codexWslFileLinkTelemetry,
|
|
100
|
-
inspectCodexWslBridge,
|
|
101
|
-
isWslUncPath,
|
|
102
|
-
renderCodexWslFileLinkContext,
|
|
103
|
-
} from "./codex-wsl-bridge.ts";
|
|
104
104
|
import {
|
|
105
105
|
captureImages,
|
|
106
106
|
detectPresence,
|
|
@@ -130,12 +130,12 @@ import type { SessionNameDisplayInspection } from "./resolve/transcript.ts";
|
|
|
130
130
|
import {
|
|
131
131
|
inspectSessionNameDisplayImmediately,
|
|
132
132
|
scanAssistantStatusBoxPresent,
|
|
133
|
-
scanSessionNameDisplayedImmediately,
|
|
134
133
|
scanStatusBoxPresent,
|
|
135
134
|
scanTranscriptRuntime,
|
|
136
135
|
} from "./resolve/transcript.ts";
|
|
136
|
+
import { clearHookRunMarker, writeHookRunMarker } from "./run-markers.ts";
|
|
137
137
|
import { sessionNamePresence } from "./session-name-presence.ts";
|
|
138
|
-
import {
|
|
138
|
+
import { shellWaiterReason } from "./shell-waiter.ts";
|
|
139
139
|
|
|
140
140
|
interface Argv {
|
|
141
141
|
eventName: string | null;
|
|
@@ -143,6 +143,7 @@ interface Argv {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
let hookHealthState: HookHealthState | undefined;
|
|
146
|
+
let hookRunMarkerPath: string | null = null;
|
|
146
147
|
|
|
147
148
|
function parseArgv(argv: string[]): Argv {
|
|
148
149
|
const out: Argv = { eventName: null, extra: [] };
|
|
@@ -341,12 +342,9 @@ function buildEventData(
|
|
|
341
342
|
const p = ctx.payload;
|
|
342
343
|
switch (eventType) {
|
|
343
344
|
case "session.started": {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
: ctx.adapter === "cursor"
|
|
348
|
-
? "cursor"
|
|
349
|
-
: "codex";
|
|
345
|
+
// The platform label is the adapter id itself; enumerating and defaulting
|
|
346
|
+
// to codex silently mislabels any adapter not in the list.
|
|
347
|
+
const adapterPlatform = ctx.adapter;
|
|
350
348
|
// Recorded fork lineage is NOT detected here. On claude-code a fork
|
|
351
349
|
// never fires its own session.started — SessionStart fires under the
|
|
352
350
|
// PARENT's session id (source=resume) before the fork id is minted
|
|
@@ -357,7 +355,12 @@ function buildEventData(
|
|
|
357
355
|
const forkedFrom: string | undefined = undefined;
|
|
358
356
|
// Assign (or recover) name + kind in-process. Idempotent: resume
|
|
359
357
|
// returns the original name; new owner consumes a counter slot.
|
|
360
|
-
|
|
358
|
+
// Workflow children stay unnamed, as on the tool path: a pool name
|
|
359
|
+
// would make them the addressee of mail held for that name.
|
|
360
|
+
const assigned =
|
|
361
|
+
coordEnv("WORKFLOW_CHILD") === "1"
|
|
362
|
+
? undefined
|
|
363
|
+
: assignNameInProcess(ctx.coordRoot, ctx.instanceId, "session", forkedFrom);
|
|
361
364
|
// Write the adapter pid-map row so `harn agents whoami` ppid-walks find
|
|
362
365
|
// this owner. Prefer the payload pid (the actual claude binary), then the
|
|
363
366
|
// anchor walk (the `node` ancestor for Cursor, which has no payload pid),
|
|
@@ -422,7 +425,10 @@ function buildEventData(
|
|
|
422
425
|
// SessionStart. Mint durable display identity here so a later
|
|
423
426
|
// SessionStart remains idempotent and Codec is not stuck on the
|
|
424
427
|
// 8-character native id.
|
|
425
|
-
if (
|
|
428
|
+
if (
|
|
429
|
+
coordEnv("WORKFLOW_CHILD") !== "1" &&
|
|
430
|
+
!readLiveCoordinationRow(ctx.coordRoot, ctx.instanceId)?.name
|
|
431
|
+
) {
|
|
426
432
|
assignNameInProcess(ctx.coordRoot, ctx.instanceId, "session");
|
|
427
433
|
}
|
|
428
434
|
const prompt = p?.prompt ?? "";
|
|
@@ -433,9 +439,11 @@ function buildEventData(
|
|
|
433
439
|
case "turn.completed": {
|
|
434
440
|
const lastAssistantMessage = (p?.raw.last_assistant_message as string | undefined) ?? "";
|
|
435
441
|
const fileLinkTelemetry =
|
|
436
|
-
ctx.adapter
|
|
437
|
-
|
|
438
|
-
|
|
442
|
+
adapterBehavior(ctx.adapter).fileLinkTelemetry?.(
|
|
443
|
+
ctx.coordRoot,
|
|
444
|
+
p?.cwd,
|
|
445
|
+
lastAssistantMessage,
|
|
446
|
+
) ?? null;
|
|
439
447
|
return {
|
|
440
448
|
// Backfill the model for adapters that omit it at session.started
|
|
441
449
|
// (Claude Code). The transcript is populated with assistant turns by
|
|
@@ -471,13 +479,12 @@ function buildEventData(
|
|
|
471
479
|
// exists (the stop-hook.session_name verdict reads it per turn); the
|
|
472
480
|
// transcript scan itself stops once the name has been sighted.
|
|
473
481
|
...sessionNamePresence(ctx.coordRoot, ctx.instanceId, (name) =>
|
|
474
|
-
|
|
475
|
-
//
|
|
476
|
-
// session id so the naming ritual can
|
|
482
|
+
inspectSessionNameDisplayImmediately(
|
|
483
|
+
// Some adapters omit transcript_path on stop; the behavior may
|
|
484
|
+
// discover the transcript by session id so the naming ritual can
|
|
485
|
+
// verify and stamp there too.
|
|
477
486
|
p?.transcript_path ??
|
|
478
|
-
(ctx.adapter
|
|
479
|
-
? discoverCodexSessionTranscript(p?.session_id ?? ctx.instanceId)
|
|
480
|
-
: undefined),
|
|
487
|
+
adapterBehavior(ctx.adapter).discoverTranscript?.(p?.session_id ?? ctx.instanceId),
|
|
481
488
|
name,
|
|
482
489
|
assistantTextStartsWithSessionNameBlock,
|
|
483
490
|
),
|
|
@@ -622,12 +629,70 @@ function numberField(value: unknown): number | undefined {
|
|
|
622
629
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
623
630
|
}
|
|
624
631
|
|
|
632
|
+
/** Everything one hook invocation resolved before its event handlers run. */
|
|
633
|
+
interface HookRun {
|
|
634
|
+
coordRoot: string;
|
|
635
|
+
adapter: Adapter;
|
|
636
|
+
behavior: AdapterBehavior;
|
|
637
|
+
eventName: string;
|
|
638
|
+
norm: NonNullable<ReturnType<typeof normalizeEventName>>;
|
|
639
|
+
payload: ParsedPayload | null;
|
|
640
|
+
raw: string;
|
|
641
|
+
owner: NonNullable<ReturnType<typeof resolveOwner>>;
|
|
642
|
+
sessionId: string;
|
|
643
|
+
data: Record<string, unknown>;
|
|
644
|
+
debugBase: Record<string, unknown>;
|
|
645
|
+
ledgerRoute: Exclude<ReturnType<typeof resolveLiveEventLedgerRouteV3>, { state: "blocked" }>;
|
|
646
|
+
hookStartedAt: number;
|
|
647
|
+
hookClock: ReturnType<typeof captureSpanClockV3>;
|
|
648
|
+
priorCoordination: Heartbeat | null;
|
|
649
|
+
topLevelSession: boolean;
|
|
650
|
+
stopRemediation: boolean;
|
|
651
|
+
bridge: ReturnType<AdapterBehavior["bridgeFor"]>;
|
|
652
|
+
turnRitual: TurnRitualEvidenceV3 | undefined;
|
|
653
|
+
/** Set by recordSignal once the canonical V3 event has landed. */
|
|
654
|
+
v3Result?: ReturnType<typeof recordLiveHookSignalV3>;
|
|
655
|
+
v3EventId?: string;
|
|
656
|
+
recordedGenerationId?: `gen_${string}`;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/** The completed-reply ritual path runs before event normalization and owner resolution. */
|
|
660
|
+
interface CompletedResponseRun {
|
|
661
|
+
coordRoot: string;
|
|
662
|
+
adapter: Adapter;
|
|
663
|
+
eventName: string;
|
|
664
|
+
raw: string;
|
|
665
|
+
debugBase: Record<string, unknown>;
|
|
666
|
+
ledgerRoute: ReturnType<typeof resolveLiveEventLedgerRouteV3>;
|
|
667
|
+
hookStartedAt: number;
|
|
668
|
+
hookClock: ReturnType<typeof captureSpanClockV3>;
|
|
669
|
+
}
|
|
670
|
+
|
|
625
671
|
async function main(): Promise<number> {
|
|
626
672
|
const hookStartedAt = performance.now();
|
|
627
673
|
const hookStartedRss = process.memoryUsage().rss;
|
|
628
674
|
const hookClock = captureSpanClockV3();
|
|
629
675
|
const { eventName, extra } = parseArgv(process.argv.slice(2));
|
|
630
676
|
const adapter = detectAdapter(process.argv.slice(2));
|
|
677
|
+
const behavior = adapter ? adapterBehavior(adapter) : null;
|
|
678
|
+
// Adapters that kill a hook at its timeout get a start marker, so a killed
|
|
679
|
+
// run leaves evidence for doctor (ADR 0197). Written before stdin is read.
|
|
680
|
+
const markerSpec = adapter ? ADAPTER_SPECS[adapter] : undefined;
|
|
681
|
+
const markerTimeout =
|
|
682
|
+
markerSpec && eventName && markerSpec.events.some((e) => e.subcommand === eventName)
|
|
683
|
+
? hookTimeoutFor(markerSpec, eventName)
|
|
684
|
+
: undefined;
|
|
685
|
+
if (markerTimeout !== undefined && adapter && eventName && coordEnv("AGENT_COORD_OFF") !== "1") {
|
|
686
|
+
const markerRoot = resolveCoordRoot(process.cwd());
|
|
687
|
+
if (markerRoot) {
|
|
688
|
+
hookRunMarkerPath = writeHookRunMarker(markerRoot, {
|
|
689
|
+
event: eventName,
|
|
690
|
+
adapter,
|
|
691
|
+
pid: process.pid,
|
|
692
|
+
timeout_sec: markerTimeout,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
}
|
|
631
696
|
const raw = await readStdin();
|
|
632
697
|
hookHealthState = beginHookHealth({
|
|
633
698
|
started_at_ms: hookStartedAt,
|
|
@@ -667,8 +732,8 @@ async function main(): Promise<number> {
|
|
|
667
732
|
// Kill-switch-INDEPENDENT effects: notification sounds fire BEFORE the
|
|
668
733
|
// HARNERY_AGENT_COORD_OFF gate so audible feedback survives incident-triage
|
|
669
734
|
// bypass: sound playback happens before the kill-switch bailout.
|
|
670
|
-
//
|
|
671
|
-
if (
|
|
735
|
+
// Behavior-gated; stop-failure → error, sub-agent-start → subagent-start.
|
|
736
|
+
if (behavior?.soundEffects && eventName) {
|
|
672
737
|
const s = soundForEvent(eventName);
|
|
673
738
|
if (s) {
|
|
674
739
|
const repoRoot = resolveCoordRoot(process.cwd());
|
|
@@ -724,7 +789,7 @@ async function main(): Promise<number> {
|
|
|
724
789
|
ppid: process.ppid,
|
|
725
790
|
};
|
|
726
791
|
|
|
727
|
-
if (!eventName || !adapter) {
|
|
792
|
+
if (!eventName || !adapter || !behavior) {
|
|
728
793
|
appendDebug(coordRoot, { ...debugBase, skipped: "missing-event-or-adapter" });
|
|
729
794
|
return 0;
|
|
730
795
|
}
|
|
@@ -736,88 +801,17 @@ async function main(): Promise<number> {
|
|
|
736
801
|
// assistant text. Preserve only privacy-safe ritual booleans in the V3
|
|
737
802
|
// producer's open-turn state; Stop consumes them into the authoritative
|
|
738
803
|
// turn.completed event. The response body never enters V3 or heartbeat.
|
|
739
|
-
if (
|
|
740
|
-
|
|
741
|
-
const owner = resolveOwner({ payload: payload?.raw ?? null, coordRoot });
|
|
742
|
-
if (!owner) {
|
|
743
|
-
appendDebug(coordRoot, { ...debugBase, skipped: "no-owner-resolved" });
|
|
744
|
-
return 0;
|
|
745
|
-
}
|
|
746
|
-
const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
|
|
747
|
-
const name = sessionNameDisplayPending(row);
|
|
748
|
-
const text = typeof payload?.raw.text === "string" ? payload.raw.text : "";
|
|
749
|
-
const sighting = matchSessionNameDisplay(row, text);
|
|
750
|
-
if (sighting) {
|
|
751
|
-
stampSessionNameSeen(coordRoot, owner.instance_id, sighting.pending);
|
|
752
|
-
appendDebug(coordRoot, {
|
|
753
|
-
...debugBase,
|
|
754
|
-
effect: "session-name-display-stamped",
|
|
755
|
-
session_name_pending: sighting.pending,
|
|
756
|
-
...(sighting.displayed === sighting.pending
|
|
757
|
-
? {}
|
|
758
|
-
: { session_name_displayed: sighting.displayed, session_name_drift: true }),
|
|
759
|
-
});
|
|
760
|
-
} else if (!name) {
|
|
761
|
-
// Nothing was owed. Distinct from a miss: conflating the two is what made
|
|
762
|
-
// a latched Cursor session undiagnosable from this log.
|
|
763
|
-
appendDebug(coordRoot, { ...debugBase, skipped: "no-pending-session-name" });
|
|
764
|
-
} else {
|
|
765
|
-
appendDebug(coordRoot, {
|
|
766
|
-
...debugBase,
|
|
767
|
-
skipped: "session-name-block-absent",
|
|
768
|
-
session_name_pending: name,
|
|
769
|
-
reply_bytes: text.length,
|
|
770
|
-
reply_leads_with_fence: /^\s*`{3,}/.test(text),
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
if (ledgerRoute.state === "blocked") {
|
|
774
|
-
appendDebug(coordRoot, {
|
|
775
|
-
...debugBase,
|
|
776
|
-
skipped: "v3-control-blocked",
|
|
777
|
-
reason: ledgerRoute.reason,
|
|
778
|
-
owner_source: owner.source,
|
|
779
|
-
});
|
|
780
|
-
return 0;
|
|
781
|
-
}
|
|
782
|
-
const statusBoxPresentStrict = scanAssistantStatusBoxPresent(undefined, text);
|
|
783
|
-
// The hook parser needs the completed reply long enough to scan it, but
|
|
784
|
-
// durable hook intake must never retain that body. Keep every other native
|
|
785
|
-
// field so turn correlation remains available to the producer.
|
|
786
|
-
const { text: _completedReply, ...privacySafeRaw } = payload?.raw ?? {};
|
|
787
|
-
const privacySafePayload = payload ? { ...payload, raw: privacySafeRaw } : null;
|
|
788
|
-
const v3Result = recordLiveHookSignalV3({
|
|
804
|
+
if (behavior && eventName && eventName === behavior.completedResponseEvent) {
|
|
805
|
+
return handleCompletedResponse({
|
|
789
806
|
coordRoot,
|
|
790
|
-
route: ledgerRoute,
|
|
791
|
-
eventName,
|
|
792
|
-
payload: privacySafePayload,
|
|
793
807
|
adapter,
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
status_box_present_strict: statusBoxPresentStrict,
|
|
801
|
-
session_name_required: name !== undefined,
|
|
802
|
-
session_name_present: name ? assistantTextStartsWithSessionNameBlock(text, name) : false,
|
|
803
|
-
},
|
|
804
|
-
});
|
|
805
|
-
observeHookDebug(hookHealthState, { event_v3_state: v3Result.state });
|
|
806
|
-
appendDebug(coordRoot, {
|
|
807
|
-
...debugBase,
|
|
808
|
-
effect: "cursor-response-ritual-observed",
|
|
809
|
-
owner_source: owner.source,
|
|
810
|
-
event_v3_state: v3Result.state,
|
|
811
|
-
...(v3Result.state === "observed"
|
|
812
|
-
? {
|
|
813
|
-
generation_id: v3Result.generation_id,
|
|
814
|
-
turn_id: v3Result.turn_id,
|
|
815
|
-
response_observed_at: v3Result.observed_at,
|
|
816
|
-
status_box_present_strict: statusBoxPresentStrict,
|
|
817
|
-
}
|
|
818
|
-
: {}),
|
|
808
|
+
eventName,
|
|
809
|
+
raw,
|
|
810
|
+
debugBase,
|
|
811
|
+
ledgerRoute,
|
|
812
|
+
hookStartedAt,
|
|
813
|
+
hookClock,
|
|
819
814
|
});
|
|
820
|
-
return 0;
|
|
821
815
|
}
|
|
822
816
|
|
|
823
817
|
const norm = normalizeEventName(eventName);
|
|
@@ -827,6 +821,7 @@ async function main(): Promise<number> {
|
|
|
827
821
|
}
|
|
828
822
|
|
|
829
823
|
const payload = parsePayload(raw, adapter);
|
|
824
|
+
const bridge = behavior.bridgeFor(payload?.cwd);
|
|
830
825
|
const owner = resolveOwner({
|
|
831
826
|
payload: payload?.raw ?? null,
|
|
832
827
|
coordRoot,
|
|
@@ -874,8 +869,20 @@ async function main(): Promise<number> {
|
|
|
874
869
|
status_box_present_strict: data.status_box_present_strict === true,
|
|
875
870
|
session_name_required: typeof data.session_name_present_for === "string",
|
|
876
871
|
session_name_present: data.session_name_present === true,
|
|
872
|
+
...(typeof data.session_name_unavailable_reason === "string"
|
|
873
|
+
? { session_name_unavailable_reason: data.session_name_unavailable_reason }
|
|
874
|
+
: {}),
|
|
877
875
|
}
|
|
878
876
|
: undefined;
|
|
877
|
+
if (typeof data.session_name_unavailable_reason === "string") {
|
|
878
|
+
reportSessionNameObservationUnavailable(
|
|
879
|
+
coordRoot,
|
|
880
|
+
owner.instance_id,
|
|
881
|
+
adapter,
|
|
882
|
+
eventName,
|
|
883
|
+
data.session_name_unavailable_reason,
|
|
884
|
+
);
|
|
885
|
+
}
|
|
879
886
|
const stopRemediation =
|
|
880
887
|
norm.event_type === "turn.started" &&
|
|
881
888
|
typeof data.prompt_text === "string" &&
|
|
@@ -892,6 +899,107 @@ async function main(): Promise<number> {
|
|
|
892
899
|
return 0;
|
|
893
900
|
}
|
|
894
901
|
|
|
902
|
+
const run: HookRun = {
|
|
903
|
+
coordRoot,
|
|
904
|
+
adapter,
|
|
905
|
+
behavior,
|
|
906
|
+
eventName,
|
|
907
|
+
norm,
|
|
908
|
+
payload,
|
|
909
|
+
raw,
|
|
910
|
+
owner,
|
|
911
|
+
sessionId,
|
|
912
|
+
data,
|
|
913
|
+
debugBase,
|
|
914
|
+
ledgerRoute,
|
|
915
|
+
hookStartedAt,
|
|
916
|
+
hookClock,
|
|
917
|
+
priorCoordination,
|
|
918
|
+
topLevelSession,
|
|
919
|
+
stopRemediation,
|
|
920
|
+
bridge,
|
|
921
|
+
turnRitual,
|
|
922
|
+
};
|
|
923
|
+
recordSignal(run);
|
|
924
|
+
recordContextTelemetry(run);
|
|
925
|
+
|
|
926
|
+
// Phase 8: SessionStart post-emit: project the event so the heartbeat
|
|
927
|
+
// lands synchronously, run stale-sweep, and emit the adapter-shaped
|
|
928
|
+
// systemMessage JSON (peer table + wiring check + council invites).
|
|
929
|
+
// Adapter-agnostic since v0.5.0; replaces the previous bash UX layer
|
|
930
|
+
// and the equivalent per-adapter bash session_start handlers.
|
|
931
|
+
if (norm.event_type === "session.started") await handleSessionStarted(run);
|
|
932
|
+
|
|
933
|
+
// Phase 8: SessionEnd cleanup: delete heartbeat + pid-map rows. Adapter-
|
|
934
|
+
// agnostic since v0.5.0.
|
|
935
|
+
if (norm.event_type === "session.ended") handleSessionEnded(run);
|
|
936
|
+
|
|
937
|
+
// Phase 8: SubagentStart: the child generation was opened above from the
|
|
938
|
+
// native delegation identity. Refresh its disposable projection, log the
|
|
939
|
+
// lifecycle event, and emit a context message announcing the subagent.
|
|
940
|
+
if (norm.event_type === "agent.started") await handleAgentStarted(run);
|
|
941
|
+
|
|
942
|
+
// Phase 8: SubagentStop: delete subagent heartbeat + log.
|
|
943
|
+
if (norm.event_type === "agent.completed") await handleAgentCompleted(run);
|
|
944
|
+
|
|
945
|
+
// Phase 8: UserPromptSubmit: render dedup'd peer table + council pending
|
|
946
|
+
// and emit the adapter-shaped systemMessage JSON. Adapter-agnostic since v0.5.0.
|
|
947
|
+
if (norm.event_type === "turn.started") await handleTurnStarted(run);
|
|
948
|
+
|
|
949
|
+
// turn.completed: telemetry, then the stop verdict. The
|
|
950
|
+
// verdict + codex-replay previously lived in the per-adapter shell adapters;
|
|
951
|
+
// agent-hook owns them now. Runs on the normal "stop" event only;
|
|
952
|
+
// "stop-failure" (API error) gets no gate, matching the previous
|
|
953
|
+
// stop vs stop-failure split.
|
|
954
|
+
if (norm.event_type === "turn.completed" && eventName === "stop") {
|
|
955
|
+
const exit = await handleStop(run);
|
|
956
|
+
if (exit !== undefined) return exit;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Phase 7: PreToolUse: heartbeat + pid-map self-heal on every tool call.
|
|
960
|
+
// Adapter-agnostic: both writes have the same shape regardless of who fired.
|
|
961
|
+
// Cursor/Codex bash dispatchers still fire their own G-guard logic, but the
|
|
962
|
+
// heals here keep the agent-coord layer's view of liveness fresh.
|
|
963
|
+
//
|
|
964
|
+
// The heartbeat + pid-map heals are paired by design; they were wired
|
|
965
|
+
// side-by-side in the previous pre-tool-use adapter. The Phase 4-6 refactor
|
|
966
|
+
// preserved the heartbeat half but dropped the pid-map half; the pid-map
|
|
967
|
+
// call was restored here afterward.
|
|
968
|
+
if (norm.event_type === "tool.requested") {
|
|
969
|
+
const exit = await handleToolRequested(run);
|
|
970
|
+
if (exit !== undefined) return exit;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (norm.event_type === "tool.completed" && eventName === "post-tool-use")
|
|
974
|
+
await handlePostToolUse(run);
|
|
975
|
+
|
|
976
|
+
// Phase 7: PostToolUseFailure: release claim on failed Edit (the file
|
|
977
|
+
// never landed; the claim is stale). Adapter-agnostic.
|
|
978
|
+
if (norm.event_type === "tool.completed" && eventName === "post-tool-use-failure")
|
|
979
|
+
handlePostToolUseFailure(run);
|
|
980
|
+
|
|
981
|
+
return 0;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function recordSignal(run: HookRun): void {
|
|
985
|
+
const {
|
|
986
|
+
coordRoot,
|
|
987
|
+
adapter,
|
|
988
|
+
eventName,
|
|
989
|
+
norm,
|
|
990
|
+
payload,
|
|
991
|
+
owner,
|
|
992
|
+
sessionId,
|
|
993
|
+
data,
|
|
994
|
+
debugBase,
|
|
995
|
+
ledgerRoute,
|
|
996
|
+
hookStartedAt,
|
|
997
|
+
hookClock,
|
|
998
|
+
priorCoordination,
|
|
999
|
+
stopRemediation,
|
|
1000
|
+
bridge,
|
|
1001
|
+
turnRitual,
|
|
1002
|
+
} = run;
|
|
895
1003
|
let capturedImages: ReturnType<typeof captureImages> = [];
|
|
896
1004
|
if (
|
|
897
1005
|
norm.event_type === "tool.requested" ||
|
|
@@ -924,7 +1032,7 @@ async function main(): Promise<number> {
|
|
|
924
1032
|
: {}),
|
|
925
1033
|
}
|
|
926
1034
|
: {}),
|
|
927
|
-
...(
|
|
1035
|
+
...(bridge ? { bridge } : {}),
|
|
928
1036
|
hook_name: eventName,
|
|
929
1037
|
hook_duration_ms: Math.max(0, Math.floor(performance.now() - hookStartedAt)),
|
|
930
1038
|
monotonic_ns: hookClock.monotonic_ns,
|
|
@@ -980,9 +1088,7 @@ async function main(): Promise<number> {
|
|
|
980
1088
|
parentEvent: v3Result.event,
|
|
981
1089
|
payload,
|
|
982
1090
|
adapter,
|
|
983
|
-
...(
|
|
984
|
-
? { bridge: "codex-wsl" as const }
|
|
985
|
-
: {}),
|
|
1091
|
+
...(bridge ? { bridge } : {}),
|
|
986
1092
|
monotonic_ns: hookClock.monotonic_ns,
|
|
987
1093
|
});
|
|
988
1094
|
const nativeChild = payload.subagent_id ?? payload.agent_id;
|
|
@@ -1030,7 +1136,13 @@ async function main(): Promise<number> {
|
|
|
1030
1136
|
...(v3Result ? { event_v3_state: v3Result.state } : {}),
|
|
1031
1137
|
...(v3Result && "reason" in v3Result ? { event_v3_reason: v3Result.reason } : {}),
|
|
1032
1138
|
});
|
|
1139
|
+
run.v3Result = v3Result;
|
|
1140
|
+
run.v3EventId = v3EventId;
|
|
1141
|
+
run.recordedGenerationId = recordedGenerationId;
|
|
1142
|
+
}
|
|
1033
1143
|
|
|
1144
|
+
function recordContextTelemetry(run: HookRun): void {
|
|
1145
|
+
const { coordRoot, adapter, norm, payload, owner, sessionId } = run;
|
|
1034
1146
|
// Context telemetry is opportunistic and truthful: only persist a sample
|
|
1035
1147
|
// when the adapter payload actually exposes usage/window data. Identical
|
|
1036
1148
|
// measurements are de-duplicated before they reach the canonical stream.
|
|
@@ -1081,437 +1193,549 @@ async function main(): Promise<number> {
|
|
|
1081
1193
|
logError(coordRoot, err, { phase: "context-compaction-completed" });
|
|
1082
1194
|
}
|
|
1083
1195
|
}
|
|
1196
|
+
}
|
|
1084
1197
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
//
|
|
1088
|
-
//
|
|
1089
|
-
//
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1198
|
+
async function handleSessionStarted(run: HookRun): Promise<void> {
|
|
1199
|
+
const { coordRoot, adapter, behavior, payload, owner, sessionId, data } = run;
|
|
1200
|
+
// A configured backup runs out of process and is freshness-gated per host.
|
|
1201
|
+
// Startup never waits for restic or a remote provider; the child owns no
|
|
1202
|
+
// hook stdio, so the harness is not held open until it finishes. A failed
|
|
1203
|
+
// previous run surfaces as one line in this session's context.
|
|
1204
|
+
let backupCue = "";
|
|
1205
|
+
try {
|
|
1206
|
+
backupCue = scheduleBackupSnapshot(coordRoot).cue ?? "";
|
|
1207
|
+
} catch (err) {
|
|
1208
|
+
logError(coordRoot, err, { phase: "backup-schedule" });
|
|
1209
|
+
}
|
|
1210
|
+
// Effect (journal-maintaining adapters): prune stale journal archives +
|
|
1211
|
+
// sweep orphans. The recovery-cue is merged into the session-start
|
|
1212
|
+
// additionalContext inside emitSessionStartSystemMessage.
|
|
1213
|
+
if (behavior.journalMaintenance) journalJanitor(coordRoot);
|
|
1214
|
+
imageJanitor(coordRoot);
|
|
1215
|
+
// Session start is the only caller of the artifact janitor and the storage
|
|
1216
|
+
// maintenance pass, so both load here instead of on every tool call.
|
|
1217
|
+
const { autoCleanArtifacts } = await import("../artifacts/index.ts");
|
|
1218
|
+
const { autoCleanEventV3Archives } = await import("../events/v3/archive-retention.ts");
|
|
1219
|
+
const { createAutomaticMaintenanceComposition, runAutomaticMaintenancePass } = await import(
|
|
1220
|
+
"../storage/maintenance-providers.ts"
|
|
1221
|
+
);
|
|
1222
|
+
// Effect: throttled hourly artifact sweep (see autoCleanArtifacts). It
|
|
1223
|
+
// deletes expired workspaces, plus unheld workspaces over the per-bundle
|
|
1224
|
+
// ceiling or repository budget once they have been idle past
|
|
1225
|
+
// artifacts.idle_grace_hours. Each deletion is rechecked first and recorded
|
|
1226
|
+
// in .harnery/artifact-deletions.jsonl. Best-effort like every janitor
|
|
1227
|
+
// here: a failure logs and never blocks session start.
|
|
1228
|
+
try {
|
|
1229
|
+
autoCleanArtifacts(coordRoot);
|
|
1230
|
+
} catch (err) {
|
|
1231
|
+
logError(coordRoot, err, { phase: "artifact-auto-clean" });
|
|
1232
|
+
}
|
|
1233
|
+
// Effect: apply the same daily throttle to closed V3 epochs. The newest
|
|
1234
|
+
// recovery epochs remain protected by the archive retention policy.
|
|
1235
|
+
try {
|
|
1236
|
+
autoCleanEventV3Archives(coordRoot);
|
|
1237
|
+
} catch (err) {
|
|
1238
|
+
logError(coordRoot, err, { phase: "event-v3-archive-auto-clean" });
|
|
1239
|
+
}
|
|
1240
|
+
// Plan one bounded, claim-first global slice from cached pressure only.
|
|
1241
|
+
// Existing owner janitors above remain the only active executors until
|
|
1242
|
+
// each provider receives a separate production activation.
|
|
1243
|
+
try {
|
|
1244
|
+
await runAutomaticMaintenancePass(
|
|
1245
|
+
createAutomaticMaintenanceComposition(coordRoot, {
|
|
1246
|
+
journal: () => {
|
|
1247
|
+
if (behavior.journalMaintenance) journalJanitor(coordRoot);
|
|
1248
|
+
},
|
|
1249
|
+
images: () => imageJanitor(coordRoot),
|
|
1250
|
+
artifacts: () => autoCleanArtifacts(coordRoot),
|
|
1251
|
+
}),
|
|
1112
1252
|
);
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
} catch (err) {
|
|
1119
|
-
logError(coordRoot, err, { phase: "artifact-auto-clean" });
|
|
1120
|
-
}
|
|
1121
|
-
// Effect: apply the same daily throttle to closed V3 epochs. The newest
|
|
1122
|
-
// recovery epochs remain protected by the archive retention policy.
|
|
1123
|
-
try {
|
|
1124
|
-
autoCleanEventV3Archives(coordRoot);
|
|
1125
|
-
} catch (err) {
|
|
1126
|
-
logError(coordRoot, err, { phase: "event-v3-archive-auto-clean" });
|
|
1127
|
-
}
|
|
1128
|
-
// Plan one bounded, claim-first global slice from cached pressure only.
|
|
1129
|
-
// Existing owner janitors above remain the only active executors until
|
|
1130
|
-
// each provider receives a separate production activation.
|
|
1131
|
-
try {
|
|
1132
|
-
await runAutomaticMaintenancePass(
|
|
1133
|
-
createAutomaticMaintenanceComposition(coordRoot, {
|
|
1134
|
-
journal: () => {
|
|
1135
|
-
if (adapter === "claude-code") journalJanitor(coordRoot);
|
|
1136
|
-
},
|
|
1137
|
-
images: () => imageJanitor(coordRoot),
|
|
1138
|
-
artifacts: () => autoCleanArtifacts(coordRoot),
|
|
1139
|
-
}),
|
|
1140
|
-
);
|
|
1141
|
-
} catch (err) {
|
|
1142
|
-
logError(coordRoot, err, { phase: "storage-maintenance-auto" });
|
|
1143
|
-
}
|
|
1144
|
-
let recovery: PreparedContextRecovery | null = null;
|
|
1145
|
-
if (payload?.source === "compact") {
|
|
1146
|
-
try {
|
|
1147
|
-
markContextCompactionCompleted(coordRoot, {
|
|
1148
|
-
sessionId,
|
|
1149
|
-
instanceId: owner.instance_id,
|
|
1150
|
-
});
|
|
1151
|
-
recovery = prepareContextRecovery(coordRoot, {
|
|
1152
|
-
instanceId: owner.instance_id,
|
|
1153
|
-
sessionId,
|
|
1154
|
-
cwd: payload?.cwd ?? process.cwd(),
|
|
1155
|
-
});
|
|
1156
|
-
} catch (err) {
|
|
1157
|
-
logError(coordRoot, err, { phase: "context-recovery-session-start" });
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1253
|
+
} catch (err) {
|
|
1254
|
+
logError(coordRoot, err, { phase: "storage-maintenance-auto" });
|
|
1255
|
+
}
|
|
1256
|
+
let recovery: PreparedContextRecovery | null = null;
|
|
1257
|
+
if (payload?.source === "compact") {
|
|
1160
1258
|
try {
|
|
1161
|
-
|
|
1162
|
-
coordRoot,
|
|
1163
|
-
owner.instance_id,
|
|
1259
|
+
markContextCompactionCompleted(coordRoot, {
|
|
1164
1260
|
sessionId,
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
}
|
|
1261
|
+
instanceId: owner.instance_id,
|
|
1262
|
+
});
|
|
1263
|
+
recovery = prepareContextRecovery(coordRoot, {
|
|
1264
|
+
instanceId: owner.instance_id,
|
|
1265
|
+
sessionId,
|
|
1266
|
+
cwd: payload?.cwd ?? process.cwd(),
|
|
1267
|
+
});
|
|
1173
1268
|
} catch (err) {
|
|
1174
|
-
logError(coordRoot, err, { phase: "session-start
|
|
1269
|
+
logError(coordRoot, err, { phase: "context-recovery-session-start" });
|
|
1175
1270
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1271
|
+
}
|
|
1272
|
+
try {
|
|
1273
|
+
const injected = await emitSessionStartSystemMessage(
|
|
1274
|
+
coordRoot,
|
|
1275
|
+
owner.instance_id,
|
|
1276
|
+
sessionId,
|
|
1277
|
+
data,
|
|
1278
|
+
adapter,
|
|
1279
|
+
recovery?.briefing ?? "",
|
|
1280
|
+
backupCue,
|
|
1281
|
+
);
|
|
1282
|
+
if (injected && recovery) {
|
|
1283
|
+
completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
|
|
1185
1284
|
}
|
|
1285
|
+
} catch (err) {
|
|
1286
|
+
logError(coordRoot, err, { phase: "session-start-systemMessage" });
|
|
1186
1287
|
}
|
|
1288
|
+
// Cross-machine presence is derived from the authority-safe V3 projection,
|
|
1289
|
+
// not the disposable heartbeat cache. Announce this session, pull peers,
|
|
1290
|
+
// and keep the optional live relay connected.
|
|
1291
|
+
try {
|
|
1292
|
+
publishPresence(coordRoot);
|
|
1293
|
+
fetchPresence(coordRoot);
|
|
1294
|
+
ensureRelayDaemon(coordRoot);
|
|
1295
|
+
} catch (err) {
|
|
1296
|
+
logError(coordRoot, err, { phase: "session-start-presence" });
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1187
1299
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
publishPresence(coordRoot);
|
|
1206
|
-
} catch (err) {
|
|
1207
|
-
logError(coordRoot, err, { phase: "session-end-presence" });
|
|
1208
|
-
}
|
|
1300
|
+
function handleSessionEnded(run: HookRun): void {
|
|
1301
|
+
const { coordRoot, behavior, owner } = run;
|
|
1302
|
+
try {
|
|
1303
|
+
cleanupSessionEnd(coordRoot, owner.instance_id);
|
|
1304
|
+
} catch (err) {
|
|
1305
|
+
logError(coordRoot, err, { phase: "session-end-cleanup" });
|
|
1306
|
+
}
|
|
1307
|
+
// Effects: archive the ending agent's journal + force a session-telemetry
|
|
1308
|
+
// sync (via HARNERY_CLAUDE_SESSIONS_FORCE=1), each gated by the behavior.
|
|
1309
|
+
if (behavior.journalMaintenance) journalArchive(coordRoot, owner.instance_id);
|
|
1310
|
+
if (behavior.sessionTelemetrySync) runSessionSyncExtension(coordRoot, true);
|
|
1311
|
+
// Publish the post-cleanup V3 projection so remote machines see this
|
|
1312
|
+
// session disappear without waiting for the stale window.
|
|
1313
|
+
try {
|
|
1314
|
+
publishPresence(coordRoot);
|
|
1315
|
+
} catch (err) {
|
|
1316
|
+
logError(coordRoot, err, { phase: "session-end-presence" });
|
|
1209
1317
|
}
|
|
1318
|
+
}
|
|
1210
1319
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
} catch (err) {
|
|
1219
|
-
logError(coordRoot, err, { phase: "subagent-start-context" });
|
|
1220
|
-
}
|
|
1320
|
+
async function handleAgentStarted(run: HookRun): Promise<void> {
|
|
1321
|
+
const { coordRoot, adapter, owner, sessionId, data } = run;
|
|
1322
|
+
try {
|
|
1323
|
+
const childInstanceId = (data.agent_id as string | undefined) ?? owner.instance_id;
|
|
1324
|
+
await emitSubagentStartContext(coordRoot, childInstanceId, sessionId, data, adapter);
|
|
1325
|
+
} catch (err) {
|
|
1326
|
+
logError(coordRoot, err, { phase: "subagent-start-context" });
|
|
1221
1327
|
}
|
|
1328
|
+
}
|
|
1222
1329
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
}
|
|
1330
|
+
async function handleAgentCompleted(run: HookRun): Promise<void> {
|
|
1331
|
+
const { coordRoot, payload, owner } = run;
|
|
1332
|
+
try {
|
|
1333
|
+
const childInstanceId = payload?.subagent_id ?? payload?.agent_id ?? owner.instance_id;
|
|
1334
|
+
cleanupSessionEnd(coordRoot, childInstanceId);
|
|
1335
|
+
const { reconcileSessionFinalizationV3 } = await import("../agents/session-finalizer-v3.ts");
|
|
1336
|
+
reconcileSessionFinalizationV3(coordRoot, { archive_observations: [] });
|
|
1337
|
+
} catch (err) {
|
|
1338
|
+
logError(coordRoot, err, { phase: "subagent-stop-cleanup" });
|
|
1233
1339
|
}
|
|
1340
|
+
}
|
|
1234
1341
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1342
|
+
async function handleTurnStarted(run: HookRun): Promise<void> {
|
|
1343
|
+
const {
|
|
1344
|
+
coordRoot,
|
|
1345
|
+
adapter,
|
|
1346
|
+
behavior,
|
|
1347
|
+
payload,
|
|
1348
|
+
owner,
|
|
1349
|
+
sessionId,
|
|
1350
|
+
debugBase,
|
|
1351
|
+
topLevelSession,
|
|
1352
|
+
stopRemediation,
|
|
1353
|
+
v3EventId,
|
|
1354
|
+
} = run;
|
|
1355
|
+
// Effects: reset per-turn sound rate-limit counters + run presence
|
|
1356
|
+
// detection on the prompt, each gated by the behavior.
|
|
1357
|
+
if (behavior.soundEffects) resetSoundCounters(sessionId);
|
|
1358
|
+
if (behavior.promptPresenceDetection) {
|
|
1359
|
+
const prompt = (payload?.raw?.prompt as string | undefined) ?? "";
|
|
1360
|
+
if (prompt) detectPresence(prompt);
|
|
1361
|
+
}
|
|
1362
|
+
// Presence: throttled pull of peer machines' presence refs (default 60s
|
|
1363
|
+
// interval; the systemMessage below reads whatever is locally known).
|
|
1364
|
+
try {
|
|
1365
|
+
fetchPresence(coordRoot);
|
|
1366
|
+
} catch (err) {
|
|
1367
|
+
logError(coordRoot, err, { phase: "user-prompt-submit-presence" });
|
|
1368
|
+
}
|
|
1369
|
+
let recovery: PreparedContextRecovery | null = null;
|
|
1370
|
+
const continuityState = readContextState(coordRoot, sessionId);
|
|
1371
|
+
if (
|
|
1372
|
+
continuityState?.phase === "checkpointed" &&
|
|
1373
|
+
continuityState.compaction_completed_at !== undefined
|
|
1374
|
+
) {
|
|
1247
1375
|
try {
|
|
1248
|
-
|
|
1376
|
+
recovery = prepareContextRecovery(coordRoot, {
|
|
1377
|
+
instanceId: owner.instance_id,
|
|
1378
|
+
sessionId,
|
|
1379
|
+
cwd: payload?.cwd ?? process.cwd(),
|
|
1380
|
+
});
|
|
1249
1381
|
} catch (err) {
|
|
1250
|
-
logError(coordRoot, err, { phase: "user-prompt
|
|
1251
|
-
}
|
|
1252
|
-
let recovery: PreparedContextRecovery | null = null;
|
|
1253
|
-
const continuityState = readContextState(coordRoot, sessionId);
|
|
1254
|
-
if (
|
|
1255
|
-
continuityState?.phase === "checkpointed" &&
|
|
1256
|
-
continuityState.compaction_completed_at !== undefined
|
|
1257
|
-
) {
|
|
1258
|
-
try {
|
|
1259
|
-
recovery = prepareContextRecovery(coordRoot, {
|
|
1260
|
-
instanceId: owner.instance_id,
|
|
1261
|
-
sessionId,
|
|
1262
|
-
cwd: payload?.cwd ?? process.cwd(),
|
|
1263
|
-
});
|
|
1264
|
-
} catch (err) {
|
|
1265
|
-
logError(coordRoot, err, { phase: "context-recovery-user-prompt" });
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
let hostPromptContext = "";
|
|
1269
|
-
const hostPromptEnabled = hostPromptContextConfig(coordRoot)?.enabled === true;
|
|
1270
|
-
if (hostPromptEnabled && topLevelSession && !stopRemediation) {
|
|
1271
|
-
try {
|
|
1272
|
-
const { runPromptContext } = await import("./prompt-context/runner.ts");
|
|
1273
|
-
const result = await runPromptContext({
|
|
1274
|
-
coordRoot,
|
|
1275
|
-
adapter,
|
|
1276
|
-
sessionId,
|
|
1277
|
-
turnId: payload?.turn_id ?? v3EventId ?? sessionId,
|
|
1278
|
-
cwd: payload?.cwd ?? process.cwd(),
|
|
1279
|
-
prompt: payload?.prompt ?? "",
|
|
1280
|
-
});
|
|
1281
|
-
appendDebug(coordRoot, {
|
|
1282
|
-
...debugBase,
|
|
1283
|
-
effect: "prompt-context",
|
|
1284
|
-
...result.audit,
|
|
1285
|
-
});
|
|
1286
|
-
hostPromptContext = result.context ?? "";
|
|
1287
|
-
} catch (err) {
|
|
1288
|
-
logError(coordRoot, err, { phase: "prompt-context-user-prompt" });
|
|
1289
|
-
}
|
|
1382
|
+
logError(coordRoot, err, { phase: "context-recovery-user-prompt" });
|
|
1290
1383
|
}
|
|
1384
|
+
}
|
|
1385
|
+
let hostPromptContext = "";
|
|
1386
|
+
const hostPromptEnabled = hostPromptContextConfig(coordRoot)?.enabled === true;
|
|
1387
|
+
if (hostPromptEnabled && topLevelSession && !stopRemediation) {
|
|
1291
1388
|
try {
|
|
1292
|
-
const
|
|
1389
|
+
const { runPromptContext } = await import("./prompt-context/runner.ts");
|
|
1390
|
+
const result = await runPromptContext({
|
|
1293
1391
|
coordRoot,
|
|
1294
|
-
owner.instance_id,
|
|
1295
|
-
sessionId,
|
|
1296
1392
|
adapter,
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1393
|
+
sessionId,
|
|
1394
|
+
turnId: payload?.turn_id ?? v3EventId ?? sessionId,
|
|
1395
|
+
cwd: payload?.cwd ?? process.cwd(),
|
|
1396
|
+
prompt: payload?.prompt ?? "",
|
|
1397
|
+
});
|
|
1398
|
+
appendDebug(coordRoot, {
|
|
1399
|
+
...debugBase,
|
|
1400
|
+
effect: "prompt-context",
|
|
1401
|
+
...result.audit,
|
|
1402
|
+
});
|
|
1403
|
+
hostPromptContext = result.context ?? "";
|
|
1304
1404
|
} catch (err) {
|
|
1305
|
-
logError(coordRoot, err, { phase: "user-prompt
|
|
1405
|
+
logError(coordRoot, err, { phase: "prompt-context-user-prompt" });
|
|
1306
1406
|
}
|
|
1307
1407
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
// stop vs stop-failure split.
|
|
1314
|
-
if (norm.event_type === "turn.completed" && eventName === "stop") {
|
|
1315
|
-
// Claude Code session telemetry sync remains an independent side effect.
|
|
1316
|
-
if (adapter === "claude-code") {
|
|
1317
|
-
runSessionSyncExtension(coordRoot, false);
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
// Publish after the canonical turn event has landed so the blob carries
|
|
1321
|
-
// current task, activity, and claim state. Re-ensure the relay here because
|
|
1322
|
-
// its daemon intentionally exits when a machine has no live sessions.
|
|
1323
|
-
try {
|
|
1324
|
-
publishPresence(coordRoot);
|
|
1325
|
-
ensureRelayDaemon(coordRoot);
|
|
1326
|
-
} catch (err) {
|
|
1327
|
-
logError(coordRoot, err, { phase: "stop-presence" });
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
// Stop verdict (V3 ritual + task/status gate). Direct in-process call: the
|
|
1331
|
-
// rule lives in Harnery. agent-hook already emitted this turn.completed
|
|
1332
|
-
// with privacy-safe ritual observations, so the evidence is in the stream.
|
|
1333
|
-
const verdict = evaluateStopHook(coordRoot, {
|
|
1334
|
-
rule: "stop-hook",
|
|
1335
|
-
instance_id: owner.instance_id,
|
|
1336
|
-
session_id: sessionId,
|
|
1408
|
+
try {
|
|
1409
|
+
const injected = await emitUserPromptSubmitSystemMessage(
|
|
1410
|
+
coordRoot,
|
|
1411
|
+
owner.instance_id,
|
|
1412
|
+
sessionId,
|
|
1337
1413
|
adapter,
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
// Backstop for sessions whose evidence can never land (e.g. a headless
|
|
1345
|
-
// child whose status command resolves to a different owner): after
|
|
1346
|
-
// `cap` consecutive blocked stops in one cycle, allow the stop instead
|
|
1347
|
-
// of bouncing the model forever. Cycle boundaries come from the
|
|
1348
|
-
// adapter's continuation flag, so adapters without one are unaffected.
|
|
1349
|
-
const { recordRemediationBlock } = await import("./remediation-cap.ts");
|
|
1350
|
-
const remediation = recordRemediationBlock(sessionId, payload?.stop_hook_active === true);
|
|
1351
|
-
if (remediation.exceeded) {
|
|
1352
|
-
appendDebug(coordRoot, {
|
|
1353
|
-
...debugBase,
|
|
1354
|
-
skipped: "stop-remediation-cap-exhausted",
|
|
1355
|
-
blocked_rule: verdict.rule,
|
|
1356
|
-
blocked_count: remediation.count,
|
|
1357
|
-
session_id: sessionId,
|
|
1358
|
-
remediation_cycle_anchor: verdict.remediation_cycle_anchor,
|
|
1359
|
-
});
|
|
1360
|
-
} else {
|
|
1361
|
-
// Adapter-aware enforcement channel: Claude Code honors exit-2 + stderr
|
|
1362
|
-
// as a turn block; Cursor ignores exit codes and re-prompts only via a
|
|
1363
|
-
// `followup_message` it auto-submits.
|
|
1364
|
-
const { emitStopOutcome } = await import("./adapter/output.ts");
|
|
1365
|
-
return emitStopOutcome(adapter, { verdict }, coordRoot);
|
|
1366
|
-
}
|
|
1367
|
-
} else {
|
|
1368
|
-
const { clearRemediationCount } = await import("./remediation-cap.ts");
|
|
1369
|
-
clearRemediationCount(sessionId);
|
|
1414
|
+
payload?.cwd,
|
|
1415
|
+
recovery?.briefing ?? "",
|
|
1416
|
+
hostPromptContext,
|
|
1417
|
+
);
|
|
1418
|
+
if (injected && recovery) {
|
|
1419
|
+
completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
|
|
1370
1420
|
}
|
|
1421
|
+
} catch (err) {
|
|
1422
|
+
logError(coordRoot, err, { phase: "user-prompt-submit-systemMessage" });
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1371
1425
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
}
|
|
1386
|
-
if (adapter === "codex") scheduleCodexRuntimeContextRetry(coordRoot, sessionId);
|
|
1426
|
+
async function handleStop(run: HookRun): Promise<number | undefined> {
|
|
1427
|
+
const { coordRoot, adapter, behavior, payload, owner, sessionId, data, debugBase } = run;
|
|
1428
|
+
// Session telemetry sync remains an independent side effect.
|
|
1429
|
+
if (behavior.sessionTelemetrySync) runSessionSyncExtension(coordRoot, false);
|
|
1430
|
+
|
|
1431
|
+
// Publish after the canonical turn event has landed so the blob carries
|
|
1432
|
+
// current task, activity, and claim state. Re-ensure the relay here because
|
|
1433
|
+
// its daemon intentionally exits when a machine has no live sessions.
|
|
1434
|
+
try {
|
|
1435
|
+
publishPresence(coordRoot);
|
|
1436
|
+
ensureRelayDaemon(coordRoot);
|
|
1437
|
+
} catch (err) {
|
|
1438
|
+
logError(coordRoot, err, { phase: "stop-presence" });
|
|
1387
1439
|
}
|
|
1388
1440
|
|
|
1389
|
-
//
|
|
1390
|
-
//
|
|
1391
|
-
//
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1441
|
+
// Stop verdict (V3 ritual + task/status gate). Direct in-process call: the
|
|
1442
|
+
// rule lives in Harnery. agent-hook already emitted this turn.completed
|
|
1443
|
+
// with privacy-safe ritual observations, so the evidence is in the stream.
|
|
1444
|
+
const verdict = evaluateStopHook(coordRoot, {
|
|
1445
|
+
rule: "stop-hook",
|
|
1446
|
+
instance_id: owner.instance_id,
|
|
1447
|
+
session_id: sessionId,
|
|
1448
|
+
adapter,
|
|
1449
|
+
stop_hook_active: payload?.stop_hook_active === true,
|
|
1450
|
+
status_box_present_strict: data.status_box_present_strict === true,
|
|
1451
|
+
session_name_observation_unavailable: typeof data.session_name_unavailable_reason === "string",
|
|
1452
|
+
bypass: coordEnv("AGENT_COORD_BYPASS_STOP") === "1",
|
|
1453
|
+
workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
|
|
1454
|
+
});
|
|
1455
|
+
if (!verdict.allow) {
|
|
1456
|
+
// Backstop for sessions whose evidence can never land (e.g. a headless
|
|
1457
|
+
// child whose status command resolves to a different owner): after
|
|
1458
|
+
// `cap` consecutive blocked stops in one cycle, allow the stop instead
|
|
1459
|
+
// of bouncing the model forever. Cycle boundaries come from the
|
|
1460
|
+
// adapter's continuation flag, so adapters without one are unaffected.
|
|
1461
|
+
const { recordRemediationBlock } = await import("./remediation-cap.ts");
|
|
1462
|
+
const remediation = recordRemediationBlock(sessionId, payload?.stop_hook_active === true);
|
|
1463
|
+
if (remediation.exceeded) {
|
|
1464
|
+
appendDebug(coordRoot, {
|
|
1465
|
+
...debugBase,
|
|
1466
|
+
skipped: "stop-remediation-cap-exhausted",
|
|
1467
|
+
blocked_rule: verdict.rule,
|
|
1468
|
+
blocked_count: remediation.count,
|
|
1469
|
+
session_id: sessionId,
|
|
1470
|
+
remediation_cycle_anchor: verdict.remediation_cycle_anchor,
|
|
1471
|
+
});
|
|
1472
|
+
} else {
|
|
1473
|
+
// Adapter-aware enforcement channel: Claude Code honors exit-2 + stderr
|
|
1474
|
+
// as a turn block; Cursor ignores exit codes and re-prompts only via a
|
|
1475
|
+
// `followup_message` it auto-submits.
|
|
1476
|
+
const { emitStopOutcome } = await import("./adapter/output.ts");
|
|
1477
|
+
return emitStopOutcome(adapter, { verdict }, coordRoot);
|
|
1409
1478
|
}
|
|
1479
|
+
} else {
|
|
1480
|
+
const { clearRemediationCount } = await import("./remediation-cap.ts");
|
|
1481
|
+
clearRemediationCount(sessionId);
|
|
1482
|
+
}
|
|
1410
1483
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
const displayAllowed = await enforcePendingSessionNameDisplay(
|
|
1422
|
-
coordRoot,
|
|
1423
|
-
owner.instance_id,
|
|
1424
|
-
adapter,
|
|
1425
|
-
payload,
|
|
1426
|
-
generationBoundHeartbeat(readHeartbeat(coordRoot, owner.instance_id), recordedGenerationId),
|
|
1427
|
-
);
|
|
1428
|
-
if (!displayAllowed) return 0;
|
|
1429
|
-
} catch (err) {
|
|
1430
|
-
logError(coordRoot, err, { phase: "pre-tool-use-session-name" });
|
|
1484
|
+
// An explicit end requested from inside this turn cannot be authoritative
|
|
1485
|
+
// until the adapter has committed turn.completed above. Reconcile only
|
|
1486
|
+
// when such a request exists; any later real work cancels it in the
|
|
1487
|
+
// finalizer instead of being terminated underneath the agent.
|
|
1488
|
+
try {
|
|
1489
|
+
const { hasPendingExplicitSessionEndV3, reconcileSessionFinalizationV3 } = await import(
|
|
1490
|
+
"../agents/session-finalizer-v3.ts"
|
|
1491
|
+
);
|
|
1492
|
+
if (hasPendingExplicitSessionEndV3(coordRoot)) {
|
|
1493
|
+
reconcileSessionFinalizationV3(coordRoot);
|
|
1431
1494
|
}
|
|
1495
|
+
} catch (err) {
|
|
1496
|
+
logError(coordRoot, err, { phase: "stop-explicit-session-finalization" });
|
|
1497
|
+
}
|
|
1498
|
+
if (behavior.runtimeContextRetry) scheduleRuntimeContextRetry(coordRoot, adapter, sessionId);
|
|
1499
|
+
return undefined;
|
|
1500
|
+
}
|
|
1432
1501
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1502
|
+
async function handleToolRequested(run: HookRun): Promise<number | undefined> {
|
|
1503
|
+
const { coordRoot, adapter, behavior, payload, owner, sessionId, data, recordedGenerationId } =
|
|
1504
|
+
run;
|
|
1505
|
+
try {
|
|
1506
|
+
// Recorded fork lineage, heal-path flavor. A forked CC conversation
|
|
1507
|
+
// never fires its own session.started (SessionStart fires under the
|
|
1508
|
+
// PARENT's session id with source=resume, before the fork id is
|
|
1509
|
+
// minted; verified 2026-08-05), so the fork's new instance first
|
|
1510
|
+
// materializes right here. Gate detection on "no heartbeat yet" so the
|
|
1511
|
+
// transcript scan runs once per instance lifetime, not per tool call.
|
|
1512
|
+
refreshPidmap(coordRoot, owner.instance_id, adapter, payload?.pid);
|
|
1513
|
+
} catch (err) {
|
|
1514
|
+
logError(coordRoot, err, { phase: "pre-tool-use-heal" });
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
// The suggested name is a pending display latch. The set-task call itself
|
|
1518
|
+
// runs before a name exists; every later tool waits until the exact block
|
|
1519
|
+
// is the first assistant text after the mint result. Cursor supplies
|
|
1520
|
+
// agent_message directly; Claude Code and Codex are resolved from their
|
|
1521
|
+
// JSONL transcripts. Cursor's current narration can provide positive
|
|
1522
|
+
// evidence, but cannot disprove an earlier response; afterAgentResponse
|
|
1523
|
+
// stamps that durable sighting. Later commentary must not erase an
|
|
1524
|
+
// already-correct display, and unavailable evidence must not deadlock
|
|
1525
|
+
// every tool.
|
|
1526
|
+
try {
|
|
1527
|
+
const displayAllowed = await enforcePendingSessionNameDisplay(
|
|
1528
|
+
coordRoot,
|
|
1529
|
+
owner.instance_id,
|
|
1438
1530
|
adapter,
|
|
1531
|
+
payload,
|
|
1532
|
+
generationBoundHeartbeat(readHeartbeat(coordRoot, owner.instance_id), recordedGenerationId),
|
|
1533
|
+
);
|
|
1534
|
+
if (!displayAllowed) return 0;
|
|
1535
|
+
} catch (err) {
|
|
1536
|
+
logError(coordRoot, err, { phase: "pre-tool-use-session-name" });
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// Windows-native Codex + WSL UNC only: block the one cross-shell shape
|
|
1540
|
+
// proven to corrupt argument boundaries. Normal WSL argv calls, literal
|
|
1541
|
+
// bash -s scripts, native Linux/macOS sessions, and every other adapter
|
|
1542
|
+
// pass through. The host instructions name its concrete safe bridge.
|
|
1543
|
+
const unsafeShellReason =
|
|
1544
|
+
behavior.unsafeCrossShellReason?.({
|
|
1439
1545
|
cwd: payload?.cwd,
|
|
1440
1546
|
toolName: payload?.tool_name,
|
|
1441
1547
|
toolInput: payload?.tool_input,
|
|
1442
|
-
});
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1548
|
+
}) ?? null;
|
|
1549
|
+
if (unsafeShellReason) {
|
|
1550
|
+
const { emitDeny } = await import("./adapter/output.ts");
|
|
1551
|
+
emitDeny(adapter, unsafeShellReason);
|
|
1552
|
+
return 0;
|
|
1553
|
+
}
|
|
1448
1554
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
// apply_patch (codex) parses paths from the patch body and runs verdict
|
|
1455
|
-
// per-path; Edit/Write/NotebookEdit resolve a single target. Non-write tools
|
|
1456
|
-
// (incl. Agent) yield no targets and pass through with no deny.
|
|
1457
|
-
let guardAllowed = true;
|
|
1458
|
-
try {
|
|
1459
|
-
guardAllowed = await runPreToolUseGuard(
|
|
1460
|
-
coordRoot,
|
|
1461
|
-
owner.instance_id,
|
|
1462
|
-
sessionId,
|
|
1463
|
-
data,
|
|
1464
|
-
adapter,
|
|
1465
|
-
);
|
|
1466
|
-
} catch (err) {
|
|
1467
|
-
logError(coordRoot, err, { phase: "pre-tool-use-guard" });
|
|
1468
|
-
}
|
|
1469
|
-
if (!guardAllowed) return 0;
|
|
1555
|
+
const waiterReason = shellWaiterReason(payload?.tool_name, payload?.tool_input);
|
|
1556
|
+
if (waiterReason) {
|
|
1557
|
+
const { emitDeny } = await import("./adapter/output.ts");
|
|
1558
|
+
emitDeny(adapter, waiterReason);
|
|
1559
|
+
return 0;
|
|
1470
1560
|
}
|
|
1471
1561
|
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1562
|
+
// G-guard for ALL adapters. Claude Code previously ran this via a
|
|
1563
|
+
// pre-tool-use bash adapter (which called `agent-coord verdict --rule=claim`);
|
|
1564
|
+
// that adapter is now deleted, so agent-hook owns the deny for every adapter.
|
|
1565
|
+
// emitDeny() inside emits the adapter-shaped permission JSON (claude-code +
|
|
1566
|
+
// codex use hookSpecificOutput.permissionDecision; cursor uses .permission).
|
|
1567
|
+
// apply_patch (codex) parses paths from the patch body and runs verdict
|
|
1568
|
+
// per-path; Edit/Write/NotebookEdit resolve a single target. Non-write tools
|
|
1569
|
+
// (incl. Agent) yield no targets and pass through with no deny.
|
|
1570
|
+
let guardAllowed = true;
|
|
1571
|
+
try {
|
|
1572
|
+
guardAllowed = await runPreToolUseGuard(coordRoot, owner.instance_id, sessionId, data, adapter);
|
|
1573
|
+
} catch (err) {
|
|
1574
|
+
logError(coordRoot, err, { phase: "pre-tool-use-guard" });
|
|
1575
|
+
}
|
|
1576
|
+
if (!guardAllowed) return 0;
|
|
1577
|
+
return undefined;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
async function handlePostToolUse(run: HookRun): Promise<void> {
|
|
1581
|
+
const { coordRoot, adapter, eventName, payload, owner } = run;
|
|
1582
|
+
// Inject the full instruction only at the successful tool boundary that
|
|
1583
|
+
// actually minted or retried a name: the first non-empty set-task, a
|
|
1584
|
+
// pending-name set-task retry, or the transition to lifecycle done. The
|
|
1585
|
+
// coordination row intentionally remains pending until transcript evidence
|
|
1586
|
+
// catches up, so the latch alone cannot drive injection: that would print
|
|
1587
|
+
// the same block after every later tool.
|
|
1588
|
+
//
|
|
1589
|
+
// Between those two extremes sits a bounded reminder. Since ADR 0182 the
|
|
1590
|
+
// PreToolUse gate allows a tool when transcript evidence is unavailable
|
|
1591
|
+
// and says so only on stderr, which the model never reads. A model that
|
|
1592
|
+
// skipped the block after the mint was therefore never asked again
|
|
1593
|
+
// (ADR 0183). While the requested title is still pending, ordinary tool
|
|
1594
|
+
// results carry an idempotent reminder up to a fixed count; coordination
|
|
1595
|
+
// chores (set-task, status, suggest-name) are skipped because a mint or
|
|
1596
|
+
// retry response already carries the full instruction and an end-turn
|
|
1597
|
+
// status is the wrong place to ask.
|
|
1598
|
+
try {
|
|
1599
|
+
const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
|
|
1600
|
+
// A workflow child has no user-visible title to set.
|
|
1601
|
+
const name = coordEnv("WORKFLOW_CHILD") === "1" ? undefined : sessionNameDisplayPending(row);
|
|
1602
|
+
if (name && toolResponseMintedSessionName(payload?.tool_response, name)) {
|
|
1603
|
+
// Record the title before asking for it. The suggestion can still
|
|
1604
|
+
// change afterwards (an assigned-name rewrite, a lifecycle re-mint, a
|
|
1605
|
+
// rebuilt cache), and the agent must not be stranded for displaying
|
|
1606
|
+
// exactly what it was handed.
|
|
1607
|
+
stampSessionNameRequested(coordRoot, owner.instance_id, name);
|
|
1608
|
+
const { emitContext } = await import("./adapter/output.ts");
|
|
1609
|
+
emitContext(adapter, "PostToolUse", sessionNameDisplayInstruction(name));
|
|
1610
|
+
} else if (name) {
|
|
1611
|
+
const due = sessionNameDisplayReminderDue(row);
|
|
1612
|
+
const command = extractBashCommand(payload?.tool_name, payload?.tool_input);
|
|
1613
|
+
if (due && !isSessionNameRemediationCommand(command, resolveBinName(coordRoot))) {
|
|
1614
|
+
stampSessionNameReminded(coordRoot, owner.instance_id, due);
|
|
1615
|
+
appendDebug(coordRoot, {
|
|
1616
|
+
ts: new Date().toISOString(),
|
|
1617
|
+
event_name: eventName,
|
|
1618
|
+
adapter,
|
|
1619
|
+
instance_id: owner.instance_id,
|
|
1620
|
+
effect: "session-name-display-reminded",
|
|
1621
|
+
session_name_pending: due,
|
|
1622
|
+
reminders: (row?.session_name_display_reminders ?? 0) + 1,
|
|
1623
|
+
});
|
|
1487
1624
|
const { emitContext } = await import("./adapter/output.ts");
|
|
1488
|
-
emitContext(adapter, "PostToolUse",
|
|
1625
|
+
emitContext(adapter, "PostToolUse", sessionNameDisplayReminder(due));
|
|
1489
1626
|
}
|
|
1490
|
-
} catch (err) {
|
|
1491
|
-
logError(coordRoot, err, { phase: "post-tool-use-session-name" });
|
|
1492
1627
|
}
|
|
1628
|
+
} catch (err) {
|
|
1629
|
+
logError(coordRoot, err, { phase: "post-tool-use-session-name" });
|
|
1493
1630
|
}
|
|
1631
|
+
}
|
|
1494
1632
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
logError(coordRoot, err, { phase: "post-tool-use-failure-release" });
|
|
1502
|
-
}
|
|
1633
|
+
function handlePostToolUseFailure(run: HookRun): void {
|
|
1634
|
+
const { coordRoot, payload, owner, data } = run;
|
|
1635
|
+
try {
|
|
1636
|
+
releaseClaimOnFailure(coordRoot, owner.instance_id, data, payload?.raw);
|
|
1637
|
+
} catch (err) {
|
|
1638
|
+
logError(coordRoot, err, { phase: "post-tool-use-failure-release" });
|
|
1503
1639
|
}
|
|
1640
|
+
}
|
|
1504
1641
|
|
|
1642
|
+
async function handleCompletedResponse(run: CompletedResponseRun): Promise<number> {
|
|
1643
|
+
const { coordRoot, adapter, eventName, raw, debugBase, ledgerRoute, hookStartedAt, hookClock } =
|
|
1644
|
+
run;
|
|
1645
|
+
const payload = parsePayload(raw, adapter);
|
|
1646
|
+
const owner = resolveOwner({ payload: payload?.raw ?? null, coordRoot });
|
|
1647
|
+
if (!owner) {
|
|
1648
|
+
appendDebug(coordRoot, { ...debugBase, skipped: "no-owner-resolved" });
|
|
1649
|
+
return 0;
|
|
1650
|
+
}
|
|
1651
|
+
const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
|
|
1652
|
+
const name = sessionNameDisplayPending(row);
|
|
1653
|
+
const text = typeof payload?.raw.text === "string" ? payload.raw.text : "";
|
|
1654
|
+
const sighting = matchSessionNameDisplay(row, text);
|
|
1655
|
+
if (sighting) {
|
|
1656
|
+
stampSessionNameSeen(coordRoot, owner.instance_id, sighting.pending);
|
|
1657
|
+
appendDebug(coordRoot, {
|
|
1658
|
+
...debugBase,
|
|
1659
|
+
effect: "session-name-display-stamped",
|
|
1660
|
+
session_name_pending: sighting.pending,
|
|
1661
|
+
...(sighting.displayed === sighting.pending
|
|
1662
|
+
? {}
|
|
1663
|
+
: { session_name_displayed: sighting.displayed, session_name_drift: true }),
|
|
1664
|
+
});
|
|
1665
|
+
} else if (!name) {
|
|
1666
|
+
// Nothing was owed. Distinct from a miss: conflating the two is what made
|
|
1667
|
+
// a latched Cursor session undiagnosable from this log.
|
|
1668
|
+
appendDebug(coordRoot, { ...debugBase, skipped: "no-pending-session-name" });
|
|
1669
|
+
} else {
|
|
1670
|
+
appendDebug(coordRoot, {
|
|
1671
|
+
...debugBase,
|
|
1672
|
+
skipped: "session-name-block-absent",
|
|
1673
|
+
session_name_pending: name,
|
|
1674
|
+
reply_bytes: text.length,
|
|
1675
|
+
reply_leads_with_fence: /^\s*`{3,}/.test(text),
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
if (ledgerRoute.state === "blocked") {
|
|
1679
|
+
appendDebug(coordRoot, {
|
|
1680
|
+
...debugBase,
|
|
1681
|
+
skipped: "v3-control-blocked",
|
|
1682
|
+
reason: ledgerRoute.reason,
|
|
1683
|
+
owner_source: owner.source,
|
|
1684
|
+
});
|
|
1685
|
+
return 0;
|
|
1686
|
+
}
|
|
1687
|
+
const statusBoxPresentStrict = scanAssistantStatusBoxPresent(undefined, text);
|
|
1688
|
+
// The hook parser needs the completed reply long enough to scan it, but
|
|
1689
|
+
// durable hook intake must never retain that body. Keep every other native
|
|
1690
|
+
// field so turn correlation remains available to the producer.
|
|
1691
|
+
const { text: _completedReply, ...privacySafeRaw } = payload?.raw ?? {};
|
|
1692
|
+
const privacySafePayload = payload ? { ...payload, raw: privacySafeRaw } : null;
|
|
1693
|
+
const v3Result = recordLiveHookSignalV3({
|
|
1694
|
+
coordRoot,
|
|
1695
|
+
route: ledgerRoute,
|
|
1696
|
+
eventName,
|
|
1697
|
+
payload: privacySafePayload,
|
|
1698
|
+
adapter,
|
|
1699
|
+
instanceId: owner.instance_id,
|
|
1700
|
+
hook_name: eventName,
|
|
1701
|
+
hook_duration_ms: Math.max(0, Math.floor(performance.now() - hookStartedAt)),
|
|
1702
|
+
monotonic_ns: hookClock.monotonic_ns,
|
|
1703
|
+
turn_ritual: {
|
|
1704
|
+
status_box_present: statusBoxPresentStrict,
|
|
1705
|
+
status_box_present_strict: statusBoxPresentStrict,
|
|
1706
|
+
session_name_required: name !== undefined,
|
|
1707
|
+
session_name_present: name ? assistantTextStartsWithSessionNameBlock(text, name) : false,
|
|
1708
|
+
},
|
|
1709
|
+
});
|
|
1710
|
+
observeHookDebug(hookHealthState, { event_v3_state: v3Result.state });
|
|
1711
|
+
appendDebug(coordRoot, {
|
|
1712
|
+
...debugBase,
|
|
1713
|
+
effect: "cursor-response-ritual-observed",
|
|
1714
|
+
owner_source: owner.source,
|
|
1715
|
+
event_v3_state: v3Result.state,
|
|
1716
|
+
status_box_present_strict: statusBoxPresentStrict,
|
|
1717
|
+
...(v3Result.state === "observed"
|
|
1718
|
+
? {
|
|
1719
|
+
generation_id: v3Result.generation_id,
|
|
1720
|
+
turn_id: v3Result.turn_id,
|
|
1721
|
+
response_observed_at: v3Result.observed_at,
|
|
1722
|
+
}
|
|
1723
|
+
: {}),
|
|
1724
|
+
});
|
|
1505
1725
|
return 0;
|
|
1506
1726
|
}
|
|
1507
1727
|
|
|
1508
|
-
function
|
|
1728
|
+
function scheduleRuntimeContextRetry(
|
|
1729
|
+
coordRoot: string,
|
|
1730
|
+
adapter: Adapter,
|
|
1731
|
+
nativeSessionId: string,
|
|
1732
|
+
): void {
|
|
1509
1733
|
const modulePath = process.argv[1];
|
|
1510
1734
|
if (!modulePath) return;
|
|
1511
1735
|
try {
|
|
1512
1736
|
const child = spawn(
|
|
1513
1737
|
process.execPath,
|
|
1514
|
-
[modulePath, "runtime-context-retry", "--adapter",
|
|
1738
|
+
[modulePath, "runtime-context-retry", "--adapter", adapter, "--session-id", nativeSessionId],
|
|
1515
1739
|
{
|
|
1516
1740
|
cwd: coordRoot,
|
|
1517
1741
|
detached: true,
|
|
@@ -1531,7 +1755,7 @@ async function runRuntimeContextRetryWorker(
|
|
|
1531
1755
|
extra: string[],
|
|
1532
1756
|
): Promise<number> {
|
|
1533
1757
|
const nativeSessionId = extraArg(extra, "--session-id");
|
|
1534
|
-
if (adapter
|
|
1758
|
+
if (!adapter || !adapterBehavior(adapter).runtimeContextRetry || !nativeSessionId) return 0;
|
|
1535
1759
|
for (const [index, delayMs] of [400, 1_000].entries()) {
|
|
1536
1760
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1537
1761
|
const route = resolveLiveEventLedgerRouteV3(coordRoot);
|
|
@@ -1547,6 +1771,27 @@ async function runRuntimeContextRetryWorker(
|
|
|
1547
1771
|
return 0;
|
|
1548
1772
|
}
|
|
1549
1773
|
|
|
1774
|
+
function reportSessionNameObservationUnavailable(
|
|
1775
|
+
coordRoot: string,
|
|
1776
|
+
instanceId: string,
|
|
1777
|
+
adapter: Adapter,
|
|
1778
|
+
eventName: string,
|
|
1779
|
+
reason: string,
|
|
1780
|
+
): void {
|
|
1781
|
+
appendDebug(coordRoot, {
|
|
1782
|
+
ts: new Date().toISOString(),
|
|
1783
|
+
event_name: eventName,
|
|
1784
|
+
adapter,
|
|
1785
|
+
instance_id: instanceId,
|
|
1786
|
+
effect: "session-name-observation-unavailable",
|
|
1787
|
+
reason,
|
|
1788
|
+
action: "fail-open",
|
|
1789
|
+
});
|
|
1790
|
+
process.stderr.write(
|
|
1791
|
+
`Harnery: session-name observation unavailable (${reason}); allowing this ${eventName} without display enforcement. The name remains pending.\n`,
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1550
1795
|
async function enforcePendingSessionNameDisplay(
|
|
1551
1796
|
coordRoot: string,
|
|
1552
1797
|
instanceId: string,
|
|
@@ -1554,6 +1799,7 @@ async function enforcePendingSessionNameDisplay(
|
|
|
1554
1799
|
payload: ParsedPayload | null,
|
|
1555
1800
|
coordination: Heartbeat | null,
|
|
1556
1801
|
): Promise<boolean> {
|
|
1802
|
+
if (coordEnv("WORKFLOW_CHILD") === "1") return true;
|
|
1557
1803
|
const name = sessionNameDisplayPending(coordination);
|
|
1558
1804
|
if (!name) return true;
|
|
1559
1805
|
|
|
@@ -1567,11 +1813,13 @@ async function enforcePendingSessionNameDisplay(
|
|
|
1567
1813
|
}
|
|
1568
1814
|
|
|
1569
1815
|
if (inspection.state === "unavailable") {
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1816
|
+
reportSessionNameObservationUnavailable(
|
|
1817
|
+
coordRoot,
|
|
1818
|
+
instanceId,
|
|
1819
|
+
adapter,
|
|
1820
|
+
"pre-tool-use",
|
|
1821
|
+
inspection.reason,
|
|
1822
|
+
);
|
|
1575
1823
|
return true;
|
|
1576
1824
|
}
|
|
1577
1825
|
|
|
@@ -1605,21 +1853,20 @@ function inspectDisplayEvidence(
|
|
|
1605
1853
|
payload: ParsedPayload | null,
|
|
1606
1854
|
coordination: Heartbeat | null,
|
|
1607
1855
|
): SessionNameDisplayInspection {
|
|
1608
|
-
|
|
1609
|
-
|
|
1856
|
+
const behavior = adapterBehavior(adapter);
|
|
1857
|
+
const inlineText = behavior.inlineAssistantText?.(payload);
|
|
1858
|
+
if (inlineText !== undefined) {
|
|
1859
|
+
return matchSessionNameDisplay(coordination, inlineText)
|
|
1610
1860
|
? { state: "present" }
|
|
1611
1861
|
: { state: "unavailable", reason: "transcript_not_ready" };
|
|
1612
1862
|
}
|
|
1613
1863
|
|
|
1614
|
-
//
|
|
1615
|
-
// inspection permanently unavailable and the latch never stamped.
|
|
1616
|
-
// the
|
|
1617
|
-
// the latch closes after one success.
|
|
1864
|
+
// Some adapters omit transcript_path on every event, which left this
|
|
1865
|
+
// inspection permanently unavailable and the latch never stamped. The
|
|
1866
|
+
// behavior may discover the transcript by session id; the scan only runs
|
|
1867
|
+
// while a name is pending, so the latch closes after one success.
|
|
1618
1868
|
const transcriptPath =
|
|
1619
|
-
payload?.transcript_path ??
|
|
1620
|
-
(adapter === "codex"
|
|
1621
|
-
? discoverCodexSessionTranscript(payload?.session_id ?? instanceId)
|
|
1622
|
-
: undefined);
|
|
1869
|
+
payload?.transcript_path ?? behavior.discoverTranscript?.(payload?.session_id ?? instanceId);
|
|
1623
1870
|
|
|
1624
1871
|
let verdict: SessionNameDisplayInspection = { state: "absent" };
|
|
1625
1872
|
for (const candidate of sessionNameDisplayAcceptedNames(coordination)) {
|
|
@@ -1913,7 +2160,7 @@ function releaseClaimOnFailure(
|
|
|
1913
2160
|
coordRoot,
|
|
1914
2161
|
owner: instanceId,
|
|
1915
2162
|
nativeSessionId: before?.session_id ?? instanceId,
|
|
1916
|
-
adapter:
|
|
2163
|
+
adapter: sharedAdapterFromPlatform(before?.platform, { context: "hook-claim-release" }),
|
|
1917
2164
|
operation: "released",
|
|
1918
2165
|
path: canonical,
|
|
1919
2166
|
access: "write",
|
|
@@ -1956,22 +2203,22 @@ async function emitUserPromptSubmitSystemMessage(
|
|
|
1956
2203
|
recoveryBriefing = "",
|
|
1957
2204
|
hostPromptContext = "",
|
|
1958
2205
|
): Promise<boolean> {
|
|
2206
|
+
// Workflow children get no prompt-time coordination context, matching
|
|
2207
|
+
// SessionStart. The turn ritual is not enforced for them, the naming nudge
|
|
2208
|
+
// would register them as named peers, and rendering drains peer mail.
|
|
2209
|
+
if (coordEnv("WORKFLOW_CHILD") === "1") return false;
|
|
2210
|
+
const behavior = adapterBehavior(adapter);
|
|
1959
2211
|
const { renderPromptContext } = await import("../agents/render/prompt-context.ts");
|
|
1960
2212
|
let additionalContext = renderPromptContext({
|
|
1961
2213
|
coordRoot,
|
|
1962
2214
|
instanceId,
|
|
1963
2215
|
sessionId,
|
|
1964
2216
|
sessionNameNudge: true,
|
|
1965
|
-
|
|
1966
|
-
hostPromptReminder: adapter === "codex" || adapter === "cursor",
|
|
1967
|
-
statusFooterNudge: adapter === "codex",
|
|
1968
|
-
turnRitualNudge: adapter === "claude-code" ? adapter : undefined,
|
|
2217
|
+
...behavior.promptContextNudges,
|
|
1969
2218
|
}).trim();
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
|
|
1974
|
-
}
|
|
2219
|
+
const fileLinkContext = behavior.fileLinkContext?.(coordRoot, workspaceCwd) ?? "";
|
|
2220
|
+
if (fileLinkContext) {
|
|
2221
|
+
additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
|
|
1975
2222
|
}
|
|
1976
2223
|
if (recoveryBriefing) {
|
|
1977
2224
|
additionalContext = [additionalContext, recoveryBriefing].filter(Boolean).join("\n\n");
|
|
@@ -2022,16 +2269,9 @@ async function emitSubagentStartContext(
|
|
|
2022
2269
|
}
|
|
2023
2270
|
|
|
2024
2271
|
function adapterPlatform(adapter: Adapter): string {
|
|
2025
|
-
if (adapter === "claude-code") return "claude-code";
|
|
2026
2272
|
return adapter;
|
|
2027
2273
|
}
|
|
2028
2274
|
|
|
2029
|
-
function adapterFromPlatform(platform: unknown): Adapter {
|
|
2030
|
-
if (platform === "cursor") return "cursor";
|
|
2031
|
-
if (platform === "codex") return "codex";
|
|
2032
|
-
return "claude-code";
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
2275
|
async function emitSessionStartSystemMessage(
|
|
2036
2276
|
coordRoot: string,
|
|
2037
2277
|
instanceId: string,
|
|
@@ -2041,6 +2281,7 @@ async function emitSessionStartSystemMessage(
|
|
|
2041
2281
|
recoveryBriefing = "",
|
|
2042
2282
|
backupCue = "",
|
|
2043
2283
|
): Promise<boolean> {
|
|
2284
|
+
const behavior = adapterBehavior(adapter);
|
|
2044
2285
|
const workflowChild = coordEnv("WORKFLOW_CHILD") === "1";
|
|
2045
2286
|
let additionalContext = "";
|
|
2046
2287
|
// Opportunistic reconciliation makes normal session starts a failsafe for
|
|
@@ -2061,16 +2302,15 @@ async function emitSessionStartSystemMessage(
|
|
|
2061
2302
|
instanceId,
|
|
2062
2303
|
sessionId,
|
|
2063
2304
|
agentName: agentName || undefined,
|
|
2064
|
-
platformLabel:
|
|
2065
|
-
adapter === "claude-code" ? undefined : adapter === "cursor" ? "Cursor" : "Codex",
|
|
2305
|
+
platformLabel: behavior.sessionStartPlatformLabel,
|
|
2066
2306
|
}).trim();
|
|
2067
2307
|
}
|
|
2068
2308
|
|
|
2069
|
-
// Effect (
|
|
2070
|
-
// context. Was a standalone additionalContext emission
|
|
2071
|
-
// journal-on-start adapter; now that agent-hook is the
|
|
2072
|
-
// entry, it folds in here.
|
|
2073
|
-
if (
|
|
2309
|
+
// Effect (journal-maintaining adapters): merge the journal recovery cue into
|
|
2310
|
+
// the session-start context. Was a standalone additionalContext emission
|
|
2311
|
+
// from the previous journal-on-start adapter; now that agent-hook is the
|
|
2312
|
+
// single SessionStart entry, it folds in here.
|
|
2313
|
+
if (behavior.journalMaintenance && !workflowChild) {
|
|
2074
2314
|
const cue = journalRecoveryCue(coordRoot);
|
|
2075
2315
|
if (cue) additionalContext = [additionalContext, cue].filter(Boolean).join("\n\n");
|
|
2076
2316
|
}
|
|
@@ -2080,19 +2320,14 @@ async function emitSessionStartSystemMessage(
|
|
|
2080
2320
|
if (backupCue && !workflowChild) {
|
|
2081
2321
|
additionalContext = [additionalContext, backupCue].filter(Boolean).join("\n\n");
|
|
2082
2322
|
}
|
|
2083
|
-
if (
|
|
2084
|
-
const fileLinkContext =
|
|
2323
|
+
if (!workflowChild && behavior.bridgeFor(emittedData.cwd)) {
|
|
2324
|
+
const fileLinkContext = behavior.fileLinkContext?.(coordRoot, emittedData.cwd) ?? "";
|
|
2085
2325
|
if (fileLinkContext) {
|
|
2086
2326
|
additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
|
|
2087
2327
|
}
|
|
2088
|
-
const
|
|
2089
|
-
if (
|
|
2090
|
-
additionalContext = [
|
|
2091
|
-
additionalContext,
|
|
2092
|
-
`Harnery hybrid warning: ${bridge.detail}. Run \`${resolveBinName(coordRoot)} doctor\` for the repair hint.`,
|
|
2093
|
-
]
|
|
2094
|
-
.filter(Boolean)
|
|
2095
|
-
.join("\n\n");
|
|
2328
|
+
const warning = behavior.bridgeWarning?.(coordRoot, emittedData.cwd);
|
|
2329
|
+
if (warning) {
|
|
2330
|
+
additionalContext = [additionalContext, warning].filter(Boolean).join("\n\n");
|
|
2096
2331
|
}
|
|
2097
2332
|
}
|
|
2098
2333
|
const { emitContext } = await import("./adapter/output.ts");
|
|
@@ -2111,6 +2346,7 @@ function completeRecoveryInjection(
|
|
|
2111
2346
|
|
|
2112
2347
|
main()
|
|
2113
2348
|
.then(async (code) => {
|
|
2349
|
+
clearHookRunMarker(hookRunMarkerPath);
|
|
2114
2350
|
writeHookHealthCompletion(hookHealthState, {
|
|
2115
2351
|
finished_at_ms: performance.now(),
|
|
2116
2352
|
finished_rss_bytes: process.memoryUsage().rss,
|
|
@@ -2125,6 +2361,7 @@ main()
|
|
|
2125
2361
|
pid: process.pid,
|
|
2126
2362
|
phase: "top-level",
|
|
2127
2363
|
});
|
|
2364
|
+
clearHookRunMarker(hookRunMarkerPath);
|
|
2128
2365
|
writeHookHealthCompletion(hookHealthState, {
|
|
2129
2366
|
finished_at_ms: performance.now(),
|
|
2130
2367
|
finished_rss_bytes: process.memoryUsage().rss,
|