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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The permission modes for everything Harnery writes as project state under `.harnery/`.
|
|
3
|
+
*
|
|
4
|
+
* `private` (the default) is owner-only: 0700 directories and 0600 files, exactly as before this
|
|
5
|
+
* module existed. `group` is for a project several Unix users share through one group, such as a
|
|
6
|
+
* workspace that several people's agents work in at once: 2770 (setgid) and 0660, with nothing ever granted
|
|
7
|
+
* to other users. Every state write takes its mode from here, and every integrity check that
|
|
8
|
+
* rejects a loosely permissioned file asks `stateModeTooOpen`, so the two cannot disagree.
|
|
9
|
+
*
|
|
10
|
+
* Personal state outside the project (browser cookies, sessions, pacing ledgers in the user's home)
|
|
11
|
+
* is not project state and stays owner-only regardless.
|
|
12
|
+
*/
|
|
13
|
+
import { storageSharing } from "../config.ts";
|
|
14
|
+
|
|
15
|
+
let resolved: "private" | "group" | undefined;
|
|
16
|
+
|
|
17
|
+
/** The sharing mode for this process, resolved once. Tests reset it with `resetStorageSharing`. */
|
|
18
|
+
export function stateSharing(): "private" | "group" {
|
|
19
|
+
if (resolved === undefined) {
|
|
20
|
+
resolved = storageSharing();
|
|
21
|
+
// A umask that strips group bits would quietly turn every group file back into an owner-only
|
|
22
|
+
// one, and the next user's write would fail. Keep the process's other-bits mask as it was.
|
|
23
|
+
if (resolved === "group" && typeof process.umask === "function")
|
|
24
|
+
process.umask(process.umask() & ~0o070);
|
|
25
|
+
}
|
|
26
|
+
return resolved;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resetStorageSharing(): void {
|
|
30
|
+
resolved = undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Mode for a directory of project state. Group mode includes setgid: Harnery chmods directories
|
|
35
|
+
* after creating them, and a plain 0770 would clear the bit, so files made inside would take the
|
|
36
|
+
* writer's own group instead of the project's, and other users could not read them.
|
|
37
|
+
*/
|
|
38
|
+
export function stateDirMode(): number {
|
|
39
|
+
return stateSharing() === "group" ? 0o2770 : 0o700;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Mode for a file of project state. */
|
|
43
|
+
export function stateFileMode(): number {
|
|
44
|
+
return stateSharing() === "group" ? 0o660 : 0o600;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* True when a state file or directory is readable or writable by someone it should not be: anyone
|
|
49
|
+
* but the owner in private mode, anyone outside the owner and group in group mode.
|
|
50
|
+
*/
|
|
51
|
+
export function stateModeTooOpen(mode: number): boolean {
|
|
52
|
+
return (mode & (stateSharing() === "group" ? 0o007 : 0o077)) !== 0;
|
|
53
|
+
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { basename, dirname, join, resolve } from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
import { createStorageCatalog } from "./catalog.ts";
|
|
15
|
+
import { stateDirMode, stateFileMode } from "./modes.ts";
|
|
15
16
|
import { familyLogDirectory } from "./segments.ts";
|
|
16
17
|
|
|
17
18
|
export interface RotatingTextSinkOptions {
|
|
@@ -79,7 +80,7 @@ export class RotatingTextSink {
|
|
|
79
80
|
append(text: string): void {
|
|
80
81
|
if (this.#closed) throw new Error("text sink is closed");
|
|
81
82
|
const bytes = Buffer.from(text, "utf8");
|
|
82
|
-
mkdirSync(dirname(this.#options.path), { recursive: true, mode:
|
|
83
|
+
mkdirSync(dirname(this.#options.path), { recursive: true, mode: stateDirMode() });
|
|
83
84
|
let offset = 0;
|
|
84
85
|
while (offset < bytes.byteLength) {
|
|
85
86
|
let current = existsSync(this.#options.path) ? statSync(this.#options.path).size : 0;
|
|
@@ -88,7 +89,7 @@ export class RotatingTextSink {
|
|
|
88
89
|
current = 0;
|
|
89
90
|
}
|
|
90
91
|
const length = Math.min(bytes.byteLength - offset, this.#options.max_bytes - current);
|
|
91
|
-
const fd = openSync(this.#options.path, "a",
|
|
92
|
+
const fd = openSync(this.#options.path, "a", stateFileMode());
|
|
92
93
|
try {
|
|
93
94
|
writeSync(fd, bytes.subarray(offset, offset + length));
|
|
94
95
|
if (this.#options.durable) fdatasyncSync(fd);
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
HarneryMaintenanceError,
|
|
19
19
|
type HarneryMaintenanceReceipt,
|
|
20
20
|
} from "./maintenance.ts";
|
|
21
|
+
import { stateDirMode, stateFileMode } from "./modes.ts";
|
|
21
22
|
|
|
22
23
|
export const HARNERY_RECEIPT_SEGMENT_MANIFEST_SCHEMA =
|
|
23
24
|
"harnery.storage-maintenance-receipt-segment/v1" as const;
|
|
@@ -85,7 +86,7 @@ export function consolidateMutationReceipts(
|
|
|
85
86
|
);
|
|
86
87
|
}
|
|
87
88
|
const segments = join(root, "segments");
|
|
88
|
-
mkdirSync(segments, { recursive: true, mode:
|
|
89
|
+
mkdirSync(segments, { recursive: true, mode: stateDirMode() });
|
|
89
90
|
const payloadPath = join(segments, `${segmentId}.jsonl`);
|
|
90
91
|
const manifestPath = join(segments, `${segmentId}.manifest.json`);
|
|
91
92
|
writeExclusiveDurable(payloadPath, payload);
|
|
@@ -144,9 +145,9 @@ function readReceipt(path: string): {
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
function writeExclusiveDurable(path: string, bytes: string): void {
|
|
147
|
-
mkdirSync(dirname(path), { recursive: true, mode:
|
|
148
|
+
mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
|
|
148
149
|
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
149
|
-
const fd = openSync(temporary, "wx",
|
|
150
|
+
const fd = openSync(temporary, "wx", stateFileMode());
|
|
150
151
|
try {
|
|
151
152
|
writeFileSync(fd, bytes, "utf8");
|
|
152
153
|
fsyncSync(fd);
|
|
@@ -23,6 +23,7 @@ import type { HarneryRegisteredStorageFamily } from "./contract.ts";
|
|
|
23
23
|
import { parseLogRecord } from "./jsonl.ts";
|
|
24
24
|
import type { HarneryLogMetricsDelta } from "./metrics.ts";
|
|
25
25
|
import { mergeMetricsSidecar } from "./metrics.ts";
|
|
26
|
+
import { stateDirMode, stateFileMode } from "./modes.ts";
|
|
26
27
|
|
|
27
28
|
export interface HarneryLogSegmentV1 {
|
|
28
29
|
sequence: number;
|
|
@@ -165,7 +166,7 @@ export class FileSegmentSink {
|
|
|
165
166
|
const fd = openSync(
|
|
166
167
|
active,
|
|
167
168
|
fsConstants.O_RDWR | fsConstants.O_APPEND | fsConstants.O_CREAT | noFollowFlag(),
|
|
168
|
-
|
|
169
|
+
stateFileMode(),
|
|
169
170
|
);
|
|
170
171
|
let bytes = 0;
|
|
171
172
|
try {
|
|
@@ -337,7 +338,7 @@ export async function withFamilyLease<T>(
|
|
|
337
338
|
let owner: LeaseOwner | undefined;
|
|
338
339
|
while (true) {
|
|
339
340
|
try {
|
|
340
|
-
mkdirSync(lease, { mode:
|
|
341
|
+
mkdirSync(lease, { mode: stateDirMode() });
|
|
341
342
|
const leaseIdentity = captureDirectoryIdentity(lease);
|
|
342
343
|
owner = {
|
|
343
344
|
owner_id: randomUUID(),
|
|
@@ -397,7 +398,7 @@ function sealActive(active: string, options: ResolvedSegmentOptions, now: Date):
|
|
|
397
398
|
const sequence = manifest.next_sequence;
|
|
398
399
|
const name = `${now.toISOString().slice(0, 10).replaceAll("-", "")}-${String(sequence).padStart(8, "0")}.jsonl.gz`;
|
|
399
400
|
const target = join(directory, "segments", name);
|
|
400
|
-
mkdirSync(dirname(target), { recursive: true, mode:
|
|
401
|
+
mkdirSync(dirname(target), { recursive: true, mode: stateDirMode() });
|
|
401
402
|
const temporary = `${target}.tmp-${process.pid}-${randomUUID()}`;
|
|
402
403
|
const compressed = gzipSync(content);
|
|
403
404
|
writeNewFile(temporary, compressed);
|
|
@@ -425,7 +426,7 @@ function sealActive(active: string, options: ResolvedSegmentOptions, now: Date):
|
|
|
425
426
|
],
|
|
426
427
|
};
|
|
427
428
|
const sealedManifest = join(directory, "manifests", `${String(sequence).padStart(8, "0")}.json`);
|
|
428
|
-
mkdirSync(dirname(sealedManifest), { recursive: true, mode:
|
|
429
|
+
mkdirSync(dirname(sealedManifest), { recursive: true, mode: stateDirMode() });
|
|
429
430
|
writeNewFile(sealedManifest, Buffer.from(`${JSON.stringify(next)}\n`));
|
|
430
431
|
atomicJson(join(directory, "manifest.json"), next);
|
|
431
432
|
truncateRegularFile(active);
|
|
@@ -539,7 +540,7 @@ interface DirectoryIdentity {
|
|
|
539
540
|
}
|
|
540
541
|
|
|
541
542
|
function ensurePrivateDirectory(path: string): DirectoryIdentity {
|
|
542
|
-
mkdirSync(path, { recursive: true, mode:
|
|
543
|
+
mkdirSync(path, { recursive: true, mode: stateDirMode() });
|
|
543
544
|
return captureDirectoryIdentity(path);
|
|
544
545
|
}
|
|
545
546
|
|
|
@@ -632,7 +633,7 @@ function writeNewFile(path: string, content: Buffer): void {
|
|
|
632
633
|
const fd = openSync(
|
|
633
634
|
path,
|
|
634
635
|
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollowFlag(),
|
|
635
|
-
|
|
636
|
+
stateFileMode(),
|
|
636
637
|
);
|
|
637
638
|
try {
|
|
638
639
|
assertOpenFileIdentity(path, fd, parent);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type EventAdapterIdV3, normalizeEventAdapterIdV3 } from "../events/v3/adapter-id.ts";
|
|
1
2
|
import {
|
|
2
3
|
HOOK_HEALTH_EVENT,
|
|
3
4
|
HOOK_HEALTH_RECEIPT_VERSION,
|
|
@@ -18,7 +19,7 @@ export interface CompletedHookHealth {
|
|
|
18
19
|
id: string;
|
|
19
20
|
observed_at: string;
|
|
20
21
|
hook_name: string;
|
|
21
|
-
adapter:
|
|
22
|
+
adapter: EventAdapterIdV3 | "unknown";
|
|
22
23
|
outcome: HookHealthOutcome;
|
|
23
24
|
duration_ms: number;
|
|
24
25
|
rss_start_bytes: number;
|
|
@@ -267,9 +268,8 @@ function token(value: unknown): string | undefined {
|
|
|
267
268
|
}
|
|
268
269
|
|
|
269
270
|
function adapterValue(value: unknown): CompletedHookHealth["adapter"] | undefined {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
: undefined;
|
|
271
|
+
if (value === "unknown") return value;
|
|
272
|
+
return normalizeEventAdapterIdV3(value) ?? undefined;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
function outcomeValue(value: unknown): HookHealthOutcome | undefined {
|
|
@@ -28,6 +28,7 @@ import { readResourceServiceStatus } from "../resources/service-status.ts";
|
|
|
28
28
|
import { resourcePaths } from "../resources/storage.ts";
|
|
29
29
|
import { writePrivateJsonAtomic } from "../storage/atomic-json.ts";
|
|
30
30
|
import { closeProcessLoggers, legacyLogFields, processLogger } from "../storage/logger.ts";
|
|
31
|
+
import { stateDirMode, stateFileMode } from "../storage/modes.ts";
|
|
31
32
|
import { collectSupervisorActivitySnapshot } from "./activity.ts";
|
|
32
33
|
import {
|
|
33
34
|
SUPERVISOR_PRESSURE_SCHEMA_VERSION,
|
|
@@ -103,7 +104,7 @@ export async function spawnSupervisor(
|
|
|
103
104
|
const intervalMs = normalizeInterval(options.intervalMs);
|
|
104
105
|
const idleExitMs = normalizeIdleExit(options.idleExitMs);
|
|
105
106
|
const paths = supervisorPaths(coordRoot);
|
|
106
|
-
mkdirSync(paths.root, { recursive: true, mode:
|
|
107
|
+
mkdirSync(paths.root, { recursive: true, mode: stateDirMode() });
|
|
107
108
|
rmSync(paths.stop, { force: true });
|
|
108
109
|
const cliPath = fileURLToPath(
|
|
109
110
|
new URL(import.meta.url.endsWith(".ts") ? "../../cli.ts" : "../../cli.js", import.meta.url),
|
|
@@ -170,7 +171,7 @@ export function requestSupervisorStop(coordRootRaw: string): SupervisorStatus {
|
|
|
170
171
|
const coordRoot = resolve(coordRootRaw);
|
|
171
172
|
const paths = supervisorPaths(coordRoot);
|
|
172
173
|
const status = readSupervisorStatus(coordRoot);
|
|
173
|
-
mkdirSync(paths.root, { recursive: true, mode:
|
|
174
|
+
mkdirSync(paths.root, { recursive: true, mode: stateDirMode() });
|
|
174
175
|
writePrivateJsonAtomic(paths.stop, {
|
|
175
176
|
requested_at: new Date().toISOString(),
|
|
176
177
|
requested_by_pid: process.pid,
|
|
@@ -352,8 +353,8 @@ export async function runSupervisor(
|
|
|
352
353
|
});
|
|
353
354
|
}
|
|
354
355
|
pressureHysteresis = assessment.hysteresis;
|
|
355
|
-
mkdirSync(paths.timelines, { recursive: true, mode:
|
|
356
|
-
mkdirSync(paths.explanations, { recursive: true, mode:
|
|
356
|
+
mkdirSync(paths.timelines, { recursive: true, mode: stateDirMode() });
|
|
357
|
+
mkdirSync(paths.explanations, { recursive: true, mode: stateDirMode() });
|
|
357
358
|
const projectionUpdates = [
|
|
358
359
|
...findings.active.filter((finding) => !priorActiveFindingIds.has(finding.id)),
|
|
359
360
|
...findings.transitions.filter(
|
|
@@ -495,7 +496,7 @@ function observeLedgerControlState(
|
|
|
495
496
|
|
|
496
497
|
function acquireLease(coordRoot: string, now: Date): SupervisorLease {
|
|
497
498
|
const paths = supervisorPaths(coordRoot);
|
|
498
|
-
mkdirSync(paths.root, { recursive: true, mode:
|
|
499
|
+
mkdirSync(paths.root, { recursive: true, mode: stateDirMode() });
|
|
499
500
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
500
501
|
const startToken = processStartToken(process.pid);
|
|
501
502
|
const lease: SupervisorLease = {
|
|
@@ -506,7 +507,7 @@ function acquireLease(coordRoot: string, now: Date): SupervisorLease {
|
|
|
506
507
|
created_at: now.toISOString(),
|
|
507
508
|
};
|
|
508
509
|
try {
|
|
509
|
-
const fd = openSync(paths.lease, "wx",
|
|
510
|
+
const fd = openSync(paths.lease, "wx", stateFileMode());
|
|
510
511
|
try {
|
|
511
512
|
writeFileSync(fd, `${JSON.stringify(lease)}\n`, "utf8");
|
|
512
513
|
} finally {
|
package/src/core/work/state.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
appendSegmentedJsonlFile,
|
|
18
18
|
readSegmentedJsonlFileSync,
|
|
19
19
|
} from "../storage/durable-history.ts";
|
|
20
|
+
import { stateDirMode, stateFileMode } from "../storage/modes.ts";
|
|
20
21
|
import { readWorkflowApproval } from "../workflow/approvals.ts";
|
|
21
22
|
import { stableDigest } from "../workflow/durable-record.ts";
|
|
22
23
|
import { readWorkflowProof } from "../workflow/proof.ts";
|
|
@@ -260,9 +261,9 @@ export function createWorkItem(input: CreateWorkItemInput): WorkRecord {
|
|
|
260
261
|
created_at: new Date().toISOString(),
|
|
261
262
|
};
|
|
262
263
|
const dir = workDir(coordRoot, id);
|
|
263
|
-
mkdirSync(dirname(dir), { recursive: true, mode:
|
|
264
|
-
mkdirSync(dir, { mode:
|
|
265
|
-
chmodSync(dir,
|
|
264
|
+
mkdirSync(dirname(dir), { recursive: true, mode: stateDirMode() });
|
|
265
|
+
mkdirSync(dir, { mode: stateDirMode() });
|
|
266
|
+
chmodSync(dir, stateDirMode());
|
|
266
267
|
writePrivateJson(join(dir, "intent.json"), intent, MAX_INTENT_BYTES);
|
|
267
268
|
const release = acquireWorkLease(coordRoot, id);
|
|
268
269
|
try {
|
|
@@ -1250,13 +1251,13 @@ function acquireWorkFileLease(
|
|
|
1250
1251
|
};
|
|
1251
1252
|
const acquire = (): boolean => {
|
|
1252
1253
|
try {
|
|
1253
|
-
const fd = openSync(path, "wx",
|
|
1254
|
+
const fd = openSync(path, "wx", stateFileMode());
|
|
1254
1255
|
try {
|
|
1255
1256
|
writeFileSync(fd, `${JSON.stringify(owner)}\n`, "utf8");
|
|
1256
1257
|
} finally {
|
|
1257
1258
|
closeSync(fd);
|
|
1258
1259
|
}
|
|
1259
|
-
chmodSync(path,
|
|
1260
|
+
chmodSync(path, stateFileMode());
|
|
1260
1261
|
return true;
|
|
1261
1262
|
} catch (error) {
|
|
1262
1263
|
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
@@ -1337,8 +1338,8 @@ function writePrivateJson(path: string, value: unknown, maxBytes: number): void
|
|
|
1337
1338
|
const body = `${JSON.stringify(value, null, 2)}\n`;
|
|
1338
1339
|
if (Buffer.byteLength(body) > maxBytes)
|
|
1339
1340
|
throw new Error(`private record exceeds ${maxBytes} bytes`);
|
|
1340
|
-
writeFileSync(path, body, { encoding: "utf8", flag: "wx", mode:
|
|
1341
|
-
chmodSync(path,
|
|
1341
|
+
writeFileSync(path, body, { encoding: "utf8", flag: "wx", mode: stateFileMode() });
|
|
1342
|
+
chmodSync(path, stateFileMode());
|
|
1342
1343
|
}
|
|
1343
1344
|
|
|
1344
1345
|
function readBoundedLines(
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* but they do drift — keep this module the single place they live.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { Adapter } from "../adapter.ts";
|
|
11
|
+
import { probeBinaryVersion } from "../adapters/bench.ts";
|
|
10
12
|
import { BUILTIN_ADAPTER_PROFILES } from "../adapters/profiles.ts";
|
|
11
13
|
import type { AdapterName } from "./types.ts";
|
|
12
14
|
|
|
@@ -24,6 +26,19 @@ export const ADAPTER_LOGIN_HINTS: Record<string, string> = Object.fromEntries(
|
|
|
24
26
|
Object.values(BUILTIN_ADAPTER_PROFILES).map((profile) => [profile.id, profile.loginHint]),
|
|
25
27
|
);
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Adapter ids whose CLI answers a version probe on this machine's PATH, in
|
|
31
|
+
* `BUILTIN_ADAPTER_PROFILES` order. The probe is injectable so callers can keep
|
|
32
|
+
* tests deterministic instead of keying on the host's toolchain.
|
|
33
|
+
*/
|
|
34
|
+
export function detectInstalledAdapters(
|
|
35
|
+
probe: (binary: string) => unknown = probeBinaryVersion,
|
|
36
|
+
): Adapter[] {
|
|
37
|
+
return (Object.keys(ADAPTER_BINARIES) as Adapter[]).filter(
|
|
38
|
+
(id) => probe(ADAPTER_BINARIES[id]) !== null,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
/** One-line "it's missing, here's the fix" string for spawn adapters. */
|
|
28
43
|
export function notFoundError(adapter: AdapterName): string {
|
|
29
44
|
const binary = ADAPTER_BINARIES[adapter] ?? adapter;
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "node:fs";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import type { PolicyEvaluation, PolicyRequestSummary, PolicyVerdict } from "../policy/index.ts";
|
|
15
|
+
import { stateDirMode, stateFileMode } from "../storage/modes.ts";
|
|
15
16
|
import { appendWorkflowTranscriptEvent } from "./transcript.ts";
|
|
16
17
|
|
|
17
18
|
export const WORKFLOW_APPROVAL_SCHEMA_VERSION = 1 as const;
|
|
@@ -108,8 +109,8 @@ export function createWorkflowApproval(input: CreateWorkflowApprovalInput): {
|
|
|
108
109
|
request_sha256: requestSha256,
|
|
109
110
|
};
|
|
110
111
|
const dir = approvalDir(input.coordRoot, id);
|
|
111
|
-
mkdirSync(dir, { recursive: true, mode:
|
|
112
|
-
chmodSync(dir,
|
|
112
|
+
mkdirSync(dir, { recursive: true, mode: stateDirMode() });
|
|
113
|
+
chmodSync(dir, stateDirMode());
|
|
113
114
|
const created = writeExclusiveJson(join(dir, "request.json"), record);
|
|
114
115
|
const approval = readWorkflowApproval(input.coordRoot, id);
|
|
115
116
|
if (
|
|
@@ -249,17 +250,17 @@ function writeExclusiveJson(path: string, value: unknown): boolean {
|
|
|
249
250
|
if (Buffer.byteLength(body) > RECORD_LIMIT_BYTES) {
|
|
250
251
|
throw new Error(`approval record exceeds ${RECORD_LIMIT_BYTES} bytes`);
|
|
251
252
|
}
|
|
252
|
-
mkdirSync(dirname(path), { recursive: true, mode:
|
|
253
|
+
mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
|
|
253
254
|
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
254
255
|
try {
|
|
255
|
-
writeFileSync(temporary, body, { encoding: "utf8", flag: "wx", mode:
|
|
256
|
+
writeFileSync(temporary, body, { encoding: "utf8", flag: "wx", mode: stateFileMode() });
|
|
256
257
|
try {
|
|
257
258
|
linkSync(temporary, path);
|
|
258
259
|
} catch (error) {
|
|
259
260
|
if ((error as NodeJS.ErrnoException).code === "EEXIST") return false;
|
|
260
261
|
throw error;
|
|
261
262
|
}
|
|
262
|
-
chmodSync(path,
|
|
263
|
+
chmodSync(path, stateFileMode());
|
|
263
264
|
return true;
|
|
264
265
|
} finally {
|
|
265
266
|
if (existsSync(temporary)) unlinkSync(temporary);
|
|
@@ -85,10 +85,7 @@ export function probeBilling(adapter: AdapterName, io: ProbeIo = {}): BillingPro
|
|
|
85
85
|
break;
|
|
86
86
|
}
|
|
87
87
|
case "cursor":
|
|
88
|
-
|
|
89
|
-
// live install (adapter itself is pending verification); never claim
|
|
90
|
-
// presence or absence we can't prove.
|
|
91
|
-
login = "unknown";
|
|
88
|
+
login = probeCursorLogin(env, home);
|
|
92
89
|
break;
|
|
93
90
|
default:
|
|
94
91
|
// External adapters own their authentication. The generic engine must
|
|
@@ -126,6 +123,39 @@ function probeClaudeLogin(home: string): LoginState {
|
|
|
126
123
|
return existsSync(dir) ? "unknown" : "absent";
|
|
127
124
|
}
|
|
128
125
|
|
|
126
|
+
/**
|
|
127
|
+
* cursor-agent: the stored login is `auth.json` holding `accessToken` and
|
|
128
|
+
* `refreshToken`. The path follows the CLI's own resolution, read from the
|
|
129
|
+
* installed 2026.09.18 build: `$CURSOR_CONFIG_DIR` when set, else
|
|
130
|
+
* `%APPDATA%\\cursor` on Windows, `~/.cursor` on macOS, and
|
|
131
|
+
* `$XDG_CONFIG_HOME/cursor` (default `~/.config/cursor`) elsewhere.
|
|
132
|
+
* `cursor-agent status --format json` reports the same two tokens as
|
|
133
|
+
* `hasAccessToken` / `hasRefreshToken`.
|
|
134
|
+
*/
|
|
135
|
+
function probeCursorLogin(env: NodeJS.ProcessEnv, home: string): LoginState {
|
|
136
|
+
const authPath = join(cursorConfigDir(env, home), "auth.json");
|
|
137
|
+
if (!existsSync(authPath)) return "absent";
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(readFileSync(authPath, "utf8")) as {
|
|
140
|
+
accessToken?: string;
|
|
141
|
+
refreshToken?: string;
|
|
142
|
+
};
|
|
143
|
+
return parsed.accessToken?.trim() || parsed.refreshToken?.trim() ? "present" : "absent";
|
|
144
|
+
} catch {
|
|
145
|
+
return "unknown";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function cursorConfigDir(env: NodeJS.ProcessEnv, home: string): string {
|
|
150
|
+
const override = env.CURSOR_CONFIG_DIR?.trim();
|
|
151
|
+
if (override) return override;
|
|
152
|
+
const platform = env.HARNERY_BILLING_PLATFORM?.trim() || process.platform;
|
|
153
|
+
if (platform === "win32")
|
|
154
|
+
return join(env.APPDATA?.trim() || join(home, "AppData", "Roaming"), "cursor");
|
|
155
|
+
if (platform === "darwin") return join(home, ".cursor");
|
|
156
|
+
return join(env.XDG_CONFIG_HOME?.trim() || join(home, ".config"), "cursor");
|
|
157
|
+
}
|
|
158
|
+
|
|
129
159
|
/** codex: auth lives at $CODEX_HOME/auth.json (default ~/.codex). A `tokens`
|
|
130
160
|
* object means a ChatGPT (subscription) login; an OPENAI_API_KEY field means
|
|
131
161
|
* a stored API key (`codex login --api-key`). */
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
writeFileSync,
|
|
14
14
|
} from "node:fs";
|
|
15
15
|
import { dirname } from "node:path";
|
|
16
|
+
import { stateDirMode, stateFileMode } from "../storage/modes.ts";
|
|
16
17
|
|
|
17
18
|
const JSON_LIMIT = 512 * 1024;
|
|
18
19
|
|
|
@@ -33,18 +34,18 @@ export function writeImmutableJson(path: string, value: unknown): boolean {
|
|
|
33
34
|
if (Buffer.byteLength(body) > JSON_LIMIT) {
|
|
34
35
|
throw new Error(`immutable record exceeds ${JSON_LIMIT} bytes`);
|
|
35
36
|
}
|
|
36
|
-
mkdirSync(dirname(path), { recursive: true, mode:
|
|
37
|
+
mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
|
|
37
38
|
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
38
39
|
let fd: number | undefined;
|
|
39
40
|
try {
|
|
40
|
-
fd = openSync(temporary, "wx",
|
|
41
|
+
fd = openSync(temporary, "wx", stateFileMode());
|
|
41
42
|
writeFileSync(fd, body, "utf8");
|
|
42
43
|
fsyncSync(fd);
|
|
43
44
|
closeSync(fd);
|
|
44
45
|
fd = undefined;
|
|
45
46
|
try {
|
|
46
47
|
linkSync(temporary, path);
|
|
47
|
-
chmodSync(path,
|
|
48
|
+
chmodSync(path, stateFileMode());
|
|
48
49
|
fsyncParentDirectory(path);
|
|
49
50
|
return true;
|
|
50
51
|
} catch (error) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Adapter, adapterFromPlatform } from "../adapter.ts";
|
|
2
2
|
import { recordLiveTaskChangeV3 } from "../agents/live-authority-v3.ts";
|
|
3
3
|
import { endSessionExplicitV3 } from "../agents/session-finalizer-v3.ts";
|
|
4
4
|
import {
|
|
@@ -115,8 +115,7 @@ function requireV3Route(coordRoot: string) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
function workflowAdapter(adapter: string | undefined): Adapter {
|
|
118
|
-
|
|
119
|
-
return "claude-code";
|
|
118
|
+
return adapterFromPlatform(adapter, { context: "workflow-child" });
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
export { stableScopeId } from "./scope-id.ts";
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type PolicyNetworkAccess,
|
|
21
21
|
policyDigest,
|
|
22
22
|
} from "../policy/index.ts";
|
|
23
|
+
import { stateFileMode } from "../storage/modes.ts";
|
|
23
24
|
import { assertWorkflowRunId, readWorkflowApproval } from "./approvals.ts";
|
|
24
25
|
import { isCanonicalWorkflowAttemptContext } from "./attempt-context.ts";
|
|
25
26
|
import { readJsonRecord, writeImmutableJson } from "./durable-record.ts";
|
|
@@ -205,13 +206,13 @@ export function acquireWorkflowResumeLease(coordRoot: string, runId: string): ()
|
|
|
205
206
|
|
|
206
207
|
const acquire = (): boolean => {
|
|
207
208
|
try {
|
|
208
|
-
const fd = openSync(path, "wx",
|
|
209
|
+
const fd = openSync(path, "wx", stateFileMode());
|
|
209
210
|
try {
|
|
210
211
|
writeFileSync(fd, `${JSON.stringify(owner)}\n`, "utf8");
|
|
211
212
|
} finally {
|
|
212
213
|
closeSync(fd);
|
|
213
214
|
}
|
|
214
|
-
chmodSync(path,
|
|
215
|
+
chmodSync(path, stateFileMode());
|
|
215
216
|
return true;
|
|
216
217
|
} catch (error) {
|
|
217
218
|
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opencode spawn adapter: runs one subagent as a headless `opencode run`
|
|
3
|
+
* subprocess with `--format json`.
|
|
4
|
+
*
|
|
5
|
+
* Contract notes (verified 2026-09-20 against opencode v2.0.11: a
|
|
6
|
+
* `--format json` run streams NDJSON parts and a `PONG` probe round-trips):
|
|
7
|
+
* - `opencode run --format json --model <provider/model#variant> "<prompt>"`
|
|
8
|
+
* prints one JSON object per line (NDJSON). Each line carries `sessionID`;
|
|
9
|
+
* the assistant answer is the concatenation of `type:"text"` parts.
|
|
10
|
+
* - `--title` names the session; the final text is assembled from the stream
|
|
11
|
+
* rather than a single result envelope (there is no `type:"result"` line).
|
|
12
|
+
* - `run` connects to the shared background service by default; a workflow
|
|
13
|
+
* child inherits whatever service is discovered, matching interactive runs.
|
|
14
|
+
* - No per-run cost surface in the stream (cost lives in `session export`) →
|
|
15
|
+
* left undefined. No max-turns equivalent → `maxTurns` accepted and ignored.
|
|
16
|
+
* - OpenCode selects reasoning through the model id variant, so there is no
|
|
17
|
+
* effort flag; `effortValues` is empty and a supplied effort is rejected.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { exec } from "../../lib/exec.ts";
|
|
21
|
+
import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profiles.ts";
|
|
22
|
+
import type { AdapterInvocation, AdapterRawResult } from "../adapters/types.ts";
|
|
23
|
+
import { notFoundError } from "./adapters.ts";
|
|
24
|
+
import { buildChildEnv } from "./child-env.ts";
|
|
25
|
+
import { resolveSandboxProjection } from "./sandbox-projection.ts";
|
|
26
|
+
import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.ts";
|
|
27
|
+
import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
|
|
28
|
+
|
|
29
|
+
interface OpenCodeStreamPart {
|
|
30
|
+
type?: string;
|
|
31
|
+
sessionID?: string;
|
|
32
|
+
part?: { type?: string; text?: string };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse the NDJSON `opencode run --format json` stream into the final answer
|
|
37
|
+
* text and the session id. Exported for unit tests (no live binary needed).
|
|
38
|
+
*
|
|
39
|
+
* Tolerant by contract: non-JSON lines (a stray log line) are skipped, and the
|
|
40
|
+
* text is the ordered concatenation of every `text` part so a multi-part answer
|
|
41
|
+
* is preserved.
|
|
42
|
+
*/
|
|
43
|
+
export function parseOpenCodeStream(stdout: string): {
|
|
44
|
+
text: string;
|
|
45
|
+
sessionId?: string;
|
|
46
|
+
} {
|
|
47
|
+
let sessionId: string | undefined;
|
|
48
|
+
const chunks: string[] = [];
|
|
49
|
+
for (const line of stdout.split("\n")) {
|
|
50
|
+
const trimmed = line.trim();
|
|
51
|
+
if (!trimmed) continue;
|
|
52
|
+
let parsed: OpenCodeStreamPart;
|
|
53
|
+
try {
|
|
54
|
+
parsed = JSON.parse(trimmed) as OpenCodeStreamPart;
|
|
55
|
+
} catch {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!sessionId && typeof parsed.sessionID === "string" && parsed.sessionID.length > 0) {
|
|
59
|
+
sessionId = parsed.sessionID;
|
|
60
|
+
}
|
|
61
|
+
if (parsed.type === "text" && typeof parsed.part?.text === "string") {
|
|
62
|
+
chunks.push(parsed.part.text);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { text: chunks.join("").trim(), sessionId };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function buildOpenCodeInvocation(req: SpawnRequest): AdapterInvocation {
|
|
69
|
+
validateAdapterEffort("opencode", req.effort);
|
|
70
|
+
if (req.filesystemPolicy) {
|
|
71
|
+
// Declared unrepresentable: refuse rather than drop it silently (ADR 0039).
|
|
72
|
+
resolveSandboxProjection(
|
|
73
|
+
"opencode",
|
|
74
|
+
builtinAdapterProfile("opencode")?.sandboxProjection,
|
|
75
|
+
req.filesystemPolicy,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
// `--auto` auto-approves permissions that are not explicitly denied, which a
|
|
79
|
+
// headless workflow child needs because it has no interactive approval
|
|
80
|
+
// channel; it does not map host policy into child tools (unsupported).
|
|
81
|
+
const argv = ["opencode", "run", "--format", "json", "--auto"];
|
|
82
|
+
if (req.model) argv.push("--model", req.model);
|
|
83
|
+
argv.push(req.prompt);
|
|
84
|
+
return { argv };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function normalizeOpenCodeResult(raw: AdapterRawResult): SpawnResult {
|
|
88
|
+
if (raw.timedOut) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
text: "",
|
|
92
|
+
durationMs: raw.durationMs,
|
|
93
|
+
error: `opencode timed out after ${raw.durationMs}ms and was killed`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// Structural environment signal: the binary was never there (spawned directly,
|
|
97
|
+
// so a missing binary surfaces as ENOENT). Uncharged and not retried.
|
|
98
|
+
if (raw.spawnErrno === "ENOENT") {
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
text: "",
|
|
102
|
+
durationMs: raw.durationMs,
|
|
103
|
+
error: notFoundError("opencode"),
|
|
104
|
+
class: "environment",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (raw.exitCode === 127) {
|
|
108
|
+
// A bare 127 with no errno is a shell/vendor 127, indistinguishable from a
|
|
109
|
+
// legitimate one — charged as work rather than classed environment.
|
|
110
|
+
return { ok: false, text: "", durationMs: raw.durationMs, error: notFoundError("opencode") };
|
|
111
|
+
}
|
|
112
|
+
if (raw.exitCode !== 0) {
|
|
113
|
+
const failureText = vendorFailureText(raw);
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
text: "",
|
|
117
|
+
durationMs: raw.durationMs,
|
|
118
|
+
error: `opencode exited ${raw.exitCode}: ${failureText}`,
|
|
119
|
+
...(isUpstreamFailureText(failureText) ? { class: "upstream" as const } : {}),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const parsed = parseOpenCodeStream(raw.stdout);
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
text: parsed.text,
|
|
127
|
+
sessionId: parsed.sessionId,
|
|
128
|
+
durationMs: raw.durationMs,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const openCodeSpawner: Spawner = async (req: SpawnRequest): Promise<SpawnResult> => {
|
|
133
|
+
const t0 = Date.now();
|
|
134
|
+
let invocation: AdapterInvocation;
|
|
135
|
+
try {
|
|
136
|
+
invocation = buildOpenCodeInvocation(req);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return { ok: false, text: "", durationMs: 0, error: (error as Error).message };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const r = await exec(invocation.argv, {
|
|
142
|
+
cwd: req.cwd,
|
|
143
|
+
env: buildChildEnv(req.runId, {
|
|
144
|
+
subscriptionOnly: req.subscriptionOnly,
|
|
145
|
+
agentId: req.agentId,
|
|
146
|
+
}),
|
|
147
|
+
timeout: req.timeoutMs,
|
|
148
|
+
});
|
|
149
|
+
return normalizeOpenCodeResult({ ...r, durationMs: Date.now() - t0 });
|
|
150
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { stateDirMode, stateFileMode } from "../storage/modes.ts";
|
|
3
4
|
import { fsyncParentDirectory, stableDigest } from "./durable-record.ts";
|
|
4
5
|
|
|
5
6
|
// Limit for the JSON record body. The trailing newline delimiter is outside the
|
|
@@ -47,9 +48,9 @@ export function appendWorkflowTranscriptEvent(
|
|
|
47
48
|
data,
|
|
48
49
|
);
|
|
49
50
|
const line = `${JSON.stringify(record)}\n`;
|
|
50
|
-
mkdirSync(dirname(path), { recursive: true, mode:
|
|
51
|
+
mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
|
|
51
52
|
const existed = existsSync(path);
|
|
52
|
-
const fd = openSync(path, "a",
|
|
53
|
+
const fd = openSync(path, "a", stateFileMode());
|
|
53
54
|
try {
|
|
54
55
|
writeFileSync(fd, line, "utf8");
|
|
55
56
|
fsyncSync(fd);
|