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
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "node:fs";
|
|
17
17
|
import { hostname } from "node:os";
|
|
18
18
|
import { basename, dirname, join, resolve } from "node:path";
|
|
19
|
+
import { stateDirMode, stateFileMode } from "../../storage/modes.ts";
|
|
19
20
|
|
|
20
21
|
const MAX_LEASE_BYTES = 8 * 1024;
|
|
21
22
|
const MAX_CONTENTION_OBSERVATION_ATTEMPTS = 8;
|
|
@@ -202,11 +203,11 @@ function validateInput(input: AcquireNoClobberLeaseInput): void {
|
|
|
202
203
|
}
|
|
203
204
|
|
|
204
205
|
function ensureLeaseDirectory(leaseDir: string, onCreate?: () => void): void {
|
|
205
|
-
mkdirSync(dirname(leaseDir), { recursive: true, mode:
|
|
206
|
+
mkdirSync(dirname(leaseDir), { recursive: true, mode: stateDirMode() });
|
|
206
207
|
let created = false;
|
|
207
208
|
onCreate?.();
|
|
208
209
|
try {
|
|
209
|
-
mkdirSync(leaseDir, { mode:
|
|
210
|
+
mkdirSync(leaseDir, { mode: stateDirMode() });
|
|
210
211
|
created = true;
|
|
211
212
|
} catch (error) {
|
|
212
213
|
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
@@ -226,7 +227,7 @@ function createRecoveryClaim(
|
|
|
226
227
|
observed: { owner: NoClobberLeaseOwner; bytes: string },
|
|
227
228
|
): void {
|
|
228
229
|
const temporary = join(leaseDir, `recovery-claim-${claimant.owner_id}`);
|
|
229
|
-
mkdirSync(temporary, { mode:
|
|
230
|
+
mkdirSync(temporary, { mode: stateDirMode() });
|
|
230
231
|
const claim: RecoveryClaim = {
|
|
231
232
|
schema_version: 1,
|
|
232
233
|
claimant,
|
|
@@ -385,7 +386,7 @@ function leaseHandle(
|
|
|
385
386
|
}
|
|
386
387
|
|
|
387
388
|
function writeExclusive(path: string, bytes: string): void {
|
|
388
|
-
const fd = openSync(path, "wx",
|
|
389
|
+
const fd = openSync(path, "wx", stateFileMode());
|
|
389
390
|
try {
|
|
390
391
|
writeFileSync(fd, bytes, "utf8");
|
|
391
392
|
fsyncSync(fd);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
writeFileSync,
|
|
9
9
|
} from "node:fs";
|
|
10
10
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
11
|
+
import { stateDirMode, stateFileMode } from "../../storage/modes.ts";
|
|
11
12
|
import { WorkspaceAttestationError } from "./attestation-error.ts";
|
|
12
13
|
import {
|
|
13
14
|
git,
|
|
@@ -323,7 +324,7 @@ function claimedParentSegment(claim: WorkspaceClaim): string {
|
|
|
323
324
|
function hideWorkspaceParentFromGit(parent: string): void {
|
|
324
325
|
const ignoreFile = join(parent, ".gitignore");
|
|
325
326
|
if (existsSync(ignoreFile)) return;
|
|
326
|
-
writeFileSync(ignoreFile, "*\n", { mode:
|
|
327
|
+
writeFileSync(ignoreFile, "*\n", { mode: stateFileMode() });
|
|
327
328
|
}
|
|
328
329
|
|
|
329
330
|
function reconcileAllocation(
|
|
@@ -1908,7 +1909,7 @@ function capabilityDigestForClaim(claim: WorkspaceClaim): string {
|
|
|
1908
1909
|
|
|
1909
1910
|
function acquireRepositoryLease(coordRoot: string, claim: WorkspaceClaim): () => void {
|
|
1910
1911
|
const leaseDir = join(resolve(coordRoot), ".harnery", "workspaces", PROVIDER_ID, ".leases");
|
|
1911
|
-
mkdirSync(leaseDir, { recursive: true, mode:
|
|
1912
|
+
mkdirSync(leaseDir, { recursive: true, mode: stateDirMode() });
|
|
1912
1913
|
// Mutual exclusion keys on the Git common directory alone. `git worktree add`,
|
|
1913
1914
|
// `prune`, and the shared-`config` migration all write to the common directory's
|
|
1914
1915
|
// administrative area, and once worktrees and submodules are allowed, several
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
statSync,
|
|
10
10
|
} from "node:fs";
|
|
11
11
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
12
|
+
import { stateDirMode } from "../../storage/modes.ts";
|
|
12
13
|
import type { FilesystemIdentity, ValidatedFilesystemPath } from "./types.ts";
|
|
13
14
|
|
|
14
15
|
const WINDOWS_AMBIGUOUS = /[<>:"|?*]|[ .]$/;
|
|
@@ -152,7 +153,7 @@ export function openContainedDirectory(
|
|
|
152
153
|
);
|
|
153
154
|
}
|
|
154
155
|
} else {
|
|
155
|
-
mkdirSync(candidate, { mode:
|
|
156
|
+
mkdirSync(candidate, { mode: stateDirMode() });
|
|
156
157
|
}
|
|
157
158
|
const childFd = openSync(
|
|
158
159
|
candidate,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
writeFileSync,
|
|
11
11
|
} from "node:fs";
|
|
12
12
|
import { dirname, join, resolve } from "node:path";
|
|
13
|
+
import { stateDirMode, stateFileMode } from "../../storage/modes.ts";
|
|
13
14
|
import {
|
|
14
15
|
canonicalJson,
|
|
15
16
|
fsyncParentDirectory,
|
|
@@ -192,7 +193,7 @@ export function appendWorkspaceEvent(
|
|
|
192
193
|
throw new Error("workspace provider event is invalid or too large");
|
|
193
194
|
}
|
|
194
195
|
const path = workspaceEventsPath(coordRoot, claim.provider_id, claim.binding_id);
|
|
195
|
-
mkdirSync(dirname(path), { recursive: true, mode:
|
|
196
|
+
mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
|
|
196
197
|
const existed = existsSync(path);
|
|
197
198
|
const prior = readWorkspaceEvents(coordRoot, claim.provider_id, claim.binding_id);
|
|
198
199
|
const basis = {
|
|
@@ -213,14 +214,14 @@ export function appendWorkspaceEvent(
|
|
|
213
214
|
const line = `${canonicalJson(record)}\n`;
|
|
214
215
|
if (Buffer.byteLength(line) > WORKSPACE_EVENT_BYTES)
|
|
215
216
|
throw new Error("workspace provider event is too large");
|
|
216
|
-
const fd = openSync(path, "a",
|
|
217
|
+
const fd = openSync(path, "a", stateFileMode());
|
|
217
218
|
try {
|
|
218
219
|
writeFileSync(fd, line, "utf8");
|
|
219
220
|
fsyncSync(fd);
|
|
220
221
|
} finally {
|
|
221
222
|
closeSync(fd);
|
|
222
223
|
}
|
|
223
|
-
chmodSync(path,
|
|
224
|
+
chmodSync(path, stateFileMode());
|
|
224
225
|
if (!existed) fsyncParentDirectory(path);
|
|
225
226
|
return record;
|
|
226
227
|
}
|
|
@@ -295,7 +296,7 @@ export function appendCleanupAttempt(
|
|
|
295
296
|
input: Omit<WorkspaceCleanupAttempt, "seq" | "previous_sha256" | "record_sha256">,
|
|
296
297
|
): WorkspaceCleanupAttempt {
|
|
297
298
|
const path = join(workflowRunDir(coordRoot, runId), "cleanup", "attempts.jsonl");
|
|
298
|
-
mkdirSync(dirname(path), { recursive: true, mode:
|
|
299
|
+
mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
|
|
299
300
|
const existed = existsSync(path);
|
|
300
301
|
const prior = readCleanupAttempts(coordRoot, runId);
|
|
301
302
|
const basis = {
|
|
@@ -311,14 +312,14 @@ export function appendCleanupAttempt(
|
|
|
311
312
|
if (Buffer.byteLength(line) > WORKSPACE_EVENT_BYTES) {
|
|
312
313
|
throw new Error("workspace cleanup attempt is too large");
|
|
313
314
|
}
|
|
314
|
-
const fd = openSync(path, "a",
|
|
315
|
+
const fd = openSync(path, "a", stateFileMode());
|
|
315
316
|
try {
|
|
316
317
|
writeFileSync(fd, line, "utf8");
|
|
317
318
|
fsyncSync(fd);
|
|
318
319
|
} finally {
|
|
319
320
|
closeSync(fd);
|
|
320
321
|
}
|
|
321
|
-
chmodSync(path,
|
|
322
|
+
chmodSync(path, stateFileMode());
|
|
322
323
|
if (!existed) fsyncParentDirectory(path);
|
|
323
324
|
return attempt;
|
|
324
325
|
}
|
|
@@ -329,7 +330,7 @@ export function appendIntegrationAttempt(
|
|
|
329
330
|
input: Omit<IntegrationApplyAttempt, "seq" | "previous_sha256" | "record_sha256">,
|
|
330
331
|
): IntegrationApplyAttempt {
|
|
331
332
|
const path = join(workflowRunDir(coordRoot, runId), "integration", "attempts.jsonl");
|
|
332
|
-
mkdirSync(dirname(path), { recursive: true, mode:
|
|
333
|
+
mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
|
|
333
334
|
const existed = existsSync(path);
|
|
334
335
|
const prior = readIntegrationAttempts(coordRoot, runId);
|
|
335
336
|
const basis = {
|
|
@@ -372,14 +373,14 @@ function appendChainedRecord(
|
|
|
372
373
|
if (Buffer.byteLength(line) > WORKSPACE_EVENT_BYTES) {
|
|
373
374
|
throw new Error(`${label} is too large`);
|
|
374
375
|
}
|
|
375
|
-
const fd = openSync(path, "a",
|
|
376
|
+
const fd = openSync(path, "a", stateFileMode());
|
|
376
377
|
try {
|
|
377
378
|
writeFileSync(fd, line, "utf8");
|
|
378
379
|
fsyncSync(fd);
|
|
379
380
|
} finally {
|
|
380
381
|
closeSync(fd);
|
|
381
382
|
}
|
|
382
|
-
chmodSync(path,
|
|
383
|
+
chmodSync(path, stateFileMode());
|
|
383
384
|
}
|
|
384
385
|
|
|
385
386
|
function readChainedRecords<
|
package/src/lazy-commands.ts
CHANGED
|
@@ -256,7 +256,17 @@ function selectedCommand(
|
|
|
256
256
|
if (token === "--") return undefined;
|
|
257
257
|
if (token.startsWith("-")) {
|
|
258
258
|
const option = findRootOption(program, token);
|
|
259
|
-
|
|
259
|
+
// `--format=json` carries its value inline, so the next token is the
|
|
260
|
+
// command, not the value. Skipping it here consumed the command name and
|
|
261
|
+
// the wrong bundle (or none) was materialized, which surfaced downstream
|
|
262
|
+
// as empty output or a bogus "too many arguments" error.
|
|
263
|
+
const inlineValue = token.includes("=");
|
|
264
|
+
if (
|
|
265
|
+
!inlineValue &&
|
|
266
|
+
(option?.required || (option?.optional && !args[index + 1]?.startsWith("-")))
|
|
267
|
+
) {
|
|
268
|
+
index++;
|
|
269
|
+
}
|
|
260
270
|
continue;
|
|
261
271
|
}
|
|
262
272
|
if (token === "help") return state.byName.get(args[index + 1] ?? "");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type SpawnSyncOptions, spawnSync } from "node:child_process";
|
|
2
2
|
import { writeFileSync } from "node:fs";
|
|
3
3
|
import type { CookieJar, Cookie as JarCookie } from "../cookies/index.ts";
|
|
4
|
+
import { PaceGate, pacePolicyFromEnv } from "../pace/index.ts";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Thin wrapper over Vercel Labs' `agent-browser` Rust CLI.
|
|
@@ -34,6 +35,11 @@ export interface AgentBrowserOptions {
|
|
|
34
35
|
* agent-browser session. Defaults to a tmp file derived from the jar.
|
|
35
36
|
*/
|
|
36
37
|
stateFilePath?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Human-pace gate applied before every `open`. Unset resolves the default
|
|
40
|
+
* from the `HARNERY_PACE*` environment; `null` disables pacing.
|
|
41
|
+
*/
|
|
42
|
+
pace?: PaceGate | null;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
export interface ExecResult {
|
|
@@ -48,11 +54,21 @@ const DEFAULT_TIMEOUT_MS = 60_000;
|
|
|
48
54
|
export class AgentBrowser {
|
|
49
55
|
private readonly opts: AgentBrowserOptions;
|
|
50
56
|
private cookiesSeeded = false;
|
|
57
|
+
private paceGate: PaceGate | null | undefined;
|
|
51
58
|
|
|
52
59
|
constructor(opts: AgentBrowserOptions = {}) {
|
|
53
60
|
this.opts = opts;
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
/** Block for the human-pace slot before a page load. Synchronous like the rest of this client. */
|
|
64
|
+
private paceBefore(url: string): void {
|
|
65
|
+
if (this.paceGate === undefined) {
|
|
66
|
+
this.paceGate =
|
|
67
|
+
this.opts.pace === undefined ? new PaceGate(pacePolicyFromEnv()) : this.opts.pace;
|
|
68
|
+
}
|
|
69
|
+
this.paceGate?.beforeSync(url);
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
/**
|
|
57
73
|
* Run an `agent-browser` subcommand. Returns a structured result.
|
|
58
74
|
* Throws only on spawn failure (e.g., binary not found); non-zero exit
|
|
@@ -132,6 +148,7 @@ export class AgentBrowser {
|
|
|
132
148
|
|
|
133
149
|
open(url: string, timeoutMs?: number): ExecResult {
|
|
134
150
|
this.seedCookies();
|
|
151
|
+
this.paceBefore(url);
|
|
135
152
|
return this.execOrThrow(["open", url], timeoutMs);
|
|
136
153
|
}
|
|
137
154
|
|
|
@@ -183,6 +200,7 @@ export class AgentBrowser {
|
|
|
183
200
|
// Naive shell-like splitting; agent-browser's own argv parser handles
|
|
184
201
|
// the actual command; we just split on whitespace for the wrapper.
|
|
185
202
|
const args = trimmed.split(/\s+/);
|
|
203
|
+
if (args[0] === "open" && args[1]) this.paceBefore(args[1]);
|
|
186
204
|
results.push(this.exec(args));
|
|
187
205
|
}
|
|
188
206
|
return results;
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
CookieStoreParseError,
|
|
17
17
|
type Cookie as JarCookie,
|
|
18
18
|
} from "../cookies/index.ts";
|
|
19
|
+
import { PaceGate, pacePolicyFromEnv } from "../pace/index.ts";
|
|
19
20
|
import { type AssertResult, type AssertSpec, buildAssertCheck } from "./asserts.js";
|
|
20
21
|
import { stitchPngRows } from "./capture-fidelity.js";
|
|
21
22
|
import {
|
|
@@ -33,6 +34,8 @@ import {
|
|
|
33
34
|
type LayoutLintRequest,
|
|
34
35
|
type LayoutLintResult,
|
|
35
36
|
} from "./geometry.js";
|
|
37
|
+
import { browserIdentity, installDocumentIdentity } from "./identity.js";
|
|
38
|
+
import { AUTOMATION_DEFAULT_ARGS_TO_DROP, automationDisguiseArgs } from "./launch-args.js";
|
|
36
39
|
import {
|
|
37
40
|
buildClearLayoutAnnotationsScript,
|
|
38
41
|
buildLayoutAnnotateScript,
|
|
@@ -91,7 +94,7 @@ export interface BrowserOptions {
|
|
|
91
94
|
/** Cookie jar to seed/sync with. Pass `null` to skip jar entirely. */
|
|
92
95
|
jar?: CookieJar | null;
|
|
93
96
|
/** Viewport. Default 1280x800. */
|
|
94
|
-
viewport?: { width: number; height: number };
|
|
97
|
+
viewport?: { width: number; height: number } | null;
|
|
95
98
|
/**
|
|
96
99
|
* Device scale factor (DPR) for the context. Unset keeps Playwright's
|
|
97
100
|
* default of 1, so screenshots are CSS-pixel sized. A higher value renders
|
|
@@ -128,6 +131,13 @@ export interface BrowserOptions {
|
|
|
128
131
|
* via this callback (e.g., a Cloudflare-bypass header for specific zones).
|
|
129
132
|
*/
|
|
130
133
|
extraHeaders?: (url: string) => Record<string, string>;
|
|
134
|
+
/**
|
|
135
|
+
* Human-pace gate applied before every page load: `navigate`, `reload`, and
|
|
136
|
+
* the session tab verbs. Unset resolves the default from the `HARNERY_PACE*`
|
|
137
|
+
* environment (on, 3 to 9 seconds between loads of one site). `null`
|
|
138
|
+
* disables pacing for this client.
|
|
139
|
+
*/
|
|
140
|
+
pace?: PaceGate | null;
|
|
131
141
|
/**
|
|
132
142
|
* Extra Chromium command-line flags, passed through to Playwright's
|
|
133
143
|
* `launchPersistentContext` `args`. Used for environment-specific
|
|
@@ -136,6 +146,28 @@ export interface BrowserOptions {
|
|
|
136
146
|
* defaults only.
|
|
137
147
|
*/
|
|
138
148
|
launchArgs?: string[];
|
|
149
|
+
/**
|
|
150
|
+
* Playwright browser channel. `"chrome"` launches the operator's installed
|
|
151
|
+
* Google Chrome instead of the bundled "Chrome for Testing" build. Unset or
|
|
152
|
+
* `"chromium"` keeps the bundled browser.
|
|
153
|
+
*/
|
|
154
|
+
channel?: "chrome" | "chrome-beta" | "msedge" | "chromium";
|
|
155
|
+
/**
|
|
156
|
+
* Drop Playwright's `--enable-automation` default, disable Blink's
|
|
157
|
+
* AutomationControlled feature, and run with the Chromium sandbox on (no
|
|
158
|
+
* `--no-sandbox`), so pages do not see `navigator.webdriver`, the automation
|
|
159
|
+
* infobar, or the unsupported-flag banner. Headed sessions set this; sites
|
|
160
|
+
* that screen for automation otherwise refuse clicks a person makes in the
|
|
161
|
+
* window.
|
|
162
|
+
*/
|
|
163
|
+
hideAutomation?: boolean;
|
|
164
|
+
/**
|
|
165
|
+
* User agent to present. Set by `browse` from `lib/browser/user-agent`
|
|
166
|
+
* (never Linux, never HeadlessChrome). Applied both as the Playwright
|
|
167
|
+
* context override and as `--user-agent` so workers and frames agree.
|
|
168
|
+
* Unset keeps the browser's native string.
|
|
169
|
+
*/
|
|
170
|
+
userAgent?: string;
|
|
139
171
|
/** Authenticated browser proxy passed directly to Playwright. */
|
|
140
172
|
proxy?: { server: string; username?: string; password?: string };
|
|
141
173
|
}
|
|
@@ -306,6 +338,7 @@ export class Browser {
|
|
|
306
338
|
private consoleEvents: ConsoleEvent[] = [];
|
|
307
339
|
private pageErrors: PageErrorEvent[] = [];
|
|
308
340
|
private failedRequests: FailedRequest[] = [];
|
|
341
|
+
private identityReady = new WeakMap<Page, Promise<void>>();
|
|
309
342
|
|
|
310
343
|
constructor(private opts: BrowserOptions = {}) {
|
|
311
344
|
this.profileDir = opts.profileDir ?? DEFAULT_PROFILE;
|
|
@@ -326,20 +359,54 @@ export class Browser {
|
|
|
326
359
|
*/
|
|
327
360
|
private async openOnce(): Promise<void> {
|
|
328
361
|
const jarCookies = this.opts.jar?.list() ?? [];
|
|
362
|
+
const launchIdentity = this.opts.userAgent ? browserIdentity(this.opts.userAgent) : undefined;
|
|
363
|
+
const launchArgs = [
|
|
364
|
+
...new Set([
|
|
365
|
+
...(this.opts.headed && this.opts.viewport == null ? ["--window-size=1536,864"] : []),
|
|
366
|
+
...(this.opts.launchArgs ?? []),
|
|
367
|
+
...(this.opts.hideAutomation ? automationDisguiseArgs() : []),
|
|
368
|
+
...(this.opts.userAgent ? [`--user-agent=${this.opts.userAgent}`] : []),
|
|
369
|
+
]),
|
|
370
|
+
];
|
|
329
371
|
|
|
330
372
|
// Hold a launch slot only across the spawn itself, not the browser's life.
|
|
331
373
|
await acquireLaunchSlot();
|
|
332
374
|
try {
|
|
333
375
|
this.context = await chromium.launchPersistentContext(this.profileDir, {
|
|
334
376
|
headless: !this.opts.headed,
|
|
335
|
-
|
|
377
|
+
// Service-worker-owned navigations bypass Playwright request routes.
|
|
378
|
+
...(this.getPaceGate()?.policy.enabled ? { serviceWorkers: "block" as const } : {}),
|
|
379
|
+
viewport:
|
|
380
|
+
this.opts.viewport !== undefined
|
|
381
|
+
? this.opts.viewport
|
|
382
|
+
: this.opts.headed
|
|
383
|
+
? null
|
|
384
|
+
: { width: 1280, height: 800 },
|
|
336
385
|
...(this.opts.deviceScaleFactor !== undefined
|
|
337
386
|
? { deviceScaleFactor: this.opts.deviceScaleFactor }
|
|
338
387
|
: {}),
|
|
339
388
|
...(this.opts.colorScheme ? { colorScheme: this.opts.colorScheme } : {}),
|
|
389
|
+
...(this.opts.userAgent ? { userAgent: this.opts.userAgent } : {}),
|
|
390
|
+
...(launchIdentity
|
|
391
|
+
? {
|
|
392
|
+
extraHTTPHeaders: {
|
|
393
|
+
"sec-ch-ua-platform": JSON.stringify(launchIdentity.userAgentMetadata.platform),
|
|
394
|
+
"sec-ch-ua-mobile": "?0",
|
|
395
|
+
"sec-ch-ua": launchIdentity.userAgentMetadata.brands
|
|
396
|
+
.map(
|
|
397
|
+
({ brand, version }) => `${JSON.stringify(brand)};v=${JSON.stringify(version)}`,
|
|
398
|
+
)
|
|
399
|
+
.join(", "),
|
|
400
|
+
},
|
|
401
|
+
}
|
|
402
|
+
: {}),
|
|
340
403
|
...(this.opts.launchTimeout !== undefined ? { timeout: this.opts.launchTimeout } : {}),
|
|
341
|
-
...(
|
|
342
|
-
|
|
404
|
+
...(launchArgs.length > 0 ? { args: launchArgs } : {}),
|
|
405
|
+
...(this.opts.channel && this.opts.channel !== "chromium"
|
|
406
|
+
? { channel: this.opts.channel }
|
|
407
|
+
: {}),
|
|
408
|
+
...(this.opts.hideAutomation
|
|
409
|
+
? { ignoreDefaultArgs: [...AUTOMATION_DEFAULT_ARGS_TO_DROP], chromiumSandbox: true }
|
|
343
410
|
: {}),
|
|
344
411
|
...(this.opts.proxy ? { proxy: this.opts.proxy } : {}),
|
|
345
412
|
...(this.opts.recordHarPath
|
|
@@ -350,18 +417,55 @@ export class Browser {
|
|
|
350
417
|
releaseLaunchSlot();
|
|
351
418
|
}
|
|
352
419
|
this.context.setDefaultNavigationTimeout(this.opts.navigationTimeout ?? 30_000);
|
|
420
|
+
const identity = this.opts.userAgent
|
|
421
|
+
? browserIdentity(this.opts.userAgent, this.context.browser()?.version())
|
|
422
|
+
: undefined;
|
|
423
|
+
if (identity) await this.context.addInitScript(installDocumentIdentity, identity);
|
|
353
424
|
|
|
354
425
|
if (jarCookies.length > 0) {
|
|
355
426
|
await this.context.addCookies(jarCookies.map(toPWCookie));
|
|
356
427
|
}
|
|
357
428
|
|
|
358
|
-
//
|
|
359
|
-
//
|
|
360
|
-
//
|
|
429
|
+
// Gate actual document requests, including links, forms, script navigation,
|
|
430
|
+
// and the first request of a popup. Method-level waits miss those loads.
|
|
431
|
+
// Redirect hops are part of the original load, not additional reservations.
|
|
361
432
|
const headersCb = this.opts.extraHeaders;
|
|
362
|
-
if (headersCb) {
|
|
433
|
+
if (this.getPaceGate()?.policy.enabled || headersCb || this.opts.userAgent) {
|
|
363
434
|
await this.context.route("**/*", async (route, request) => {
|
|
364
|
-
|
|
435
|
+
let mainDocument = false;
|
|
436
|
+
if (request.isNavigationRequest() && !request.redirectedFrom()) {
|
|
437
|
+
try {
|
|
438
|
+
mainDocument = request.frame() === request.frame().page().mainFrame();
|
|
439
|
+
} catch {
|
|
440
|
+
// A popup's first request can precede creation of its Page/Frame.
|
|
441
|
+
mainDocument = true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (mainDocument) {
|
|
445
|
+
try {
|
|
446
|
+
await this.getPaceGate()?.before(request.url());
|
|
447
|
+
} catch {
|
|
448
|
+
await route.abort("failed");
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const extra = { ...headersCb?.(request.url()) };
|
|
453
|
+
// Popup request headers are already computed before its CDP session is
|
|
454
|
+
// available. Keep negotiated hints consistent on that first request too.
|
|
455
|
+
if (identity) {
|
|
456
|
+
const metadata = identity.userAgentMetadata;
|
|
457
|
+
extra["sec-ch-ua-platform"] = JSON.stringify(metadata.platform);
|
|
458
|
+
extra["sec-ch-ua-mobile"] = "?0";
|
|
459
|
+
extra["sec-ch-ua"] = metadata.brands
|
|
460
|
+
.map(({ brand, version }) => `${JSON.stringify(brand)};v=${JSON.stringify(version)}`)
|
|
461
|
+
.join(", ");
|
|
462
|
+
for (const [header, value] of Object.entries({
|
|
463
|
+
"sec-ch-ua-platform-version": metadata.platformVersion,
|
|
464
|
+
"sec-ch-ua-arch": metadata.architecture,
|
|
465
|
+
"sec-ch-ua-bitness": metadata.bitness,
|
|
466
|
+
}))
|
|
467
|
+
if (header in request.headers()) extra[header] = JSON.stringify(value);
|
|
468
|
+
}
|
|
365
469
|
if (Object.keys(extra).length === 0) return route.continue();
|
|
366
470
|
const headers = { ...request.headers(), ...extra };
|
|
367
471
|
return route.continue({ headers });
|
|
@@ -370,6 +474,7 @@ export class Browser {
|
|
|
370
474
|
|
|
371
475
|
const pages = this.context.pages();
|
|
372
476
|
const firstPage = pages[0] ?? (await this.context.newPage());
|
|
477
|
+
await Promise.all(this.context.pages().map((page) => this.preparePageIdentity(page)));
|
|
373
478
|
for (const page of this.context.pages()) this.trackPage(page, page === firstPage);
|
|
374
479
|
this.context.on("page", (page) => this.trackPage(page, true));
|
|
375
480
|
}
|
|
@@ -491,6 +596,10 @@ export class Browser {
|
|
|
491
596
|
type: "unknown",
|
|
492
597
|
});
|
|
493
598
|
this.attachDiagnosticListeners(page);
|
|
599
|
+
void this.preparePageIdentity(page).catch((error) => {
|
|
600
|
+
if (!page.isClosed())
|
|
601
|
+
this.pageErrors.push({ message: `Browser identity setup failed: ${String(error)}` });
|
|
602
|
+
});
|
|
494
603
|
page.on("domcontentloaded", () => this.recordDocumentNavigation(page));
|
|
495
604
|
page.once("close", () => {
|
|
496
605
|
this.pageRecency.delete(page);
|
|
@@ -501,6 +610,23 @@ export class Browser {
|
|
|
501
610
|
if (makeActive) this.setActivePage(page);
|
|
502
611
|
}
|
|
503
612
|
|
|
613
|
+
private preparePageIdentity(page: Page): Promise<void> {
|
|
614
|
+
if (!this.opts.userAgent || !this.context) return Promise.resolve();
|
|
615
|
+
let ready = this.identityReady.get(page);
|
|
616
|
+
if (!ready) {
|
|
617
|
+
const context = this.context;
|
|
618
|
+
const userAgent = this.opts.userAgent;
|
|
619
|
+
ready = (async () => {
|
|
620
|
+
const identity = browserIdentity(userAgent, context.browser()?.version());
|
|
621
|
+
if (!identity) return;
|
|
622
|
+
const session = await context.newCDPSession(page);
|
|
623
|
+
await session.send("Emulation.setUserAgentOverride", identity);
|
|
624
|
+
})();
|
|
625
|
+
this.identityReady.set(page, ready);
|
|
626
|
+
}
|
|
627
|
+
return ready;
|
|
628
|
+
}
|
|
629
|
+
|
|
504
630
|
private setActivePage(page: Page): void {
|
|
505
631
|
if (page.isClosed()) return;
|
|
506
632
|
this.page = page;
|
|
@@ -569,8 +695,19 @@ export class Browser {
|
|
|
569
695
|
};
|
|
570
696
|
}
|
|
571
697
|
|
|
698
|
+
private paceGate: PaceGate | null | undefined;
|
|
699
|
+
|
|
700
|
+
private getPaceGate(): PaceGate | null {
|
|
701
|
+
if (this.paceGate === undefined) {
|
|
702
|
+
this.paceGate =
|
|
703
|
+
this.opts.pace === undefined ? new PaceGate(pacePolicyFromEnv()) : this.opts.pace;
|
|
704
|
+
}
|
|
705
|
+
return this.paceGate;
|
|
706
|
+
}
|
|
707
|
+
|
|
572
708
|
async navigate(url: string): Promise<NavigateResult> {
|
|
573
709
|
const page = this.currentPage;
|
|
710
|
+
await this.preparePageIdentity(page);
|
|
574
711
|
const response = await page.goto(url, { waitUntil: this.opts.waitUntil ?? "load" });
|
|
575
712
|
return {
|
|
576
713
|
url: page.url(),
|
|
@@ -1381,6 +1518,7 @@ export class Browser {
|
|
|
1381
1518
|
const page = await this.context.newPage();
|
|
1382
1519
|
this.trackPage(page, true);
|
|
1383
1520
|
try {
|
|
1521
|
+
await this.preparePageIdentity(page);
|
|
1384
1522
|
await page.goto(url, { waitUntil: this.opts.waitUntil ?? "load" });
|
|
1385
1523
|
this.sessionRevision++;
|
|
1386
1524
|
return await this.describeTab(page);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Keep Chromium's UA, navigator.platform and client hints in one profile. */
|
|
2
|
+
export function browserIdentity(userAgent: string, browserVersion?: string) {
|
|
3
|
+
const chrome = /(?:Chrome|Chromium)\/(\d+)\.([\d.]+)/.exec(userAgent);
|
|
4
|
+
const windows = userAgent.includes("Windows NT");
|
|
5
|
+
const mac = userAgent.includes("Macintosh");
|
|
6
|
+
// A custom non-Chromium or non-desktop UA is an explicit caller choice.
|
|
7
|
+
if (!chrome || (!windows && !mac)) return undefined;
|
|
8
|
+
const major = chrome[1]!;
|
|
9
|
+
const fullVersion = browserVersion?.startsWith(`${major}.`)
|
|
10
|
+
? browserVersion
|
|
11
|
+
: `${major}.${chrome[2]}`;
|
|
12
|
+
const brand = userAgent.includes("Edg/") ? "Microsoft Edge" : "Google Chrome";
|
|
13
|
+
return {
|
|
14
|
+
userAgent,
|
|
15
|
+
platform: windows ? "Win32" : "MacIntel",
|
|
16
|
+
userAgentMetadata: {
|
|
17
|
+
brands: [
|
|
18
|
+
{ brand: "Chromium", version: major },
|
|
19
|
+
{ brand, version: major },
|
|
20
|
+
],
|
|
21
|
+
fullVersionList: [
|
|
22
|
+
{ brand: "Chromium", version: fullVersion },
|
|
23
|
+
{ brand, version: fullVersion },
|
|
24
|
+
],
|
|
25
|
+
platform: windows ? "Windows" : "macOS",
|
|
26
|
+
platformVersion: windows ? "10.0.0" : "10.15.7",
|
|
27
|
+
architecture: "x86",
|
|
28
|
+
bitness: "64",
|
|
29
|
+
model: "",
|
|
30
|
+
mobile: false,
|
|
31
|
+
wow64: false,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Initial popup documents exist before Playwright can attach a page session. */
|
|
37
|
+
export function installDocumentIdentity(identity: NonNullable<ReturnType<typeof browserIdentity>>) {
|
|
38
|
+
const metadata = identity.userAgentMetadata;
|
|
39
|
+
const basics = { brands: metadata.brands, mobile: metadata.mobile, platform: metadata.platform };
|
|
40
|
+
Object.defineProperty(Navigator.prototype, "platform", {
|
|
41
|
+
configurable: true,
|
|
42
|
+
get: () => identity.platform,
|
|
43
|
+
});
|
|
44
|
+
if (!("userAgentData" in navigator)) return;
|
|
45
|
+
const nativeData = (navigator as Navigator & { userAgentData: object }).userAgentData;
|
|
46
|
+
const data = Object.create(Object.getPrototypeOf(nativeData));
|
|
47
|
+
for (const [name, value] of Object.entries(basics)) {
|
|
48
|
+
Object.defineProperty(data, name, { enumerable: true, get: () => value });
|
|
49
|
+
}
|
|
50
|
+
Object.defineProperty(data, "toJSON", { value: () => ({ ...basics }) });
|
|
51
|
+
Object.defineProperty(data, "getHighEntropyValues", {
|
|
52
|
+
value: async (hints: string[]) => {
|
|
53
|
+
const result: Record<string, unknown> = { ...basics };
|
|
54
|
+
for (const hint of hints) {
|
|
55
|
+
if (Object.hasOwn(metadata, hint)) result[hint] = metadata[hint as keyof typeof metadata];
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(Navigator.prototype, "userAgentData", {
|
|
61
|
+
configurable: true,
|
|
62
|
+
get: () => data,
|
|
63
|
+
});
|
|
64
|
+
}
|
package/src/lib/browser/index.ts
CHANGED
|
@@ -76,7 +76,14 @@ export type {
|
|
|
76
76
|
OverlapResult,
|
|
77
77
|
} from "./geometry.js";
|
|
78
78
|
export { buildLayoutLintCheck } from "./geometry.js";
|
|
79
|
-
export {
|
|
79
|
+
export {
|
|
80
|
+
AUTOMATION_DEFAULT_ARGS_TO_DROP,
|
|
81
|
+
automationDisguiseArgs,
|
|
82
|
+
installedChromeChannel,
|
|
83
|
+
installedChromePath,
|
|
84
|
+
isWSL,
|
|
85
|
+
wslHeadedLaunchArgs,
|
|
86
|
+
} from "./launch-args.js";
|
|
80
87
|
export type {
|
|
81
88
|
OverflowElement,
|
|
82
89
|
OverflowResult,
|
|
@@ -237,6 +244,13 @@ export {
|
|
|
237
244
|
snappedBandRects,
|
|
238
245
|
type VisualAtom,
|
|
239
246
|
} from "./tiling.js";
|
|
247
|
+
export {
|
|
248
|
+
chromeMajorFromExecutable,
|
|
249
|
+
hostFamily,
|
|
250
|
+
normalUserAgent,
|
|
251
|
+
readStoredUserAgent,
|
|
252
|
+
resolveUserAgent,
|
|
253
|
+
} from "./user-agent.js";
|
|
240
254
|
export type {
|
|
241
255
|
CheckVisibilityOptions,
|
|
242
256
|
VisibilityResult,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Chromium launch-arg helpers for environment-specific workarounds.
|
|
@@ -31,3 +31,55 @@ export function isWSL(): boolean {
|
|
|
31
31
|
export function wslHeadedLaunchArgs(): string[] {
|
|
32
32
|
return isWSL() ? ["--disable-gpu"] : [];
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Playwright's bundled browser is "Chrome for Testing", launched with
|
|
37
|
+
* `--enable-automation`. Both are visible to the page: the User-Agent brand
|
|
38
|
+
* list carries "Chrome for Testing" and `navigator.webdriver` reads true, so
|
|
39
|
+
* sign-in flows on sites that screen for automation (X, some banks, some
|
|
40
|
+
* Cloudflare-fronted apps) silently refuse clicks in a headed window that a
|
|
41
|
+
* person is driving. Headed sessions therefore launch the operator's real
|
|
42
|
+
* Google Chrome when one is installed and drop the automation signals.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/** Default Chromium args Playwright adds that a headed session must not carry. */
|
|
46
|
+
export const AUTOMATION_DEFAULT_ARGS_TO_DROP: readonly string[] = ["--enable-automation"];
|
|
47
|
+
|
|
48
|
+
/** Launch flags that stop Blink from reporting the automation state to pages. */
|
|
49
|
+
export function automationDisguiseArgs(): string[] {
|
|
50
|
+
return ["--disable-blink-features=AutomationControlled"];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const CHROME_PATHS: Record<string, string[]> = {
|
|
54
|
+
linux: ["/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/opt/google/chrome/chrome"],
|
|
55
|
+
darwin: ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"],
|
|
56
|
+
win32: [
|
|
57
|
+
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
58
|
+
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* `"chrome"` when a stable Google Chrome install is present at a well-known
|
|
64
|
+
* path for this platform, else `undefined` (use Playwright's bundled
|
|
65
|
+
* Chromium). Playwright resolves the `chrome` channel to the same install, so
|
|
66
|
+
* the check only has to answer whether asking for it can succeed.
|
|
67
|
+
*/
|
|
68
|
+
export function installedChromeChannel(
|
|
69
|
+
platform: NodeJS.Platform = process.platform,
|
|
70
|
+
exists: (path: string) => boolean = existsSync,
|
|
71
|
+
): "chrome" | undefined {
|
|
72
|
+
return installedChromePath(platform, exists) ? "chrome" : undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Filesystem path of the installed stable Google Chrome for this platform, or
|
|
77
|
+
* `undefined`. Used to spawn Chrome with nothing attached for sign-in flows
|
|
78
|
+
* whose verification vendors detect the DevTools protocol itself.
|
|
79
|
+
*/
|
|
80
|
+
export function installedChromePath(
|
|
81
|
+
platform: NodeJS.Platform = process.platform,
|
|
82
|
+
exists: (path: string) => boolean = existsSync,
|
|
83
|
+
): string | undefined {
|
|
84
|
+
return (CHROME_PATHS[platform] ?? []).find((candidate) => exists(candidate));
|
|
85
|
+
}
|