harnery 0.40.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commander.js +1 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +25 -19
- package/dist/commands/browse-ai.d.ts.map +1 -1
- package/dist/commands/browse-ai.js +4 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +141 -10
- package/dist/commands/checkpoint.js +8 -12
- package/dist/commands/deinit.d.ts +1 -1
- package/dist/commands/deinit.d.ts.map +1 -1
- package/dist/commands/deinit.js +8 -4
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +2 -0
- package/dist/commands/doctor.js +10 -2
- package/dist/commands/fetch.d.ts.map +1 -1
- package/dist/commands/fetch.js +10 -0
- package/dist/commands/init.d.ts +19 -3
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +232 -104
- package/dist/commands/instructions.js +5 -4
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +2 -1
- package/dist/commands/webview.d.ts +4 -0
- package/dist/commands/webview.d.ts.map +1 -0
- package/dist/commands/webview.js +94 -0
- package/dist/core/adapter.d.ts +50 -1
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +59 -1
- package/dist/core/adapters/attestation.d.ts.map +1 -1
- package/dist/core/adapters/attestation.js +3 -2
- package/dist/core/adapters/profiles.d.ts +20 -3
- package/dist/core/adapters/profiles.d.ts.map +1 -1
- package/dist/core/adapters/profiles.js +32 -1
- package/dist/core/adapters/registry.d.ts.map +1 -1
- package/dist/core/adapters/registry.js +33 -0
- package/dist/core/agents/canonical-emit.d.ts +1 -1
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +2 -7
- package/dist/core/agents/cli.js +18 -16
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +7 -2
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +3 -9
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +2 -8
- package/dist/core/agents/rules/stop-hook.d.ts +2 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +1 -1
- package/dist/core/agents/session-events.d.ts +6 -2
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +105 -26
- package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -1
- package/dist/core/agents/session-finalization-state-v3.js +2 -1
- package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
- package/dist/core/agents/session-finalizer-v3.js +5 -4
- package/dist/core/agents/session-name-display.d.ts +28 -0
- package/dist/core/agents/session-name-display.d.ts.map +1 -1
- package/dist/core/agents/session-name-display.js +43 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +10 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +21 -2
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +1 -0
- package/dist/core/agents/state/live-coordination-writer.js +3 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +2 -10
- package/dist/core/agents/turn-changes.d.ts +17 -0
- package/dist/core/agents/turn-changes.d.ts.map +1 -0
- package/dist/core/agents/turn-changes.js +76 -0
- package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
- package/dist/core/artifacts/delivery-card.js +2 -1
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +4 -3
- package/dist/core/config.d.ts +5 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +14 -0
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +2 -1
- package/dist/core/conversations/archive.d.ts.map +1 -1
- package/dist/core/conversations/archive.js +4 -3
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +3 -2
- package/dist/core/events/legacy-storage/canary.d.ts.map +1 -1
- package/dist/core/events/legacy-storage/canary.js +5 -4
- package/dist/core/events/legacy-storage/compression.d.ts.map +1 -1
- package/dist/core/events/legacy-storage/compression.js +2 -1
- package/dist/core/events/v3/adapter-id.d.ts +10 -2
- package/dist/core/events/v3/adapter-id.d.ts.map +1 -1
- package/dist/core/events/v3/adapter-id.js +16 -0
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +2 -1
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +5 -4
- package/dist/core/events/v3/capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/capabilities.js +17 -0
- package/dist/core/events/v3/contract.d.ts +5 -0
- package/dist/core/events/v3/contract.d.ts.map +1 -1
- package/dist/core/events/v3/contract.js +7 -1
- package/dist/core/events/v3/control-witness.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.js +3 -2
- package/dist/core/events/v3/coordination-transaction-recovery.d.ts.map +1 -1
- package/dist/core/events/v3/coordination-transaction-recovery.js +4 -3
- package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -1
- package/dist/core/events/v3/fingerprint-keys.js +7 -6
- package/dist/core/events/v3/generated.d.ts +1 -1
- package/dist/core/events/v3/generated.js +1 -1
- package/dist/core/events/v3/live-feed.d.ts.map +1 -1
- package/dist/core/events/v3/live-feed.js +5 -4
- package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/command-recorder.js +8 -7
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +8 -7
- package/dist/core/events/v3/producers/diagnostic-summaries.d.ts.map +1 -1
- package/dist/core/events/v3/producers/diagnostic-summaries.js +5 -4
- package/dist/core/events/v3/producers/hook-base.d.ts +1 -0
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook-base.js +10 -4
- package/dist/core/events/v3/producers/hook.d.ts +2 -0
- package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook.js +16 -1
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +16 -6
- package/dist/core/events/v3/producers/recorder.d.ts +2 -4
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +109 -101
- package/dist/core/events/v3/recovery.d.ts.map +1 -1
- package/dist/core/events/v3/recovery.js +4 -3
- package/dist/core/events/v3/support-storage/inventory.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/inventory.js +2 -1
- package/dist/core/events/v3/support-storage/maintenance.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/maintenance.js +4 -3
- package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/pack-reader.js +4 -3
- package/dist/core/events/v3/support-storage/pack-writer.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/pack-writer.js +6 -5
- package/dist/core/events/v3/tool-line-changes.d.ts +8 -0
- package/dist/core/events/v3/tool-line-changes.d.ts.map +1 -0
- package/dist/core/events/v3/tool-line-changes.js +135 -0
- package/dist/core/events/v3/writer.d.ts.map +1 -1
- package/dist/core/events/v3/writer.js +8 -7
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +6 -5
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +12 -11
- package/dist/core/governor/state.d.ts.map +1 -1
- package/dist/core/governor/state.js +7 -6
- package/dist/core/guard/evaluator.js +1 -0
- package/dist/core/guard/storage.d.ts.map +1 -1
- package/dist/core/guard/storage.js +3 -2
- package/dist/core/hooks/adapter/behaviors/claude-code.d.ts +4 -0
- package/dist/core/hooks/adapter/behaviors/claude-code.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/claude-code.js +31 -0
- package/dist/core/hooks/adapter/behaviors/codex.d.ts +15 -0
- package/dist/core/hooks/adapter/behaviors/codex.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/codex.js +82 -0
- package/dist/core/hooks/adapter/behaviors/cursor.d.ts +7 -0
- package/dist/core/hooks/adapter/behaviors/cursor.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/cursor.js +35 -0
- package/dist/core/hooks/adapter/behaviors/index.d.ts +11 -0
- package/dist/core/hooks/adapter/behaviors/index.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/index.js +20 -0
- package/dist/core/hooks/adapter/behaviors/openclaw.d.ts +7 -0
- package/dist/core/hooks/adapter/behaviors/openclaw.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/openclaw.js +34 -0
- package/dist/core/hooks/adapter/behaviors/opencode.d.ts +7 -0
- package/dist/core/hooks/adapter/behaviors/opencode.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/opencode.js +34 -0
- package/dist/core/hooks/adapter/behaviors/types.d.ts +89 -0
- package/dist/core/hooks/adapter/behaviors/types.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/types.js +1 -0
- package/dist/core/hooks/adapter/detect.d.ts +1 -1
- package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
- package/dist/core/hooks/adapter/detect.js +2 -3
- package/dist/core/hooks/adapter/events.d.ts +29 -1
- package/dist/core/hooks/adapter/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +31 -0
- package/dist/core/hooks/adapter/opencode-plugin.d.ts +60 -0
- package/dist/core/hooks/adapter/opencode-plugin.d.ts.map +1 -0
- package/dist/core/hooks/adapter/opencode-plugin.js +129 -0
- package/dist/core/hooks/adapter/output.d.ts +7 -0
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +8 -1
- package/dist/core/hooks/adapter/wiring.d.ts +15 -1
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +74 -11
- package/dist/core/hooks/cli.js +635 -529
- package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
- package/dist/core/hooks/effects/image-capture.js +2 -1
- package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -1
- package/dist/core/hooks/prompt-context/contract.js +1 -0
- package/dist/core/hooks/resolve/transcript.d.ts +1 -1
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +35 -4
- package/dist/core/hooks/session-name-presence.d.ts +3 -1
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
- package/dist/core/hooks/session-name-presence.js +7 -2
- package/dist/core/hooks/shell-waiter.d.ts +2 -0
- package/dist/core/hooks/shell-waiter.d.ts.map +1 -0
- package/dist/core/hooks/shell-waiter.js +173 -0
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +5 -4
- package/dist/core/semantic/review.d.ts.map +1 -1
- package/dist/core/semantic/review.js +5 -4
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +15 -14
- package/dist/core/semantic/storage.d.ts.map +1 -1
- package/dist/core/semantic/storage.js +5 -4
- package/dist/core/storage/atomic-json.d.ts.map +1 -1
- package/dist/core/storage/atomic-json.js +7 -3
- package/dist/core/storage/durable-history.d.ts.map +1 -1
- package/dist/core/storage/durable-history.js +6 -5
- package/dist/core/storage/maintenance-providers.d.ts.map +1 -1
- package/dist/core/storage/maintenance-providers.js +2 -1
- package/dist/core/storage/maintenance.d.ts.map +1 -1
- package/dist/core/storage/maintenance.js +7 -6
- package/dist/core/storage/metrics.d.ts.map +1 -1
- package/dist/core/storage/metrics.js +5 -1
- package/dist/core/storage/modes.d.ts +17 -0
- package/dist/core/storage/modes.d.ts.map +1 -0
- package/dist/core/storage/modes.js +47 -0
- package/dist/core/storage/process-log.d.ts.map +1 -1
- package/dist/core/storage/process-log.js +3 -2
- package/dist/core/storage/receipts.d.ts.map +1 -1
- package/dist/core/storage/receipts.js +4 -3
- package/dist/core/storage/segments.d.ts.map +1 -1
- package/dist/core/storage/segments.js +7 -6
- package/dist/core/supervisor/hook-health.d.ts +2 -1
- package/dist/core/supervisor/hook-health.d.ts.map +1 -1
- package/dist/core/supervisor/hook-health.js +4 -3
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +7 -6
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +8 -7
- package/dist/core/workflow/adapters.d.ts +7 -0
- package/dist/core/workflow/adapters.d.ts.map +1 -1
- package/dist/core/workflow/adapters.js +9 -0
- package/dist/core/workflow/approvals.d.ts.map +1 -1
- package/dist/core/workflow/approvals.js +6 -5
- package/dist/core/workflow/billing.d.ts.map +1 -1
- package/dist/core/workflow/billing.js +33 -4
- package/dist/core/workflow/durable-record.d.ts.map +1 -1
- package/dist/core/workflow/durable-record.js +4 -3
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +2 -3
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +3 -2
- package/dist/core/workflow/spawn-opencode.d.ts +36 -0
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -0
- package/dist/core/workflow/spawn-opencode.js +132 -0
- package/dist/core/workflow/transcript.d.ts.map +1 -1
- package/dist/core/workflow/transcript.js +3 -2
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +5 -4
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +3 -2
- package/dist/core/workflow/workspaces/paths.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/paths.js +2 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +10 -9
- package/dist/lazy-commands.js +8 -1
- package/dist/lib/agent-browser/client.d.ts +9 -0
- package/dist/lib/agent-browser/client.d.ts.map +1 -1
- package/dist/lib/agent-browser/client.js +13 -0
- package/dist/lib/browser/client.d.ts +35 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +117 -8
- package/dist/lib/browser/identity.d.ts +25 -0
- package/dist/lib/browser/identity.d.ts.map +1 -0
- package/dist/lib/browser/identity.js +66 -0
- package/dist/lib/browser/index.d.ts +2 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -1
- package/dist/lib/browser/launch-args.d.ts +26 -0
- package/dist/lib/browser/launch-args.d.ts.map +1 -1
- package/dist/lib/browser/launch-args.js +41 -1
- package/dist/lib/browser/user-agent.d.ts +53 -0
- package/dist/lib/browser/user-agent.d.ts.map +1 -0
- package/dist/lib/browser/user-agent.js +79 -0
- package/dist/lib/docs-metadata-managed.d.ts +1 -0
- package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
- package/dist/lib/docs-metadata-managed.js +17 -1
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +4 -9
- package/dist/lib/instructions/apply.d.ts.map +1 -1
- package/dist/lib/instructions/apply.js +2 -0
- package/dist/lib/pace/index.d.ts +2 -0
- package/dist/lib/pace/index.d.ts.map +1 -0
- package/dist/lib/pace/index.js +1 -0
- package/dist/lib/pace/pace.d.ts +95 -0
- package/dist/lib/pace/pace.d.ts.map +1 -0
- package/dist/lib/pace/pace.js +313 -0
- package/dist/lib/tunnel/forward-headers.d.ts +11 -0
- package/dist/lib/tunnel/forward-headers.d.ts.map +1 -0
- package/dist/lib/tunnel/forward-headers.js +17 -0
- package/dist/lib/tunnel/gate.js +15 -10
- package/dist/lib/webview/index.d.ts +2 -0
- package/dist/lib/webview/index.d.ts.map +1 -0
- package/dist/lib/webview/index.js +1 -0
- package/dist/lib/webview/webview.d.ts +116 -0
- package/dist/lib/webview/webview.d.ts.map +1 -0
- package/dist/lib/webview/webview.js +289 -0
- package/opencode-plugin/package.json +19 -0
- package/opencode-plugin/src/index.ts +791 -0
- package/package.json +11 -4
- package/schemas/config.schema.json +13 -0
- package/schemas/event-v3.schema.json +164 -0
- package/src/commander.ts +7 -0
- package/src/commands/agents.ts +33 -18
- package/src/commands/browse-ai.ts +8 -0
- package/src/commands/browse.ts +189 -10
- package/src/commands/checkpoint.ts +7 -9
- package/src/commands/deinit.ts +9 -4
- package/src/commands/docs.ts +2 -0
- package/src/commands/doctor.ts +11 -2
- package/src/commands/fetch.ts +14 -0
- package/src/commands/init.ts +281 -119
- package/src/commands/instructions.ts +8 -4
- package/src/commands/ledger-v3.ts +2 -1
- package/src/commands/webview.ts +159 -0
- package/src/core/adapter.ts +102 -1
- package/src/core/adapters/attestation.ts +3 -2
- package/src/core/adapters/profiles.ts +42 -1
- package/src/core/adapters/registry.ts +37 -0
- package/src/core/agents/canonical-emit.ts +2 -5
- package/src/core/agents/cli.ts +18 -17
- package/src/core/agents/coord-client.ts +9 -3
- package/src/core/agents/rules/claim-conflict.ts +3 -8
- package/src/core/agents/rules/commit-conflict.ts +2 -7
- package/src/core/agents/rules/stop-hook.ts +3 -1
- package/src/core/agents/session-events.ts +120 -26
- package/src/core/agents/session-finalization-state-v3.ts +2 -1
- package/src/core/agents/session-finalizer-v3.ts +5 -4
- package/src/core/agents/session-name-display.ts +48 -0
- package/src/core/agents/state/heartbeat-reader.ts +7 -0
- package/src/core/agents/state/heartbeat-writer.ts +24 -2
- package/src/core/agents/state/live-coordination-view.ts +1 -0
- package/src/core/agents/state/live-coordination-writer.ts +3 -0
- package/src/core/agents/state/stale-sweep.ts +2 -9
- package/src/core/agents/turn-changes.ts +97 -0
- package/src/core/artifacts/delivery-card.ts +2 -1
- package/src/core/artifacts/index.ts +4 -3
- package/src/core/config.ts +23 -0
- package/src/core/context/index.ts +2 -1
- package/src/core/conversations/archive.ts +4 -3
- package/src/core/diagnostics/bundle.ts +3 -2
- package/src/core/events/legacy-storage/canary.ts +5 -4
- package/src/core/events/legacy-storage/compression.ts +2 -1
- package/src/core/events/v3/adapter-id.ts +29 -1
- package/src/core/events/v3/authority-outbox.ts +2 -1
- package/src/core/events/v3/bootstrap.ts +5 -4
- package/src/core/events/v3/capabilities.ts +17 -0
- package/src/core/events/v3/contract.ts +9 -1
- package/src/core/events/v3/control-witness.ts +3 -2
- package/src/core/events/v3/coordination-transaction-recovery.ts +4 -3
- package/src/core/events/v3/fingerprint-keys.ts +7 -6
- package/src/core/events/v3/generated.ts +1 -1
- package/src/core/events/v3/live-feed.ts +5 -4
- package/src/core/events/v3/producers/command-recorder.ts +8 -6
- package/src/core/events/v3/producers/coordination-recorder.ts +8 -7
- package/src/core/events/v3/producers/diagnostic-summaries.ts +5 -4
- package/src/core/events/v3/producers/hook-base.ts +11 -4
- package/src/core/events/v3/producers/hook.ts +19 -1
- package/src/core/events/v3/producers/intake.ts +18 -6
- package/src/core/events/v3/producers/recorder.ts +123 -114
- package/src/core/events/v3/recovery.ts +4 -3
- package/src/core/events/v3/support-storage/inventory.ts +2 -1
- package/src/core/events/v3/support-storage/maintenance.ts +8 -3
- package/src/core/events/v3/support-storage/pack-reader.ts +4 -3
- package/src/core/events/v3/support-storage/pack-writer.ts +6 -5
- package/src/core/events/v3/tool-line-changes.ts +139 -0
- package/src/core/events/v3/writer.ts +8 -7
- package/src/core/governor/planning.ts +6 -5
- package/src/core/governor/service.ts +12 -11
- package/src/core/governor/state.ts +7 -6
- package/src/core/guard/evaluator.ts +1 -0
- package/src/core/guard/storage.ts +3 -2
- package/src/core/hooks/adapter/behaviors/claude-code.ts +33 -0
- package/src/core/hooks/adapter/behaviors/codex.ts +96 -0
- package/src/core/hooks/adapter/behaviors/cursor.ts +37 -0
- package/src/core/hooks/adapter/behaviors/index.ts +31 -0
- package/src/core/hooks/adapter/behaviors/openclaw.ts +36 -0
- package/src/core/hooks/adapter/behaviors/opencode.ts +36 -0
- package/src/core/hooks/adapter/behaviors/types.ts +110 -0
- package/src/core/hooks/adapter/detect.ts +2 -3
- package/src/core/hooks/adapter/events.ts +53 -1
- package/src/core/hooks/adapter/opencode-plugin.ts +177 -0
- package/src/core/hooks/adapter/output.ts +8 -1
- package/src/core/hooks/adapter/wiring.ts +85 -11
- package/src/core/hooks/cli.ts +733 -537
- package/src/core/hooks/effects/image-capture.ts +2 -1
- package/src/core/hooks/prompt-context/contract.ts +1 -0
- package/src/core/hooks/resolve/transcript.ts +42 -5
- package/src/core/hooks/session-name-presence.ts +10 -3
- package/src/core/hooks/shell-waiter.ts +166 -0
- package/src/core/resources/service.ts +5 -4
- package/src/core/semantic/review.ts +5 -4
- package/src/core/semantic/service.ts +15 -14
- package/src/core/semantic/storage.ts +5 -4
- package/src/core/storage/atomic-json.ts +7 -3
- package/src/core/storage/durable-history.ts +6 -5
- package/src/core/storage/maintenance-providers.ts +2 -1
- package/src/core/storage/maintenance.ts +7 -6
- package/src/core/storage/metrics.ts +5 -1
- package/src/core/storage/modes.ts +53 -0
- package/src/core/storage/process-log.ts +3 -2
- package/src/core/storage/receipts.ts +4 -3
- package/src/core/storage/segments.ts +7 -6
- package/src/core/supervisor/hook-health.ts +4 -4
- package/src/core/supervisor/service.ts +7 -6
- package/src/core/work/state.ts +8 -7
- package/src/core/workflow/adapters.ts +15 -0
- package/src/core/workflow/approvals.ts +6 -5
- package/src/core/workflow/billing.ts +34 -4
- package/src/core/workflow/durable-record.ts +4 -3
- package/src/core/workflow/live-session-v3.ts +2 -3
- package/src/core/workflow/run-state.ts +3 -2
- package/src/core/workflow/spawn-opencode.ts +150 -0
- package/src/core/workflow/transcript.ts +3 -2
- package/src/core/workflow/workspaces/leases.ts +5 -4
- package/src/core/workflow/workspaces/local-git.ts +3 -2
- package/src/core/workflow/workspaces/paths.ts +2 -1
- package/src/core/workflow/workspaces/state.ts +10 -9
- package/src/lazy-commands.ts +11 -1
- package/src/lib/agent-browser/client.ts +18 -0
- package/src/lib/browser/client.ts +147 -9
- package/src/lib/browser/identity.ts +64 -0
- package/src/lib/browser/index.ts +15 -1
- package/src/lib/browser/launch-args.ts +53 -1
- package/src/lib/browser/user-agent.ts +130 -0
- package/src/lib/docs-metadata-managed.ts +21 -0
- package/src/lib/identities/assume.ts +7 -7
- package/src/lib/instructions/apply.ts +2 -0
- package/src/lib/pace/index.ts +14 -0
- package/src/lib/pace/pace.ts +370 -0
- package/src/lib/tunnel/forward-headers.ts +16 -0
- package/src/lib/tunnel/gate.ts +18 -10
- package/src/lib/webview/index.ts +13 -0
- package/src/lib/webview/webview.ts +457 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `writeSessionEvent` records command spans in the canonical V3 ledger.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { randomBytes } from "node:crypto";
|
|
7
|
+
import { createHash, type Hash, randomBytes } from "node:crypto";
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
9
|
import { resolve } from "node:path";
|
|
10
10
|
// Kept dependency-light: vendored verbatim into a downstream consumer, so no coordEnv import.
|
|
@@ -64,18 +64,52 @@ export function readLastIntent(instanceId?: string): string | null {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
type OutputStream = "stdout" | "stderr" | "combined";
|
|
68
|
+
|
|
69
|
+
interface OutputSummary {
|
|
70
|
+
stream: OutputStream;
|
|
71
|
+
bytes: number;
|
|
72
|
+
lines: number;
|
|
73
|
+
chunks: number;
|
|
74
|
+
hash: Hash;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface PendingCommand {
|
|
78
|
+
coordRoot: string;
|
|
79
|
+
fields: Record<string, unknown>;
|
|
80
|
+
streams: Map<OutputStream, OutputSummary>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const pendingCommands = new Map<string, PendingCommand>();
|
|
84
|
+
let exitFlushInstalled = false;
|
|
85
|
+
|
|
86
|
+
function commandKey(fields: Record<string, unknown>): string | undefined {
|
|
87
|
+
return typeof fields.instance_id === "string" && typeof fields.cmd_id === "string"
|
|
88
|
+
? JSON.stringify([fields.instance_id, fields.cmd_id])
|
|
89
|
+
: undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function flushCommandOutput(command: PendingCommand): void {
|
|
93
|
+
for (const summary of command.streams.values()) {
|
|
94
|
+
recordCommandObservation("command.output_observed", command.fields, command.coordRoot, summary);
|
|
95
|
+
}
|
|
96
|
+
command.streams.clear();
|
|
97
|
+
}
|
|
68
98
|
|
|
69
99
|
/** Emit a command/narration event to the canonical stream. Swallows every
|
|
70
100
|
* error and skips when identity can't be resolved: telemetry must never break
|
|
71
101
|
* or slow down a command. */
|
|
72
|
-
function recordCommandObservation(
|
|
102
|
+
function recordCommandObservation(
|
|
103
|
+
type: SessionEventType,
|
|
104
|
+
fields: Record<string, unknown>,
|
|
105
|
+
coordRoot: string,
|
|
106
|
+
summary?: OutputSummary,
|
|
107
|
+
): boolean {
|
|
73
108
|
const instanceId = typeof fields.instance_id === "string" ? fields.instance_id : undefined;
|
|
74
|
-
if (!instanceId) return;
|
|
109
|
+
if (!instanceId) return false;
|
|
75
110
|
try {
|
|
76
|
-
const coordRoot = coordinationRootPath();
|
|
77
111
|
const route = resolveLiveEventLedgerRouteV3(coordRoot);
|
|
78
|
-
if (route.state === "blocked") return;
|
|
112
|
+
if (route.state === "blocked") return false;
|
|
79
113
|
const liveInstanceId = liveInstanceIdV3(instanceId);
|
|
80
114
|
const hook = readHookProducerStateByInstanceV3(coordRoot, liveInstanceId);
|
|
81
115
|
if (!hook) {
|
|
@@ -84,10 +118,10 @@ function recordCommandObservation(type: SessionEventType, fields: Record<string,
|
|
|
84
118
|
instance_id: instanceId,
|
|
85
119
|
reason: "hook_generation_not_found",
|
|
86
120
|
});
|
|
87
|
-
return;
|
|
121
|
+
return false;
|
|
88
122
|
}
|
|
89
|
-
const command = commandSignalAndObservation(type, fields);
|
|
90
|
-
if (!command) return;
|
|
123
|
+
const command = commandSignalAndObservation(type, fields, summary);
|
|
124
|
+
if (!command) return false;
|
|
91
125
|
const result = recordCommandSignalV3({
|
|
92
126
|
coordRoot,
|
|
93
127
|
mode: route.mode,
|
|
@@ -105,7 +139,7 @@ function recordCommandObservation(type: SessionEventType, fields: Record<string,
|
|
|
105
139
|
const expectedLifecycleReopenGap =
|
|
106
140
|
result.reason === "turn_not_started" &&
|
|
107
141
|
hook.session_start_derivation === "approved_lifecycle_reopen";
|
|
108
|
-
if (expectedLifecycleReopenGap) return;
|
|
142
|
+
if (expectedLifecycleReopenGap) return false;
|
|
109
143
|
writeProducerDiagnosticV3(coordRoot, "command_emit_unjoinable", {
|
|
110
144
|
type,
|
|
111
145
|
instance_id: instanceId,
|
|
@@ -120,10 +154,10 @@ function recordCommandObservation(type: SessionEventType, fields: Record<string,
|
|
|
120
154
|
result_state: result.state,
|
|
121
155
|
});
|
|
122
156
|
}
|
|
157
|
+
return result.state === "recorded" || result.state === "already_recorded";
|
|
123
158
|
} catch (error) {
|
|
124
159
|
// Telemetry must never break the command, but the loss is preserved.
|
|
125
160
|
try {
|
|
126
|
-
const coordRoot = coordinationRootPath();
|
|
127
161
|
writeProducerDiagnosticV3(coordRoot, "command_emit_failed", {
|
|
128
162
|
type,
|
|
129
163
|
instance_id: instanceId,
|
|
@@ -132,17 +166,26 @@ function recordCommandObservation(type: SessionEventType, fields: Record<string,
|
|
|
132
166
|
} catch {
|
|
133
167
|
/* diagnostics are best-effort */
|
|
134
168
|
}
|
|
169
|
+
return false;
|
|
135
170
|
}
|
|
136
171
|
}
|
|
137
172
|
|
|
173
|
+
/** Count the non-empty lines in one output chunk; a single event may carry many. */
|
|
174
|
+
export function countOutputLines(text: string): number {
|
|
175
|
+
if (!text) return 0;
|
|
176
|
+
let count = 0;
|
|
177
|
+
for (const line of text.split("\n")) if (line.length > 0) count += 1;
|
|
178
|
+
return count;
|
|
179
|
+
}
|
|
180
|
+
|
|
138
181
|
function commandSignalAndObservation(
|
|
139
182
|
type: SessionEventType,
|
|
140
183
|
fields: Record<string, unknown>,
|
|
184
|
+
summary?: OutputSummary,
|
|
141
185
|
): { signal: CommandSignalV3; observation: CommandObservationV3 } | undefined {
|
|
142
186
|
const commandId = typeof fields.cmd_id === "string" ? fields.cmd_id : undefined;
|
|
143
187
|
if (!commandId) return undefined;
|
|
144
188
|
if (type === "command.started") {
|
|
145
|
-
outputSequence.set(commandId, 0);
|
|
146
189
|
const command = typeof fields.cmd === "string" ? fields.cmd : "";
|
|
147
190
|
const executable = command.trim().split(/\s+/, 1)[0] || "unknown";
|
|
148
191
|
return {
|
|
@@ -161,24 +204,25 @@ function commandSignalAndObservation(
|
|
|
161
204
|
};
|
|
162
205
|
}
|
|
163
206
|
if (type === "command.output_observed") {
|
|
164
|
-
|
|
165
|
-
outputSequence.set(commandId, sequence);
|
|
166
|
-
const line = typeof fields.line === "string" ? fields.line : "";
|
|
167
|
-
const stream =
|
|
168
|
-
fields.stream === "stdout" || fields.stream === "stderr" ? fields.stream : "combined";
|
|
207
|
+
if (!summary) return undefined;
|
|
169
208
|
return {
|
|
170
209
|
signal: "command.output_observed",
|
|
171
210
|
observation: {
|
|
172
211
|
native_command_id: commandId,
|
|
173
|
-
native_observation_id: `${commandId}:output:${
|
|
174
|
-
stream,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
212
|
+
native_observation_id: `${commandId}:output-summary:${summary.stream}`,
|
|
213
|
+
stream: summary.stream,
|
|
214
|
+
// The normalizer HMACs this bounded descriptor with the generation key.
|
|
215
|
+
// Neither raw output nor its unkeyed digest is written to the ledger.
|
|
216
|
+
output: {
|
|
217
|
+
format: "harnery-output-chunks-v1",
|
|
218
|
+
chunks: summary.chunks,
|
|
219
|
+
sha256: summary.hash.digest("hex"),
|
|
220
|
+
},
|
|
221
|
+
output_bytes: summary.bytes,
|
|
222
|
+
output_lines: summary.lines,
|
|
178
223
|
},
|
|
179
224
|
};
|
|
180
225
|
}
|
|
181
|
-
outputSequence.delete(commandId);
|
|
182
226
|
const exitCode =
|
|
183
227
|
typeof fields.exit === "number" && Number.isSafeInteger(fields.exit) ? fields.exit : undefined;
|
|
184
228
|
return {
|
|
@@ -197,14 +241,64 @@ function commandSignalAndObservation(
|
|
|
197
241
|
}
|
|
198
242
|
|
|
199
243
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
244
|
+
* Start/completion use the durable V3 recorder. Output only updates bounded
|
|
245
|
+
* per-stream counters and hashes; summaries flush before completion or at
|
|
246
|
+
* process exit. A forced kill can lose unflushed summaries (ADR 0188).
|
|
247
|
+
* Best-effort: telemetry never throws into the caller.
|
|
202
248
|
*/
|
|
203
249
|
export function writeSessionEvent(
|
|
204
250
|
type: SessionEventType,
|
|
205
251
|
fields: Record<string, unknown> = {},
|
|
206
252
|
): void {
|
|
207
|
-
|
|
253
|
+
try {
|
|
254
|
+
const key = commandKey(fields);
|
|
255
|
+
if (!key) return;
|
|
256
|
+
if (type === "command.output_observed") {
|
|
257
|
+
const command = pendingCommands.get(key);
|
|
258
|
+
if (!command) return;
|
|
259
|
+
const stream =
|
|
260
|
+
fields.stream === "stdout" || fields.stream === "stderr" ? fields.stream : "combined";
|
|
261
|
+
let summary = command.streams.get(stream);
|
|
262
|
+
if (!summary) {
|
|
263
|
+
summary = { stream, bytes: 0, lines: 0, chunks: 0, hash: createHash("sha256") };
|
|
264
|
+
command.streams.set(stream, summary);
|
|
265
|
+
}
|
|
266
|
+
const line = typeof fields.line === "string" ? fields.line : "";
|
|
267
|
+
const normalized = line.normalize("NFC");
|
|
268
|
+
// Length framing preserves chunk boundaries, including empty chunks.
|
|
269
|
+
summary.hash.update(`${Buffer.byteLength(normalized, "utf8")}:`).update(normalized);
|
|
270
|
+
summary.bytes += Buffer.byteLength(line, "utf8");
|
|
271
|
+
summary.lines += countOutputLines(line);
|
|
272
|
+
summary.chunks += 1;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const command = pendingCommands.get(key);
|
|
276
|
+
const coordRoot = command?.coordRoot ?? coordinationRootPath();
|
|
277
|
+
if (type === "command.completed") {
|
|
278
|
+
pendingCommands.delete(key);
|
|
279
|
+
if (command) flushCommandOutput(command);
|
|
280
|
+
recordCommandObservation(type, fields, coordRoot);
|
|
281
|
+
} else if (recordCommandObservation(type, fields, coordRoot) && !command) {
|
|
282
|
+
pendingCommands.set(key, {
|
|
283
|
+
coordRoot,
|
|
284
|
+
fields: {
|
|
285
|
+
instance_id: fields.instance_id,
|
|
286
|
+
cmd_id: fields.cmd_id,
|
|
287
|
+
bridge: fields.bridge,
|
|
288
|
+
},
|
|
289
|
+
streams: new Map(),
|
|
290
|
+
});
|
|
291
|
+
if (!exitFlushInstalled) {
|
|
292
|
+
exitFlushInstalled = true;
|
|
293
|
+
process.once("exit", () => {
|
|
294
|
+
for (const pending of pendingCommands.values()) flushCommandOutput(pending);
|
|
295
|
+
pendingCommands.clear();
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} catch {
|
|
300
|
+
// Root resolution and in-memory bookkeeping must not break the command.
|
|
301
|
+
}
|
|
208
302
|
}
|
|
209
303
|
|
|
210
304
|
/** Trim long values to keep individual events small. */
|
|
@@ -2,6 +2,7 @@ import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import type { ApprovedSessionEndReasonV3 } from "../events/v3/producers/recorder.ts";
|
|
4
4
|
import { EVENT_V3_LEDGER_RELATIVE_ROOT } from "../events/v3/reader.ts";
|
|
5
|
+
import { stateModeTooOpen } from "../storage/modes.ts";
|
|
5
6
|
|
|
6
7
|
export const SESSION_FINALIZATION_REQUEST_FORMAT_V3 =
|
|
7
8
|
"harnery-v3-session-finalization-request" as const;
|
|
@@ -72,7 +73,7 @@ export function listSessionFinalizationRequestsV3(
|
|
|
72
73
|
|
|
73
74
|
export function readSessionFinalizationRequestV3(filePath: string): SessionFinalizationRequestV3 {
|
|
74
75
|
const metadata = lstatSync(filePath);
|
|
75
|
-
if (!metadata.isFile() || metadata.isSymbolicLink() || (metadata.mode
|
|
76
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || stateModeTooOpen(metadata.mode)) {
|
|
76
77
|
throw new Error("V3 session finalization request is unsafe");
|
|
77
78
|
}
|
|
78
79
|
const request = JSON.parse(readFileSync(filePath, "utf8")) as SessionFinalizationRequestV3;
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
import { readLedgerV3 } from "../events/v3/reader.ts";
|
|
33
33
|
import { assertEventV3 } from "../events/v3/validate.ts";
|
|
34
34
|
import { EVENT_V3_LEDGER_RELATIVE_ROOT, writeEventV3 } from "../events/v3/writer.ts";
|
|
35
|
+
import { stateDirMode, stateFileMode } from "../storage/modes.ts";
|
|
35
36
|
import { fsyncParentDirectory } from "../workflow/durable-record.ts";
|
|
36
37
|
import { acquireNoClobberLease } from "../workflow/workspaces/leases.ts";
|
|
37
38
|
import { readCodexArchiveObservationsV3 } from "./codex-archive-v3.ts";
|
|
@@ -901,20 +902,20 @@ function writeRequest(
|
|
|
901
902
|
create = false,
|
|
902
903
|
): void {
|
|
903
904
|
const directory = sessionFinalizationRequestDirectoryV3(coordRoot);
|
|
904
|
-
mkdirSync(directory, { recursive: true, mode:
|
|
905
|
-
chmodSync(directory,
|
|
905
|
+
mkdirSync(directory, { recursive: true, mode: stateDirMode() });
|
|
906
|
+
chmodSync(directory, stateDirMode());
|
|
906
907
|
const path = sessionFinalizationRequestPathV3(coordRoot, request.request_id);
|
|
907
908
|
if (create && existsSync(path)) throw new Error("V3 finalization request already exists");
|
|
908
909
|
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
909
910
|
let fd: number | undefined;
|
|
910
911
|
try {
|
|
911
|
-
fd = openSync(temporary, "wx",
|
|
912
|
+
fd = openSync(temporary, "wx", stateFileMode());
|
|
912
913
|
writeFileSync(fd, `${JSON.stringify(request)}\n`, "utf8");
|
|
913
914
|
fsyncSync(fd);
|
|
914
915
|
closeSync(fd);
|
|
915
916
|
fd = undefined;
|
|
916
917
|
renameSync(temporary, path);
|
|
917
|
-
chmodSync(path,
|
|
918
|
+
chmodSync(path, stateFileMode());
|
|
918
919
|
fsyncParentDirectory(path);
|
|
919
920
|
} finally {
|
|
920
921
|
if (fd !== undefined) closeSync(fd);
|
|
@@ -9,8 +9,17 @@ export interface SessionNameDisplayState {
|
|
|
9
9
|
suggested_session_name?: string;
|
|
10
10
|
session_name_seen_for?: string;
|
|
11
11
|
session_name_display_requested_for?: string;
|
|
12
|
+
session_name_display_reminders?: number;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* How many times PostToolUse may re-attach the display instruction after the
|
|
17
|
+
* mint-time one, per requested title. Two covers the observed miss (the model
|
|
18
|
+
* narrates, calls a tool, and forgets) without turning an unreadable
|
|
19
|
+
* transcript into a reminder on every tool for the rest of the session.
|
|
20
|
+
*/
|
|
21
|
+
export const SESSION_NAME_DISPLAY_REMINDER_LIMIT = 2;
|
|
22
|
+
|
|
14
23
|
export const SESSION_NAME_DISPLAY_NOTE =
|
|
15
24
|
"Next, before any prose or another tool call, send suggested_session_name verbatim by itself in a fenced code block.";
|
|
16
25
|
|
|
@@ -76,6 +85,45 @@ export function sessionNameDisplayInstruction(name: string): string {
|
|
|
76
85
|
].join("\n");
|
|
77
86
|
}
|
|
78
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Whether an ordinary tool result should carry a bounded reminder.
|
|
90
|
+
*
|
|
91
|
+
* Only after the mint-time instruction has gone out (`requested_for` is set
|
|
92
|
+
* for the pending title), and only while the counter is under the cap. The
|
|
93
|
+
* transcript is deliberately not consulted: PreToolUse already stamped a
|
|
94
|
+
* readable sighting, and an unreadable one is exactly the case the reminder
|
|
95
|
+
* exists for.
|
|
96
|
+
*/
|
|
97
|
+
export function sessionNameDisplayReminderDue(
|
|
98
|
+
row: SessionNameDisplayState | null | undefined,
|
|
99
|
+
): string | null {
|
|
100
|
+
const pending = sessionNameDisplayPending(row);
|
|
101
|
+
if (!pending || row?.session_name_display_requested_for !== pending) return null;
|
|
102
|
+
return (row.session_name_display_reminders ?? 0) < SESSION_NAME_DISPLAY_REMINDER_LIMIT
|
|
103
|
+
? pending
|
|
104
|
+
: null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The follow-up nudge for a title still pending after an ordinary tool.
|
|
109
|
+
*
|
|
110
|
+
* Worded so the model can answer it from its own memory: the desktop transcript
|
|
111
|
+
* sometimes drops a text row that the operator did see, so Harnery cannot tell
|
|
112
|
+
* a skipped block from an unrecorded one. A model that already showed the block
|
|
113
|
+
* is told to do nothing, which is what keeps the reminder from producing
|
|
114
|
+
* duplicate blocks in the chat.
|
|
115
|
+
*/
|
|
116
|
+
export function sessionNameDisplayReminder(name: string): string {
|
|
117
|
+
return [
|
|
118
|
+
"Session name still pending.",
|
|
119
|
+
"If you have not already shown it this turn, send this exact fenced block as your next assistant text, before any prose or another tool call:",
|
|
120
|
+
"",
|
|
121
|
+
sessionNameDisplayBlock(name),
|
|
122
|
+
"",
|
|
123
|
+
"If you already showed that block, do nothing; Harnery could not read the transcript row and is asking once more.",
|
|
124
|
+
].join("\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
79
127
|
export function sessionNameDisplayRecoveryInstruction(name: string, binName: string): string {
|
|
80
128
|
return [
|
|
81
129
|
sessionNameDisplayInstruction(name),
|
|
@@ -40,6 +40,13 @@ export interface Heartbeat {
|
|
|
40
40
|
* counts so that drift cannot strand the latch.
|
|
41
41
|
*/
|
|
42
42
|
session_name_display_requested_for?: string;
|
|
43
|
+
/**
|
|
44
|
+
* How many bounded PostToolUse reminders have gone out for
|
|
45
|
+
* `session_name_display_requested_for`. Reset whenever a different title is
|
|
46
|
+
* requested. Caps the nudge so an unreadable transcript never turns into an
|
|
47
|
+
* endless stream of "show the block" context.
|
|
48
|
+
*/
|
|
49
|
+
session_name_display_reminders?: number;
|
|
43
50
|
last_status_at?: string;
|
|
44
51
|
current_turn_id?: string;
|
|
45
52
|
parent_instance_id?: string;
|
|
@@ -194,12 +194,34 @@ export function stampSessionNameRequested(
|
|
|
194
194
|
coordRoot: string,
|
|
195
195
|
instanceId: string,
|
|
196
196
|
name: string,
|
|
197
|
+
): Heartbeat | null {
|
|
198
|
+
if (!name) return null;
|
|
199
|
+
return mutate(coordRoot, instanceId, (hb) => {
|
|
200
|
+
if (hb.session_name_display_requested_for === name) return hb;
|
|
201
|
+
const { session_name_display_reminders: _reset, ...rest } = hb;
|
|
202
|
+
return { ...rest, session_name_display_requested_for: name };
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Count one bounded PostToolUse reminder for the title currently requested.
|
|
208
|
+
*
|
|
209
|
+
* The mint-time instruction is the only thing that used to make the block
|
|
210
|
+
* appear; a model that went straight to another tool was never asked again,
|
|
211
|
+
* because the fail-open gate writes its reason to stderr, which the model
|
|
212
|
+
* cannot read. The counter lets the hook re-attach the instruction a fixed
|
|
213
|
+
* number of times and then stop, whatever the transcript can or cannot prove.
|
|
214
|
+
*/
|
|
215
|
+
export function stampSessionNameReminded(
|
|
216
|
+
coordRoot: string,
|
|
217
|
+
instanceId: string,
|
|
218
|
+
name: string,
|
|
197
219
|
): Heartbeat | null {
|
|
198
220
|
if (!name) return null;
|
|
199
221
|
return mutate(coordRoot, instanceId, (hb) =>
|
|
200
222
|
hb.session_name_display_requested_for === name
|
|
201
|
-
? hb
|
|
202
|
-
:
|
|
223
|
+
? { ...hb, session_name_display_reminders: (hb.session_name_display_reminders ?? 0) + 1 }
|
|
224
|
+
: hb,
|
|
203
225
|
);
|
|
204
226
|
}
|
|
205
227
|
|
|
@@ -198,6 +198,7 @@ export function projectHeartbeatV3(
|
|
|
198
198
|
session_name_seen_at: cache?.session_name_seen_at,
|
|
199
199
|
session_name_seen_for: cache?.session_name_seen_for,
|
|
200
200
|
session_name_display_requested_for: cache?.session_name_display_requested_for,
|
|
201
|
+
session_name_display_reminders: cache?.session_name_display_reminders,
|
|
201
202
|
workflow_run_id: generation.run_id,
|
|
202
203
|
workflow_agent_id: generation.workflow_agent_id,
|
|
203
204
|
parent_instance_id: parent ? nativeInstanceIdV3(parent.instance_id) : undefined,
|
|
@@ -112,6 +112,9 @@ function materializeLiveCoordinationHeartbeat(
|
|
|
112
112
|
session_name_display_requested_for: current.session_name_display_requested_for,
|
|
113
113
|
}
|
|
114
114
|
: {}),
|
|
115
|
+
...(typeof current.session_name_display_reminders === "number"
|
|
116
|
+
? { session_name_display_reminders: current.session_name_display_reminders }
|
|
117
|
+
: {}),
|
|
115
118
|
}
|
|
116
119
|
: {};
|
|
117
120
|
const materialized: V3HeartbeatMaterialization = {
|
|
@@ -13,18 +13,11 @@
|
|
|
13
13
|
import { existsSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { coordFreshnessSeconds } from "../../config.ts";
|
|
16
|
-
import type
|
|
16
|
+
import { type EventAdapterIdV3, eventAdapterIdV3FromPlatform } from "../../events/v3/adapter-id.ts";
|
|
17
17
|
import { writeProducerDiagnosticV3 } from "../../events/v3/producers/intake.ts";
|
|
18
18
|
import { recordLiveSweepObservationV3 } from "../live-lifecycle-v3.ts";
|
|
19
19
|
|
|
20
20
|
/** platform → adapter, for the swept-event envelope (mirrors heartbeat-writer's adapterOf). */
|
|
21
|
-
function adapterFromPlatform(platform: unknown): EventAdapterIdV3 {
|
|
22
|
-
if (platform === "cursor") return "cursor";
|
|
23
|
-
if (platform === "codex") return "codex";
|
|
24
|
-
if (platform === "openclaw") return "openclaw";
|
|
25
|
-
return "claude-code";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
/**
|
|
29
22
|
* Record the sweep before deleting disposable cache state.
|
|
30
23
|
*
|
|
@@ -150,7 +143,7 @@ export function staleSweep(coordRoot: string): {
|
|
|
150
143
|
}
|
|
151
144
|
|
|
152
145
|
const instanceId = parsed.instance_id ?? idFromFile;
|
|
153
|
-
const adapter =
|
|
146
|
+
const adapter = eventAdapterIdV3FromPlatform(parsed.platform, { context: "stale-sweep" });
|
|
154
147
|
const sessionId = parsed.session_id ?? instanceId;
|
|
155
148
|
const ts = parsed.last_heartbeat
|
|
156
149
|
? Math.floor(Date.parse(parsed.last_heartbeat) / 1000)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { EventV3 } from "../events/v3/contract.ts";
|
|
2
|
+
import { liveInstanceIdV3 } from "../events/v3/live-routing.ts";
|
|
3
|
+
import { readLedgerV3 } from "../events/v3/reader.ts";
|
|
4
|
+
import type { LineChanges } from "../events/v3/tool-line-changes.ts";
|
|
5
|
+
|
|
6
|
+
export interface TurnChanges extends LineChanges {
|
|
7
|
+
measured_tools: number;
|
|
8
|
+
unmeasured_tools: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Sum edit activity, not a net workspace diff. Commits cannot erase it. */
|
|
12
|
+
export function resolveTurnChanges(
|
|
13
|
+
events: readonly EventV3[],
|
|
14
|
+
instanceId: string,
|
|
15
|
+
now = Date.now(),
|
|
16
|
+
): TurnChanges | null {
|
|
17
|
+
const owner = liveInstanceIdV3(instanceId);
|
|
18
|
+
const own = events.filter(
|
|
19
|
+
(event) => event.scope.instance_id === owner && Date.parse(event.time.observed_at) <= now,
|
|
20
|
+
);
|
|
21
|
+
const starts = own.filter((event) => event.event_type === "turn.started");
|
|
22
|
+
const newest = starts.at(-1);
|
|
23
|
+
if (!newest || !("generation_id" in newest.scope)) return null;
|
|
24
|
+
const generation = newest.scope.generation_id;
|
|
25
|
+
let index = starts.length - 1;
|
|
26
|
+
let restarts = 0;
|
|
27
|
+
while (
|
|
28
|
+
index > 0 &&
|
|
29
|
+
restarts < 8 &&
|
|
30
|
+
starts[index]?.event_type === "turn.started" &&
|
|
31
|
+
starts[index]?.payload.stop_remediation === true
|
|
32
|
+
) {
|
|
33
|
+
const previous = starts[index - 1]!;
|
|
34
|
+
if (!("generation_id" in previous.scope) || previous.scope.generation_id !== generation) break;
|
|
35
|
+
index--;
|
|
36
|
+
restarts++;
|
|
37
|
+
}
|
|
38
|
+
const selected = starts.slice(index);
|
|
39
|
+
const ids = new Set(
|
|
40
|
+
selected.map((event) => ("turn_id" in event.scope ? event.scope.turn_id : undefined)),
|
|
41
|
+
);
|
|
42
|
+
const from = Date.parse(selected[0]!.time.observed_at);
|
|
43
|
+
const tools = own.filter(
|
|
44
|
+
(event) =>
|
|
45
|
+
"generation_id" in event.scope &&
|
|
46
|
+
event.scope.generation_id === generation &&
|
|
47
|
+
"turn_id" in event.scope &&
|
|
48
|
+
ids.has(event.scope.turn_id) &&
|
|
49
|
+
Date.parse(event.time.observed_at) >= from,
|
|
50
|
+
);
|
|
51
|
+
const result: TurnChanges = { added: 0, removed: 0, measured_tools: 0, unmeasured_tools: 0 };
|
|
52
|
+
const completed = new Set<string>();
|
|
53
|
+
for (const event of tools) {
|
|
54
|
+
if (event.event_type !== "tool.completed") continue;
|
|
55
|
+
const span = event.payload.span.span_id;
|
|
56
|
+
if (completed.has(span)) continue;
|
|
57
|
+
completed.add(span);
|
|
58
|
+
const changes = event.payload.line_changes;
|
|
59
|
+
if (event.payload.outcome === "succeeded" && changes?.state === "observed") {
|
|
60
|
+
result.added += changes.value.added;
|
|
61
|
+
result.removed += changes.value.removed;
|
|
62
|
+
result.measured_tools++;
|
|
63
|
+
} else result.unmeasured_tools++;
|
|
64
|
+
}
|
|
65
|
+
for (const event of tools) {
|
|
66
|
+
const span = (event.links as { span_id?: string }).span_id;
|
|
67
|
+
if (event.event_type === "tool.requested" && span && !completed.has(span)) {
|
|
68
|
+
completed.add(span);
|
|
69
|
+
result.unmeasured_tools++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// No delivered tool evidence cannot prove no file changes occurred.
|
|
73
|
+
return result.measured_tools + result.unmeasured_tools > 0 ? result : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function formatTurnChanges(changes: TurnChanges | null): string {
|
|
77
|
+
if (!changes || changes.measured_tools === 0) return "unavailable (no measured edits)";
|
|
78
|
+
return `+${changes.added} / -${changes.removed} lines${changes.unmeasured_tools > 0 ? " (partial; unmeasured tools)" : " (recorded edits)"}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function turnChangesStatusRow(target: { coordRoot: string; instanceId: string }): {
|
|
82
|
+
value: string;
|
|
83
|
+
changes: TurnChanges | null;
|
|
84
|
+
} {
|
|
85
|
+
let changes: TurnChanges | null = null;
|
|
86
|
+
try {
|
|
87
|
+
const ledger = readLedgerV3(target.coordRoot);
|
|
88
|
+
if (ledger.complete)
|
|
89
|
+
changes = resolveTurnChanges(
|
|
90
|
+
ledger.events.map((item) => item.event),
|
|
91
|
+
target.instanceId,
|
|
92
|
+
);
|
|
93
|
+
} catch {
|
|
94
|
+
/* Status remains available when evidence is missing. */
|
|
95
|
+
}
|
|
96
|
+
return { value: formatTurnChanges(changes), changes };
|
|
97
|
+
}
|
|
@@ -14,6 +14,7 @@ import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
|
14
14
|
import { encodeLinkSafeComponent } from "../../lib/local-file-url.ts";
|
|
15
15
|
import { findLiveTunnelForOrigin } from "../../lib/tunnel/state.ts";
|
|
16
16
|
import { resolveWebPort } from "../config.ts";
|
|
17
|
+
import { stateFileMode } from "../storage/modes.ts";
|
|
17
18
|
import { ARTIFACT_MANIFEST } from "./constants.ts";
|
|
18
19
|
import { resolveArtifactRef, showArtifact } from "./index.ts";
|
|
19
20
|
|
|
@@ -71,7 +72,7 @@ export function writeArtifactDeliveryManifest(
|
|
|
71
72
|
try {
|
|
72
73
|
writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`, {
|
|
73
74
|
encoding: "utf8",
|
|
74
|
-
mode:
|
|
75
|
+
mode: stateFileMode(),
|
|
75
76
|
flag: "wx",
|
|
76
77
|
});
|
|
77
78
|
renameSync(tmp, target);
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
coordFreshnessSeconds,
|
|
34
34
|
resolveBinName,
|
|
35
35
|
} from "../config.ts";
|
|
36
|
+
import { stateFileMode } from "../storage/modes.ts";
|
|
36
37
|
import {
|
|
37
38
|
type ArtifactActivity,
|
|
38
39
|
artifactRootEntries,
|
|
@@ -1068,7 +1069,7 @@ function atomicWriteManifest(path: string, manifest: ArtifactManifestV2, now = n
|
|
|
1068
1069
|
try {
|
|
1069
1070
|
writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`, {
|
|
1070
1071
|
encoding: "utf8",
|
|
1071
|
-
mode:
|
|
1072
|
+
mode: stateFileMode(),
|
|
1072
1073
|
flag: "wx",
|
|
1073
1074
|
});
|
|
1074
1075
|
renameSync(tmp, target);
|
|
@@ -1143,7 +1144,7 @@ export function migrateArtifacts(
|
|
|
1143
1144
|
};
|
|
1144
1145
|
mkdirSync(dirname(preimagePath), { recursive: true });
|
|
1145
1146
|
try {
|
|
1146
|
-
writeFileSync(preimagePath, preimage, { flag: "wx", mode:
|
|
1147
|
+
writeFileSync(preimagePath, preimage, { flag: "wx", mode: stateFileMode() });
|
|
1147
1148
|
} catch (error) {
|
|
1148
1149
|
if (
|
|
1149
1150
|
(error as NodeJS.ErrnoException).code !== "EEXIST" ||
|
|
@@ -1287,7 +1288,7 @@ export function repairArtifactActivity(
|
|
|
1287
1288
|
null,
|
|
1288
1289
|
2,
|
|
1289
1290
|
)}\n`,
|
|
1290
|
-
{ flag: "wx", mode:
|
|
1291
|
+
{ flag: "wx", mode: stateFileMode() },
|
|
1291
1292
|
);
|
|
1292
1293
|
const current = lstatSync(path);
|
|
1293
1294
|
if (
|
package/src/core/config.ts
CHANGED
|
@@ -152,6 +152,16 @@ interface HarneryConfig {
|
|
|
152
152
|
archive_keep_min?: number;
|
|
153
153
|
archive_auto_clean?: boolean;
|
|
154
154
|
};
|
|
155
|
+
/**
|
|
156
|
+
* Who can use this project's `.harnery/` state. `private` (the default) keeps every state
|
|
157
|
+
* directory 0700 and every state file 0600, owned by whoever wrote it. `group` makes them 0770 and
|
|
158
|
+
* 0660 so several Unix users who share the project directory's group can coordinate in one
|
|
159
|
+
* project. It never grants access to other users. The project directory must be setgid to that
|
|
160
|
+
* group so new state inherits it. Read via `storageSharing()`.
|
|
161
|
+
*/
|
|
162
|
+
storage?: {
|
|
163
|
+
sharing?: "private" | "group";
|
|
164
|
+
};
|
|
155
165
|
/**
|
|
156
166
|
* Managed working-artifact defaults. `default_retention_days` is the
|
|
157
167
|
* create-time TTL when the caller does not pass `artifacts create --days`.
|
|
@@ -591,6 +601,19 @@ export function workflowSubscriptionOnly(coordRoot?: string | null): boolean {
|
|
|
591
601
|
return readConfig(root).workflow?.subscriptionOnly === true;
|
|
592
602
|
}
|
|
593
603
|
|
|
604
|
+
/**
|
|
605
|
+
* How this project's `.harnery/` state is shared: `private` or `group`. `HARNERY_STORAGE_SHARING`
|
|
606
|
+
* overrides per process; an unrecognized value falls back to `private`, the safer reading.
|
|
607
|
+
*/
|
|
608
|
+
export function storageSharing(coordRoot?: string | null): "private" | "group" {
|
|
609
|
+
const env = coordEnv("STORAGE_SHARING");
|
|
610
|
+
if (env === "group" || env === "private") return env;
|
|
611
|
+
const root = coordRoot ?? findCoordRoot();
|
|
612
|
+
if (!root) return "private";
|
|
613
|
+
// Project file only: a user-global "group" would loosen every project that user touches.
|
|
614
|
+
return readProjectConfig(root).storage?.sharing === "group" ? "group" : "private";
|
|
615
|
+
}
|
|
616
|
+
|
|
594
617
|
/**
|
|
595
618
|
* Whether cross-machine presence (ADR 0016) is enabled for this repo.
|
|
596
619
|
* Default ON — the transport itself additionally gates on an origin remote
|
|
@@ -11,6 +11,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
12
|
import type { Adapter } from "../adapter.ts";
|
|
13
13
|
import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.ts";
|
|
14
|
+
import { stateFileMode } from "../storage/modes.ts";
|
|
14
15
|
|
|
15
16
|
export const CONTEXT_SCHEMA_VERSION = 1 as const;
|
|
16
17
|
export const MAX_CAPSULE_BYTES = 32 * 1024;
|
|
@@ -499,7 +500,7 @@ function writeBoundedJson(path: string, value: unknown, maxBytes: number): void
|
|
|
499
500
|
}
|
|
500
501
|
mkdirSync(dirname(path), { recursive: true });
|
|
501
502
|
const temp = `${path}.tmp.${process.pid}.${randomUUID()}`;
|
|
502
|
-
writeFileSync(temp, text, { encoding: "utf8", mode:
|
|
503
|
+
writeFileSync(temp, text, { encoding: "utf8", mode: stateFileMode() });
|
|
503
504
|
renameSync(temp, path);
|
|
504
505
|
}
|
|
505
506
|
|