@reticlehq/server 3.7.0 → 3.8.1
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/SKILL.md +59 -68
- package/bin/reticle.js +9 -2
- package/dist/command/cli/adhoc-suite.js +1 -1
- package/dist/command/cli/adhoc-verdict.d.ts +12 -0
- package/dist/command/cli/adhoc-verdict.js +62 -4
- package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
- package/dist/command/cli/answers/gate-hook-message.js +5 -1
- package/dist/command/cli/auth/cloud-session.d.ts +11 -19
- package/dist/command/cli/auth/cloud-session.js +23 -0
- package/dist/command/cli/auth/device-flow.d.ts +3 -37
- package/dist/command/cli/cli-doctor.js +66 -21
- package/dist/command/cli/cli-flow-commands.js +53 -11
- package/dist/command/cli/cli-kill.d.ts +8 -0
- package/dist/command/cli/cli-kill.js +43 -3
- package/dist/command/cli/cli-parse-feedback.d.ts +4 -0
- package/dist/command/cli/cli-parse-feedback.js +13 -1
- package/dist/command/cli/cli-parse-grammar.d.ts +1 -2
- package/dist/command/cli/cli-parse-grammar.js +1 -6
- package/dist/command/cli/cli-parse-serve.d.ts +24 -0
- package/dist/command/cli/cli-parse-serve.js +124 -0
- package/dist/command/cli/cli-parse.d.ts +12 -11
- package/dist/command/cli/cli-parse.js +100 -123
- package/dist/command/cli/cli-usage.d.ts +0 -11
- package/dist/command/cli/cli-usage.js +43 -16
- package/dist/command/cli/cli-verify.d.ts +1 -1
- package/dist/command/cli/cli-verify.js +29 -14
- package/dist/command/cli/cloud-cli.js +27 -9
- package/dist/command/cli/cloud-kit.js +3 -3
- package/dist/command/cli/cloud-link.d.ts +1 -20
- package/dist/command/cli/cloud-link.js +2 -0
- package/dist/command/cli/cloud-login.d.ts +6 -2
- package/dist/command/cli/cloud-login.js +13 -41
- package/dist/command/cli/config/checkout.d.ts +10 -0
- package/dist/command/cli/config/checkout.js +78 -0
- package/dist/command/cli/doctor/doctor-agent-link.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-agent-link.js +28 -0
- package/dist/command/cli/doctor/doctor-coverage-line.js +1 -7
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/drive/drive-attach.d.ts +5 -0
- package/dist/command/cli/drive/drive-attach.js +21 -1
- package/dist/command/cli/hook/codex-transcript.d.ts +20 -0
- package/dist/command/cli/hook/codex-transcript.js +224 -0
- package/dist/command/cli/hook/episode-structure.d.ts +24 -0
- package/dist/command/cli/hook/episode-structure.js +138 -0
- package/dist/command/cli/hook/hook-command.d.ts +9 -0
- package/dist/command/cli/hook/hook-command.js +211 -0
- package/dist/command/cli/hook/hook-fast.d.ts +31 -0
- package/dist/command/cli/hook/hook-fast.js +136 -0
- package/dist/command/cli/hook/prompt-hook.d.ts +30 -0
- package/dist/command/cli/hook/prompt-hook.js +199 -0
- package/dist/command/cli/hook/tool-hook.d.ts +21 -0
- package/dist/command/cli/hook/tool-hook.js +259 -0
- package/dist/command/cli/launch/cli-launch.d.ts +2 -0
- package/dist/command/cli/launch/cli-launch.js +22 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +28 -2
- package/dist/command/cli/mcp-command.d.ts +9 -0
- package/dist/command/cli/mcp-command.js +127 -41
- package/dist/command/cli/open-navigate.d.ts +43 -0
- package/dist/command/cli/open-navigate.js +85 -0
- package/dist/command/cli/ports/port-holder.d.ts +16 -4
- package/dist/command/cli/ports/port-holder.js +58 -12
- package/dist/command/cli/ports/resolve/cli-port.js +18 -2
- package/dist/command/cli/project-name.d.ts +1 -7
- package/dist/command/cli/project-name.js +1 -0
- package/dist/command/cli/report-command.d.ts +8 -2
- package/dist/command/cli/report-command.js +95 -18
- package/dist/command/cli/status-fields.d.ts +9 -2
- package/dist/command/cli/status-fields.js +148 -4
- package/dist/command/cli/try-command.d.ts +3 -4
- package/dist/command/cli/try-command.js +6 -11
- package/dist/command/cli/verify-results.d.ts +4 -1
- package/dist/command/cli/verify-results.js +3 -1
- package/dist/command/cli.d.ts +1 -7
- package/dist/command/cli.js +79 -160
- package/dist/command/daemon/daemon-resilience.d.ts +6 -1
- package/dist/command/daemon/daemon-resilience.js +31 -18
- package/dist/command/daemon/daemon.d.ts +11 -0
- package/dist/command/daemon/daemon.js +70 -2
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +2 -0
- package/dist/command/daemon/lifetime/daemon-usefulness.js +13 -2
- package/dist/command/daemon/wake-decision.js +4 -2
- package/dist/command/daemon/win32-escape.d.ts +32 -0
- package/dist/command/daemon/win32-escape.js +119 -0
- package/dist/command/setup/agent-configs.d.ts +9 -0
- package/dist/command/setup/agent-configs.js +9 -0
- package/dist/command/setup/auto-approve.d.ts +10 -3
- package/dist/command/setup/auto-approve.js +51 -3
- package/dist/command/setup/bringup/dev-server-files.d.ts +1 -9
- package/dist/command/setup/bringup/dev-server-wait.js +5 -1
- package/dist/command/setup/bringup/ensure-daemon.js +4 -7
- package/dist/command/setup/init/init-host.js +6 -0
- package/dist/command/setup/init/init-port.js +5 -4
- package/dist/command/setup/init/init-runtime.js +7 -1
- package/dist/command/setup/node-effects.d.ts +8 -0
- package/dist/command/setup/node-effects.js +36 -4
- package/dist/command/setup/probe/dev-port.d.ts +26 -0
- package/dist/command/setup/probe/dev-port.js +117 -0
- package/dist/command/setup/probe/page-probe.d.ts +1 -1
- package/dist/command/setup/probe/page-probe.js +11 -3
- package/dist/command/setup/remaining-steps.d.ts +0 -7
- package/dist/command/setup/remaining-steps.js +3 -2
- package/dist/command/setup/run-setup.d.ts +11 -0
- package/dist/command/setup/run-setup.js +33 -5
- package/dist/command/setup/setup-command.d.ts +2 -0
- package/dist/command/setup/setup-command.js +22 -4
- package/dist/command/update/updater.d.ts +1 -6
- package/dist/command/update/updater.js +4 -5
- package/dist/command/update/version-seen.d.ts +24 -0
- package/dist/command/update/version-seen.js +65 -0
- package/dist/command/version/peer-announce.d.ts +9 -1
- package/dist/command/version/peer-announce.js +25 -2
- package/dist/command/version/sdk-fix.d.ts +5 -6
- package/dist/command/version/sdk-fix.js +17 -32
- package/dist/command/version/version-skew.d.ts +16 -1
- package/dist/command/version/version-skew.js +84 -10
- package/dist/features/crawl/crawl-tools.js +2 -2
- package/dist/features/crawl/crawl.js +2 -0
- package/dist/features/exhaust/ledger.d.ts +15 -96
- package/dist/features/exhaust/ledger.js +3 -3
- package/dist/features/harness/drive-runs.d.ts +28 -4
- package/dist/features/harness/drive-runs.js +99 -7
- package/dist/features/harness/drivers.d.ts +1 -1
- package/dist/features/harness/drivers.js +1 -1
- package/dist/features/harness/harness.d.ts +5 -0
- package/dist/features/harness/harness.js +24 -12
- package/dist/features/harness/platform/platform-drives.d.ts +7 -1
- package/dist/features/harness/platform/platform-drives.js +12 -7
- package/dist/features/harness/platform/refusal.d.ts +1 -1
- package/dist/features/harness/platform/refusal.js +4 -4
- package/dist/features/harness/platform/remote-drive.js +70 -28
- package/dist/features/harness/platform/server-driver.js +22 -1
- package/dist/features/harness/platform-config.d.ts +1 -10
- package/dist/features/harness/platform-config.js +0 -20
- package/dist/features/license/license.d.ts +2 -14
- package/dist/features/visual/step-shots.d.ts +33 -1
- package/dist/features/visual/step-shots.js +155 -1
- package/dist/features/visual/visual-tools.js +12 -4
- package/dist/hooks/coding-agents.d.ts +6 -0
- package/dist/hooks/coding-agents.js +24 -3
- package/dist/hooks/episode-stamp.d.ts +32 -0
- package/dist/hooks/episode-stamp.js +61 -0
- package/dist/hooks/hook-commands.js +19 -9
- package/dist/index.d.ts +5 -1
- package/dist/index.js +66 -24
- package/dist/judgement/app-graph/journal-steps.d.ts +8 -0
- package/dist/judgement/app-graph/journal-steps.js +92 -0
- package/dist/judgement/capsule/capsule-store.d.ts +2 -39
- package/dist/judgement/domain/domain-model.js +1 -10
- package/dist/judgement/domain/domain-tools.js +4 -2
- package/dist/judgement/runs/artifact/run-store.d.ts +28 -8
- package/dist/judgement/runs/artifact/run-store.js +153 -28
- package/dist/judgement/runs/default-run-id.d.ts +3 -1
- package/dist/judgement/runs/default-run-id.js +15 -3
- package/dist/judgement/runs/drive-run.d.ts +3 -3
- package/dist/judgement/runs/drive-run.js +11 -7
- package/dist/judgement/runs/run-ledger.d.ts +21 -0
- package/dist/judgement/runs/run-ledger.js +143 -0
- package/dist/judgement/runs/runner-port.d.ts +1 -1
- package/dist/judgement/runs/runner-port.js +8 -2
- package/dist/judgement/runs/verification-sync.d.ts +4 -3
- package/dist/judgement/runs/verification-sync.js +27 -5
- package/dist/language/flows/affected-tools.js +2 -1
- package/dist/language/flows/annotate-notes/annotate.js +8 -3
- package/dist/language/flows/annotate-tools.js +1 -1
- package/dist/language/flows/change/affected.d.ts +8 -0
- package/dist/language/flows/change/affected.js +20 -7
- package/dist/language/flows/decision.js +19 -4
- package/dist/language/flows/fields/flow-secret-field.d.ts +9 -0
- package/dist/language/flows/fields/flow-secret-field.js +67 -5
- package/dist/language/flows/flow-anchor.d.ts +5 -0
- package/dist/language/flows/flow-anchor.js +4 -0
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +9 -0
- package/dist/language/flows/flow-expect-grammar.js +15 -10
- package/dist/language/flows/flow-intent.js +6 -1
- package/dist/language/flows/flow-journey.d.ts +8 -0
- package/dist/language/flows/flow-journey.js +13 -0
- package/dist/language/flows/flow-lease.d.ts +27 -0
- package/dist/language/flows/flow-lease.js +59 -0
- package/dist/language/flows/flow-replay-run.d.ts +1 -87
- package/dist/language/flows/flow-replay-run.js +111 -310
- package/dist/language/flows/flow-replay.js +21 -19
- package/dist/language/flows/flow-replayable.d.ts +38 -0
- package/dist/language/flows/flow-replayable.js +179 -0
- package/dist/language/flows/flow-start-path.d.ts +108 -0
- package/dist/language/flows/flow-start-path.js +313 -0
- package/dist/language/flows/flow-step-runners.d.ts +23 -2
- package/dist/language/flows/flow-step-runners.js +191 -32
- package/dist/language/flows/flow-store-for-session.d.ts +8 -1
- package/dist/language/flows/flow-store-for-session.js +6 -0
- package/dist/language/flows/flow-tools.d.ts +2 -23
- package/dist/language/flows/flow-tools.js +63 -113
- package/dist/language/flows/flows.d.ts +4 -6
- package/dist/language/flows/flows.js +90 -15
- package/dist/language/flows/heal-run.d.ts +10 -4
- package/dist/language/flows/heal-run.js +26 -9
- package/dist/language/flows/recording/flake.d.ts +3 -27
- package/dist/language/flows/recording/flake.js +1 -1
- package/dist/language/flows/recording/recording-backtrack.js +1 -4
- package/dist/language/flows/recording/tape/recordings.d.ts +11 -1
- package/dist/language/flows/recording/tape/recordings.js +9 -3
- package/dist/language/flows/replay.d.ts +25 -5
- package/dist/language/flows/replay.js +83 -16
- package/dist/language/flows/stores/assertion-tiers-store.js +1 -1
- package/dist/language/flows/suite/coverage.d.ts +9 -0
- package/dist/language/flows/suite/coverage.js +12 -1
- package/dist/language/flows/suite-selection.d.ts +25 -0
- package/dist/language/flows/suite-selection.js +41 -9
- package/dist/machine/git-tracks.d.ts +6 -0
- package/dist/machine/git-tracks.js +36 -0
- package/dist/memory/cloud/background-sync.d.ts +25 -0
- package/dist/memory/cloud/background-sync.js +79 -0
- package/dist/memory/cloud/cloud-config.d.ts +12 -1
- package/dist/memory/cloud/cloud-config.js +39 -13
- package/dist/memory/cloud/cloud-sync.d.ts +3 -24
- package/dist/memory/cloud/cloud-sync.js +5 -2
- package/dist/memory/cloud/harness-config.d.ts +0 -7
- package/dist/memory/cloud/memory-scope.d.ts +19 -0
- package/dist/memory/cloud/memory-scope.js +48 -0
- package/dist/memory/cloud/sync-answer.d.ts +43 -0
- package/dist/memory/cloud/sync-answer.js +42 -0
- package/dist/memory/cloud/sync-cycle.d.ts +40 -10
- package/dist/memory/cloud/sync-cycle.js +97 -144
- package/dist/memory/cloud/sync-daemon.d.ts +50 -3
- package/dist/memory/cloud/sync-daemon.js +473 -83
- package/dist/memory/cloud/sync-describe.d.ts +28 -0
- package/dist/memory/cloud/sync-describe.js +92 -0
- package/dist/memory/cloud/sync-disk.d.ts +9 -1
- package/dist/memory/cloud/sync-disk.js +137 -33
- package/dist/memory/cloud/sync-flow-versions.d.ts +26 -0
- package/dist/memory/cloud/sync-flow-versions.js +42 -0
- package/dist/memory/impact/impact-store.js +32 -15
- package/dist/memory/intent/intent-shard.d.ts +41 -266
- package/dist/memory/intent/intent-tools.js +20 -61
- package/dist/memory/intent/record-request.d.ts +27 -0
- package/dist/memory/intent/record-request.js +88 -0
- package/dist/memory/intent/settled-words.js +9 -1
- package/dist/memory/journal/drive-flow.d.ts +3 -0
- package/dist/memory/journal/drive-flow.js +11 -41
- package/dist/memory/journal/envelope-store.js +1 -1
- package/dist/memory/journal/envelope.d.ts +6 -154
- package/dist/memory/journal/episode-log.d.ts +107 -0
- package/dist/memory/journal/episode-log.js +302 -0
- package/dist/memory/journal/journal-recorder.d.ts +7 -0
- package/dist/memory/journal/journal-recorder.js +22 -9
- package/dist/memory/journal/on-disk/ambient-file.d.ts +1 -7
- package/dist/memory/journal/on-disk/ambient-file.js +1 -1
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +24 -3
- package/dist/memory/journal/on-disk/workspace-gitignore.js +38 -7
- package/dist/memory/journal/on-disk/workspace-tiers.js +7 -0
- package/dist/memory/journal/self-observation.js +3 -1
- package/dist/memory/journal/session-end.js +23 -9
- package/dist/memory/journal/session-journal.js +39 -20
- package/dist/memory/project/artifact-root-resolver.d.ts +14 -1
- package/dist/memory/project/artifact-root-resolver.js +68 -23
- package/dist/memory/project/artifact-root.d.ts +17 -3
- package/dist/memory/project/artifact-root.js +47 -9
- package/dist/memory/project/fs/fs-port.d.ts +5 -0
- package/dist/memory/project/fs/fs-port.js +13 -0
- package/dist/memory/project/learned-routes.d.ts +3 -4
- package/dist/memory/project/learned-routes.js +3 -11
- package/dist/memory/project/link-directory.js +5 -2
- package/dist/memory/project/root-claims.d.ts +3 -1
- package/dist/memory/project/root-claims.js +41 -7
- package/dist/memory/project/session-root.d.ts +1 -1
- package/dist/memory/project/session-root.js +1 -15
- package/dist/memory/project/sync-status.js +5 -6
- package/dist/memory/recall/memory-tools.js +2 -2
- package/dist/memory/recall/platform-memory.d.ts +3 -1
- package/dist/memory/recall/platform-memory.js +9 -1
- package/dist/memory/recall/project-memory.js +15 -19
- package/dist/portal/bridge/auth-failure-reason.d.ts +13 -0
- package/dist/portal/bridge/auth-failure-reason.js +19 -0
- package/dist/portal/bridge/bridge.d.ts +19 -4
- package/dist/portal/bridge/bridge.js +150 -177
- package/dist/portal/bridge/harness-request.d.ts +15 -0
- package/dist/portal/bridge/harness-request.js +19 -0
- package/dist/portal/bridge/origin/origin-follow.d.ts +46 -0
- package/dist/portal/bridge/origin/origin-follow.js +110 -0
- package/dist/portal/bridge/pairing-token.d.ts +6 -0
- package/dist/portal/bridge/pairing-token.js +47 -16
- package/dist/portal/bridge/protocol-skew.d.ts +27 -2
- package/dist/portal/bridge/protocol-skew.js +66 -2
- package/dist/portal/bridge/session-nonces.d.ts +46 -0
- package/dist/portal/bridge/session-nonces.js +72 -0
- package/dist/portal/input/viewport-tools.d.ts +31 -0
- package/dist/portal/input/viewport-tools.js +106 -15
- package/dist/portal/pool/bounded-close.d.ts +23 -0
- package/dist/portal/pool/bounded-close.js +38 -0
- package/dist/portal/pool/browser-pool.d.ts +44 -39
- package/dist/portal/pool/browser-pool.js +247 -95
- package/dist/portal/pool/context-permissions.d.ts +9 -1
- package/dist/portal/pool/context-permissions.js +26 -0
- package/dist/portal/pool/lease-emulation.d.ts +37 -0
- package/dist/portal/pool/lease-emulation.js +29 -0
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +74 -7
- package/dist/portal/pool/pool-contract.d.ts +23 -1
- package/dist/portal/pool/pool-lease.d.ts +128 -0
- package/dist/portal/pool/pool-lease.js +49 -0
- package/dist/portal/pool/zero-install.d.ts +8 -1
- package/dist/portal/pool/zero-install.js +24 -2
- package/dist/portal/production/production-activation.d.ts +136 -0
- package/dist/portal/production/production-activation.js +173 -0
- package/dist/portal/production/production-drive.d.ts +52 -0
- package/dist/portal/production/production-drive.js +301 -0
- package/dist/portal/production/production-hello.d.ts +23 -0
- package/dist/portal/production/production-hello.js +23 -0
- package/dist/portal/realm/web-realm.js +42 -15
- package/dist/portal/session/control-envelope.d.ts +1 -1
- package/dist/portal/session/dev-server/served-document.d.ts +0 -5
- package/dist/portal/session/dev-server/served-document.js +6 -8
- package/dist/portal/session/facts/handshake-facts.d.ts +8 -1
- package/dist/portal/session/first-run-wiring.d.ts +3 -36
- package/dist/portal/session/first-run-wiring.js +10 -103
- package/dist/portal/session/live-control-tools.js +61 -19
- package/dist/portal/session/no-session-next-action.d.ts +11 -0
- package/dist/portal/session/no-session-next-action.js +40 -15
- package/dist/portal/session/no-session-watch.d.ts +7 -0
- package/dist/portal/session/no-session-watch.js +67 -23
- package/dist/portal/session/output-budget.d.ts +10 -0
- package/dist/portal/session/output-budget.js +16 -0
- package/dist/portal/session/presence/session-lease.d.ts +2 -0
- package/dist/portal/session/presence/session-lease.js +8 -0
- package/dist/portal/session/recorded-gaps.d.ts +15 -11
- package/dist/portal/session/recorded-gaps.js +39 -18
- package/dist/portal/session/serving-directory.d.ts +24 -2
- package/dist/portal/session/serving-directory.js +93 -5
- package/dist/portal/session/session-info.d.ts +26 -0
- package/dist/portal/session/session-info.js +20 -0
- package/dist/portal/session/session-manager.d.ts +35 -3
- package/dist/portal/session/session-manager.js +69 -14
- package/dist/portal/session/session-successor.js +1 -9
- package/dist/portal/session/session-tools.js +22 -4
- package/dist/portal/session/session.d.ts +10 -10
- package/dist/portal/session/session.js +37 -29
- package/dist/portal/session/zero-install-first-call.d.ts +7 -2
- package/dist/portal/session/zero-install-first-call.js +43 -20
- package/dist/start-wiring.d.ts +2 -2
- package/dist/start-wiring.js +17 -3
- package/dist/status-payload.d.ts +11 -1
- package/dist/status-payload.js +7 -1
- package/dist/surface/agent-presence-grace.d.ts +11 -0
- package/dist/surface/agent-presence-grace.js +34 -0
- package/dist/surface/http-server.d.ts +12 -1
- package/dist/surface/http-server.js +64 -15
- package/dist/surface/mcp/faults/mcp-outage.d.ts +12 -2
- package/dist/surface/mcp/faults/mcp-outage.js +19 -1
- package/dist/surface/mcp/mcp-proxy.d.ts +17 -74
- package/dist/surface/mcp/mcp-proxy.js +194 -267
- package/dist/surface/mcp/mcp.d.ts +20 -3
- package/dist/surface/mcp/mcp.js +88 -25
- package/dist/surface/mcp/proxy/drive-calls.d.ts +44 -0
- package/dist/surface/mcp/proxy/drive-calls.js +114 -5
- package/dist/surface/mcp/proxy/proxy-boot.d.ts +11 -0
- package/dist/surface/mcp/proxy/proxy-boot.js +1 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +6 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +14 -1
- package/dist/surface/mcp/proxy/proxy-handshake.d.ts +34 -2
- package/dist/surface/mcp/proxy/proxy-handshake.js +76 -8
- package/dist/surface/mcp/server-instructions.js +35 -16
- package/dist/surface/mcp/shared-params.d.ts +8 -0
- package/dist/surface/mcp/shared-params.js +15 -1
- package/dist/surface/mcp/sse-frame-parser.d.ts +11 -0
- package/dist/surface/mcp/sse-frame-parser.js +18 -0
- package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
- package/dist/surface/mcp/surface-vocabulary.js +13 -0
- package/dist/surface/mcp/tool-catalog-cache.d.ts +2 -0
- package/dist/surface/mcp/tool-catalog-cache.js +14 -4
- package/dist/surface/tools/account-tool.d.ts +4 -3
- package/dist/surface/tools/account-tool.js +20 -31
- package/dist/surface/tools/act/act-danger.d.ts +2 -0
- package/dist/surface/tools/act/act-danger.js +41 -14
- package/dist/surface/tools/act/act-observation.d.ts +11 -0
- package/dist/surface/tools/act/act-observation.js +33 -0
- package/dist/surface/tools/act/act-preflight.js +10 -3
- package/dist/surface/tools/act/act-sequence-retry.js +2 -1
- package/dist/surface/tools/act/act-target.js +19 -5
- package/dist/surface/tools/act/durable.js +1 -1
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +9 -7
- package/dist/surface/tools/act/navigation/navigate-arrival.js +11 -14
- package/dist/surface/tools/act/unsettled-find.d.ts +36 -0
- package/dist/surface/tools/act/unsettled-find.js +71 -0
- package/dist/surface/tools/act-sequence-tool.js +53 -24
- package/dist/surface/tools/act-tools.d.ts +2 -3
- package/dist/surface/tools/act-tools.js +49 -47
- package/dist/surface/tools/agent/compact-envelope.d.ts +2 -2
- package/dist/surface/tools/agent/for-the-agent.js +41 -3
- package/dist/surface/tools/assert/assert-grade.js +8 -3
- package/dist/surface/tools/assert/assert-verdict.js +4 -2
- package/dist/surface/tools/browser-tools.js +7 -4
- package/dist/surface/tools/chat-drives.js +50 -25
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.d.ts +8 -0
- package/dist/surface/tools/dynamic-tools.js +89 -34
- package/dist/surface/tools/empty-session-list.d.ts +18 -25
- package/dist/surface/tools/empty-session-list.js +67 -38
- package/dist/surface/tools/error-recovery.d.ts +17 -7
- package/dist/surface/tools/error-recovery.js +53 -20
- package/dist/surface/tools/explore-tools.d.ts +10 -0
- package/dist/surface/tools/explore-tools.js +72 -19
- package/dist/surface/tools/feedback-tools.js +23 -2
- package/dist/surface/tools/gap-novelty.d.ts +6 -0
- package/dist/surface/tools/gap-novelty.js +9 -0
- package/dist/surface/tools/gaps/capability-absences.js +4 -3
- package/dist/surface/tools/harness-explore.d.ts +1 -1
- package/dist/surface/tools/harness-explore.js +6 -4
- package/dist/surface/tools/harness-gate.d.ts +43 -21
- package/dist/surface/tools/harness-gate.js +55 -81
- package/dist/surface/tools/harness-script.d.ts +2 -0
- package/dist/surface/tools/harness-script.js +11 -6
- package/dist/surface/tools/harness-toolset.js +3 -4
- package/dist/surface/tools/invoke-tool.d.ts +0 -8
- package/dist/surface/tools/invoke-tool.js +164 -178
- package/dist/surface/tools/lease-hint.d.ts +9 -0
- package/dist/surface/tools/lease-hint.js +22 -3
- package/dist/surface/tools/lease-permissions.d.ts +1 -1
- package/dist/surface/tools/lease-readiness.d.ts +55 -0
- package/dist/surface/tools/lease-readiness.js +150 -2
- package/dist/surface/tools/lease-tools.d.ts +3 -75
- package/dist/surface/tools/lease-tools.js +124 -188
- package/dist/surface/tools/map-tools.d.ts +20 -0
- package/dist/surface/tools/map-tools.js +145 -0
- package/dist/surface/tools/merge-tools.js +11 -7
- package/dist/surface/tools/next-step.js +6 -3
- package/dist/surface/tools/observe-tools.js +46 -35
- package/dist/surface/tools/platform-moment.d.ts +3 -12
- package/dist/surface/tools/platform-moment.js +12 -24
- package/dist/surface/tools/read/complete-snapshot.d.ts +7 -1
- package/dist/surface/tools/read/complete-snapshot.js +13 -2
- package/dist/surface/tools/read/query-paginate.js +6 -0
- package/dist/surface/tools/read/query-shape.d.ts +9 -0
- package/dist/surface/tools/read/query-shape.js +15 -0
- package/dist/surface/tools/read/snapshot-unmounted-note.d.ts +1 -0
- package/dist/surface/tools/read/snapshot-unmounted-note.js +73 -0
- package/dist/surface/tools/read-tools.js +5 -5
- package/dist/surface/tools/real-input-attempt.d.ts +11 -0
- package/dist/surface/tools/real-input-attempt.js +206 -38
- package/dist/surface/tools/reconcile-tools.js +21 -8
- package/dist/surface/tools/reticle-result.d.ts +3 -0
- package/dist/surface/tools/reticle-result.js +8 -1
- package/dist/surface/tools/run-report.d.ts +22 -0
- package/dist/surface/tools/run-report.js +141 -0
- package/dist/surface/tools/session-ports.d.ts +57 -0
- package/dist/surface/tools/session-ports.js +102 -0
- package/dist/surface/tools/tool-kit.d.ts +58 -4
- package/dist/surface/tools/tool-kit.js +140 -6
- package/dist/surface/tools/tool-surface.d.ts +2 -0
- package/dist/surface/tools/tool-surface.js +45 -0
- package/dist/surface/tools/tools.js +49 -87
- package/dist/telemetry/automation-hint.d.ts +5 -0
- package/dist/telemetry/automation-hint.js +7 -0
- package/dist/telemetry/cli-telemetry.d.ts +22 -0
- package/dist/telemetry/cli-telemetry.js +80 -11
- package/dist/telemetry/daemon-telemetry.d.ts +3 -1
- package/dist/telemetry/daemon-telemetry.js +9 -2
- package/dist/telemetry/feedback-cli.d.ts +1 -1
- package/dist/telemetry/feedback-cli.js +8 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/feedback.js +51 -4
- package/dist/telemetry/harness-run.d.ts +3 -3
- package/dist/telemetry/harness-run.js +18 -7
- package/dist/telemetry/hud-events.d.ts +1 -1
- package/dist/telemetry/hud-metrics.d.ts +6 -3
- package/dist/telemetry/hud-metrics.js +20 -8
- package/dist/telemetry/install-source.js +1 -1
- package/dist/telemetry/mcp-connection.d.ts +1 -1
- package/dist/telemetry/mcp-connection.js +21 -2
- package/dist/telemetry/onboarding-firsts.d.ts +6 -1
- package/dist/telemetry/onboarding-firsts.js +41 -1
- package/dist/telemetry/proxy-telemetry.d.ts +42 -0
- package/dist/telemetry/proxy-telemetry.js +70 -1
- package/dist/telemetry/session-metrics.d.ts +1 -3
- package/dist/telemetry/session-metrics.js +16 -12
- package/dist/telemetry/telemetry.d.ts +9 -1
- package/dist/telemetry/telemetry.js +16 -1
- package/dist/telemetry/verification-of.d.ts +3 -1
- package/dist/telemetry/verification-of.js +7 -1
- package/dist/wire-journal.js +12 -2
- package/docs/README.md +4 -2
- package/docs/actions.mdx +7 -6
- package/docs/adapters.md +2 -1
- package/docs/agent-cheatsheet.md +19 -5
- package/docs/architecture.md +13 -10
- package/docs/autodrive.md +6 -5
- package/docs/benchmarks.md +2 -2
- package/docs/capabilities.mdx +1 -1
- package/docs/cli/affected.mdx +1 -1
- package/docs/cli/cloud.mdx +6 -6
- package/docs/cli/doctor.mdx +3 -1
- package/docs/cli/feedback.mdx +11 -7
- package/docs/cli/gate.mdx +1 -1
- package/docs/cli/help.mdx +1 -1
- package/docs/cli/init.mdx +20 -16
- package/docs/cli/mcp.mdx +5 -2
- package/docs/cli/open.mdx +5 -2
- package/docs/cli/report.mdx +1 -1
- package/docs/cli/restart.mdx +1 -1
- package/docs/cli/rollback.mdx +4 -4
- package/docs/cli/serve.mdx +7 -5
- package/docs/cli/setup.mdx +1 -1
- package/docs/cli/status.mdx +6 -2
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/telemetry.mdx +5 -1
- package/docs/cli/try.mdx +8 -8
- package/docs/cli/update.mdx +8 -5
- package/docs/cli/verify.mdx +12 -5
- package/docs/cli/version.mdx +1 -1
- package/docs/cli.mdx +10 -6
- package/docs/debugging.md +1 -1
- package/docs/deploy-checks.md +8 -6
- package/docs/desktop.mdx +14 -18
- package/docs/enterprise.md +6 -6
- package/docs/environment.mdx +5 -3
- package/docs/faq.mdx +9 -6
- package/docs/first-drive.md +19 -9
- package/docs/flows.md +3 -4
- package/docs/for-agents.md +1 -1
- package/docs/frameworks.mdx +34 -11
- package/docs/getting-started.md +46 -29
- package/docs/http-transport.md +2 -2
- package/docs/human-control.md +8 -8
- package/docs/index.mdx +2 -2
- package/docs/install-agentic.mdx +18 -19
- package/docs/install-manual.mdx +8 -4
- package/docs/integration-patterns.md +5 -5
- package/docs/multi-agent-testing.md +7 -5
- package/docs/packages/browser.mdx +34 -2
- package/docs/packages/core.mdx +2 -1
- package/docs/packages/electron.mdx +15 -3
- package/docs/packages/eslint-plugin.mdx +1 -1
- package/docs/packages/next.mdx +17 -5
- package/docs/packages/react.mdx +8 -1
- package/docs/packages/server.mdx +3 -3
- package/docs/packages/tauri.mdx +3 -3
- package/docs/packages/vite-plugin.mdx +1 -1
- package/docs/packages.mdx +19 -6
- package/docs/platform-integration.md +12 -4
- package/docs/predicates.mdx +18 -7
- package/docs/production-mode.md +108 -0
- package/docs/prompt-capture.md +99 -0
- package/docs/quickstart.mdx +14 -12
- package/docs/recipes.mdx +9 -8
- package/docs/required-checks.md +3 -2
- package/docs/skill-file.mdx +4 -3
- package/docs/skill-setup.md +2 -2
- package/docs/skill.md +62 -70
- package/docs/state-files.md +1 -0
- package/docs/state-management.mdx +11 -9
- package/docs/system-map.md +14 -13
- package/docs/telemetry.md +32 -24
- package/docs/testing.md +9 -5
- package/docs/tools/act-and-wait.mdx +17 -13
- package/docs/tools/act-sequence.mdx +40 -66
- package/docs/tools/act.mdx +7 -5
- package/docs/tools/assert.mdx +1 -1
- package/docs/tools/look.mdx +1 -1
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/network.mdx +13 -11
- package/docs/tools/overview.mdx +3 -3
- package/docs/tools/query.mdx +3 -3
- package/docs/tools/session-and-feedback.mdx +9 -6
- package/docs/tools/sessions.mdx +7 -0
- package/docs/tools/snapshot.mdx +9 -7
- package/docs/tools/tools-and-run.mdx +11 -10
- package/docs/tools/verify.mdx +1 -1
- package/docs/troubleshooting.mdx +21 -9
- package/docs/usage.md +58 -43
- package/docs/use-cases.mdx +2 -2
- package/docs/vs-chrome-devtools-mcp.mdx +5 -5
- package/docs/vs-playwright-mcp.mdx +5 -4
- package/docs/what-is-recorded.md +12 -7
- package/docs/what-is-reticle.mdx +2 -2
- package/docs/why-reticle.mdx +1 -1
- package/package.json +9 -12
- package/dist/command/dev/stale-issue-guard.d.ts +0 -34
- package/dist/command/dev/stale-issue-guard.js +0 -69
|
@@ -9,28 +9,16 @@
|
|
|
9
9
|
* The browser is injected via a `Launcher` so all lifecycle logic is testable without real Chromium;
|
|
10
10
|
* the thin Playwright adapter that satisfies these interfaces lives separately.
|
|
11
11
|
*/
|
|
12
|
-
import { unreachableUrlIn } from '@reticlehq/core';
|
|
12
|
+
import { LEASE_CLOSE_TIMEOUT_MS, unreachableUrlIn } from '@reticlehq/core';
|
|
13
|
+
import { log } from '../../log.js';
|
|
14
|
+
import { closeLeaseTab, LEASE_CLOSE_TIMED_OUT, settledWithin } from './bounded-close.js';
|
|
13
15
|
import { seedStorageInto, targetOriginOf } from './storage-seed.js';
|
|
14
16
|
import { installLeaseMarker, leaseMarkerOf } from './lease-marker-stamp.js';
|
|
15
|
-
import { grantLeasePermissions,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* How many reaped lease ids to remember. Only the most recent departure is ever asked about, so
|
|
22
|
-
* this is slack for a burst sweep, not a history.
|
|
23
|
-
*/
|
|
24
|
-
const MAX_REMEMBERED_REAPED_LEASES = 64;
|
|
25
|
-
/** What a headed lease is refused with where no window can be opened. */
|
|
26
|
-
export const NO_HEADED_BROWSER = 'no headed browser can be opened here';
|
|
27
|
-
/**
|
|
28
|
-
* What an acquire that asked for an init script is refused with when the page cannot run one.
|
|
29
|
-
*
|
|
30
|
-
* A refusal rather than a lease: the caller asked for a stub of the bindings a desktop shell
|
|
31
|
-
* injects, and a tab opened without it would throw in the app's view and read as a broken app.
|
|
32
|
-
*/
|
|
33
|
-
const INIT_SCRIPT_UNSUPPORTED = 'Init script failed: this page does not support init scripts, so the requested stub would not be there';
|
|
17
|
+
import { grantLeasePermissions, readNotificationPermission } from './context-permissions.js';
|
|
18
|
+
import { readViewport } from './lease-emulation.js';
|
|
19
|
+
import { NotHtmlDocumentError, READER_NOT_HTML } from './zero-install.js';
|
|
20
|
+
import { DEFAULT_LEASE_TTL_MS, DEFAULT_NAV_TIMEOUT_MS, INIT_SCRIPT_UNSUPPORTED, MAX_REMEMBERED_REAPED_LEASES, NO_HEADED_BROWSER, ORPHANED_LEASE_GRACE_MS, pageLoading, PoolAtCapacityError, } from './pool-lease.js';
|
|
21
|
+
export { DEFAULT_LEASE_TTL_MS, NO_HEADED_BROWSER, ORPHANED_LEASE_GRACE_MS, PoolAtCapacityError, } from './pool-lease.js';
|
|
34
22
|
/**
|
|
35
23
|
* Owns a headless browser (and, when asked, a headed one) and leases isolated contexts out of them,
|
|
36
24
|
* capped at `maxContexts` across both.
|
|
@@ -74,8 +62,9 @@ export class BrowserPool {
|
|
|
74
62
|
/** FIFO of acquires waiting for a slot; each resolves when one frees. */
|
|
75
63
|
#waiters = [];
|
|
76
64
|
constructor(launch, opts) {
|
|
77
|
-
if (opts.maxContexts < 1)
|
|
65
|
+
if (opts.maxContexts < 1) {
|
|
78
66
|
throw new Error('maxContexts must be >= 1');
|
|
67
|
+
}
|
|
79
68
|
this.#headless = { launch, headed: false };
|
|
80
69
|
this.#headed = { launch: opts.launchHeaded, headed: true };
|
|
81
70
|
this.#max = opts.maxContexts;
|
|
@@ -116,11 +105,13 @@ export class BrowserPool {
|
|
|
116
105
|
* Prefers the alias the agent was given, when the app registered under its own name.
|
|
117
106
|
*/
|
|
118
107
|
leaseIdOnOrigin(origin, owner) {
|
|
119
|
-
if (owner === undefined)
|
|
108
|
+
if (owner === undefined) {
|
|
120
109
|
return undefined;
|
|
110
|
+
}
|
|
121
111
|
for (const [leaseId, lease] of this.#active) {
|
|
122
|
-
if (lease.owner !== owner)
|
|
112
|
+
if (lease.owner !== owner) {
|
|
123
113
|
continue;
|
|
114
|
+
}
|
|
124
115
|
let leaseOrigin;
|
|
125
116
|
try {
|
|
126
117
|
leaseOrigin = new URL(lease.url).origin;
|
|
@@ -128,11 +119,13 @@ export class BrowserPool {
|
|
|
128
119
|
catch {
|
|
129
120
|
continue;
|
|
130
121
|
}
|
|
131
|
-
if (leaseOrigin !== origin)
|
|
122
|
+
if (leaseOrigin !== origin) {
|
|
132
123
|
continue;
|
|
124
|
+
}
|
|
133
125
|
for (const [alias, id] of this.#aliases) {
|
|
134
|
-
if (id === leaseId)
|
|
126
|
+
if (id === leaseId) {
|
|
135
127
|
return alias;
|
|
128
|
+
}
|
|
136
129
|
}
|
|
137
130
|
return leaseId;
|
|
138
131
|
}
|
|
@@ -161,10 +154,12 @@ export class BrowserPool {
|
|
|
161
154
|
* that would have read a computed value.
|
|
162
155
|
*/
|
|
163
156
|
alias(registeredId, leaseId) {
|
|
164
|
-
if (registeredId === leaseId)
|
|
157
|
+
if (registeredId === leaseId) {
|
|
165
158
|
return;
|
|
166
|
-
|
|
159
|
+
}
|
|
160
|
+
if (!this.#active.has(leaseId)) {
|
|
167
161
|
return;
|
|
162
|
+
}
|
|
168
163
|
this.#aliases.set(registeredId, leaseId);
|
|
169
164
|
}
|
|
170
165
|
/**
|
|
@@ -202,8 +197,35 @@ export class BrowserPool {
|
|
|
202
197
|
/** Mark a lease as still in use (called on agent activity) so the reaper doesn't reclaim it. */
|
|
203
198
|
touch(sessionId) {
|
|
204
199
|
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
205
|
-
if (lease
|
|
206
|
-
|
|
200
|
+
if (lease === undefined) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
lease.touchedAt = this.#now();
|
|
204
|
+
delete lease.detachedAt;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The MCP connection `owner` closed. Its leases go after {@link ORPHANED_LEASE_GRACE_MS} unless it
|
|
208
|
+
* touches them again: a reconnect is a new owner, so nothing else would ever hand them back, and
|
|
209
|
+
* each one held a browser window open for the full TTL.
|
|
210
|
+
*/
|
|
211
|
+
detachOwner(owner) {
|
|
212
|
+
const now = this.#now();
|
|
213
|
+
for (const lease of this.#active.values()) {
|
|
214
|
+
if (lease.owner === owner) {
|
|
215
|
+
lease.detachedAt = now;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The MCP connection `owner` came back: a proxy that reconnects keeps its id. Its leases stop
|
|
221
|
+
* their grace, because the owner they wait for is here again.
|
|
222
|
+
*/
|
|
223
|
+
reattachOwner(owner) {
|
|
224
|
+
for (const lease of this.#active.values()) {
|
|
225
|
+
if (lease.owner === owner) {
|
|
226
|
+
delete lease.detachedAt;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
207
229
|
}
|
|
208
230
|
/**
|
|
209
231
|
* Reclaim leases untouched for longer than the TTL — a hung/crashed agent never holds a slot
|
|
@@ -221,8 +243,9 @@ export class BrowserPool {
|
|
|
221
243
|
*/
|
|
222
244
|
async screenshotLease(sessionId, opts = {}) {
|
|
223
245
|
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
224
|
-
if (lease === undefined || lease.page.screenshot === undefined)
|
|
246
|
+
if (lease === undefined || lease.page.screenshot === undefined) {
|
|
225
247
|
return undefined;
|
|
248
|
+
}
|
|
226
249
|
this.touch(sessionId);
|
|
227
250
|
try {
|
|
228
251
|
return await lease.page.screenshot(opts);
|
|
@@ -244,8 +267,9 @@ export class BrowserPool {
|
|
|
244
267
|
*/
|
|
245
268
|
async gotoLease(sessionId, url, timeoutMs) {
|
|
246
269
|
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
247
|
-
if (lease === undefined)
|
|
270
|
+
if (lease === undefined) {
|
|
248
271
|
return false;
|
|
272
|
+
}
|
|
249
273
|
this.touch(sessionId);
|
|
250
274
|
try {
|
|
251
275
|
await lease.page.goto(url, timeoutMs === undefined ? {} : { timeoutMs });
|
|
@@ -264,8 +288,9 @@ export class BrowserPool {
|
|
|
264
288
|
*/
|
|
265
289
|
async hoverLease(sessionId, x, y) {
|
|
266
290
|
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
267
|
-
if (lease === undefined || lease.page.hover === undefined)
|
|
291
|
+
if (lease === undefined || lease.page.hover === undefined) {
|
|
268
292
|
return false;
|
|
293
|
+
}
|
|
269
294
|
this.touch(sessionId);
|
|
270
295
|
try {
|
|
271
296
|
await lease.page.hover(x, y);
|
|
@@ -275,6 +300,46 @@ export class BrowserPool {
|
|
|
275
300
|
return false;
|
|
276
301
|
}
|
|
277
302
|
}
|
|
303
|
+
/** Set a live lease's media features, or false when this is not a lease or its page cannot. */
|
|
304
|
+
async emulateLease(sessionId, media) {
|
|
305
|
+
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
306
|
+
if (lease?.page.emulateMedia === undefined) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
this.touch(sessionId);
|
|
310
|
+
try {
|
|
311
|
+
await lease.page.emulateMedia(media);
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/** Whether this lease's page is still loading. False for a non-lease, or a page that cannot say. */
|
|
319
|
+
async leaseLoading(sessionId) {
|
|
320
|
+
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
321
|
+
if (lease === undefined) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
return pageLoading(lease.page).catch(() => false);
|
|
325
|
+
}
|
|
326
|
+
/** Whether this session is a lease whose page has a real keyboard. */
|
|
327
|
+
hasKeyboard(sessionId) {
|
|
328
|
+
return this.#active.get(this.#leaseIdOf(sessionId))?.page.press !== undefined;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Press a key with the leased page's real keyboard, or undefined when this session is not a lease
|
|
332
|
+
* or its page has no keyboard. A throw is the caller's to read: a key that went down and did not
|
|
333
|
+
* come up must not be answered with a second, synthetic press.
|
|
334
|
+
*/
|
|
335
|
+
async pressLease(sessionId, args) {
|
|
336
|
+
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
337
|
+
if (lease?.page.press === undefined) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
this.touch(sessionId);
|
|
341
|
+
return lease.page.press(args);
|
|
342
|
+
}
|
|
278
343
|
/**
|
|
279
344
|
* Install network mocks on a leased page, or false when this session is not a lease or cannot
|
|
280
345
|
* intercept. Same optional-capability contract as screenshotLease: absence must read as "could not
|
|
@@ -291,8 +356,9 @@ export class BrowserPool {
|
|
|
291
356
|
*/
|
|
292
357
|
async setViewportLease(sessionId, size) {
|
|
293
358
|
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
294
|
-
if (lease === undefined || lease.page.setViewport === undefined)
|
|
359
|
+
if (lease === undefined || lease.page.setViewport === undefined) {
|
|
295
360
|
return false;
|
|
361
|
+
}
|
|
296
362
|
this.touch(sessionId);
|
|
297
363
|
try {
|
|
298
364
|
await lease.page.setViewport(size);
|
|
@@ -302,19 +368,40 @@ export class BrowserPool {
|
|
|
302
368
|
return false;
|
|
303
369
|
}
|
|
304
370
|
}
|
|
371
|
+
/** The size a leased page reports for itself, or undefined when it cannot be read. */
|
|
372
|
+
async viewportOfLease(sessionId) {
|
|
373
|
+
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
374
|
+
return lease === undefined ? undefined : readViewport(lease.page);
|
|
375
|
+
}
|
|
305
376
|
async setMocksLease(sessionId, rules) {
|
|
306
377
|
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
307
|
-
if (lease === undefined || lease.page.installMocks === undefined)
|
|
378
|
+
if (lease === undefined || lease.page.installMocks === undefined) {
|
|
308
379
|
return false;
|
|
380
|
+
}
|
|
309
381
|
this.touch(sessionId);
|
|
310
382
|
try {
|
|
311
383
|
await lease.page.installMocks(rules);
|
|
384
|
+
// Mocks registered later are asked first, and one that lets a request through would skip
|
|
385
|
+
// the production guard. Re-asserting the guard puts it back in front.
|
|
386
|
+
if (lease.guard !== undefined) {
|
|
387
|
+
await lease.page.guardRequests?.(lease.guard);
|
|
388
|
+
}
|
|
312
389
|
return true;
|
|
313
390
|
}
|
|
314
391
|
catch {
|
|
315
392
|
return false;
|
|
316
393
|
}
|
|
317
394
|
}
|
|
395
|
+
/** Guard a leased page's requests, or false when this is not a lease or its page cannot. */
|
|
396
|
+
async guardLease(sessionId, decide) {
|
|
397
|
+
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
398
|
+
if (lease === undefined || lease.page.guardRequests === undefined) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
await lease.page.guardRequests(decide);
|
|
402
|
+
lease.guard = decide;
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
318
405
|
/** What a lease was granted when it opened, or undefined for a session that is not a lease. */
|
|
319
406
|
permissionsOf(sessionId) {
|
|
320
407
|
return this.#active.get(this.#leaseIdOf(sessionId))?.permissions;
|
|
@@ -344,35 +431,23 @@ export class BrowserPool {
|
|
|
344
431
|
*/
|
|
345
432
|
async notificationPermission(sessionId) {
|
|
346
433
|
const lease = this.#active.get(this.#leaseIdOf(sessionId));
|
|
347
|
-
|
|
348
|
-
return undefined;
|
|
349
|
-
let timer;
|
|
350
|
-
const gaveUp = new Promise((resolve) => {
|
|
351
|
-
timer = setTimeout(() => resolve(undefined), NOTIFICATION_READ_TIMEOUT_MS);
|
|
352
|
-
});
|
|
353
|
-
try {
|
|
354
|
-
const value = await Promise.race([lease.page.evaluate(NOTIFICATION_PERMISSION_READ), gaveUp]);
|
|
355
|
-
return 'string' === typeof value ? value : undefined;
|
|
356
|
-
}
|
|
357
|
-
catch {
|
|
358
|
-
return undefined;
|
|
359
|
-
}
|
|
360
|
-
finally {
|
|
361
|
-
clearTimeout(timer);
|
|
362
|
-
}
|
|
434
|
+
return lease === undefined ? undefined : readNotificationPermission(lease.page);
|
|
363
435
|
}
|
|
364
436
|
async sweepExpired() {
|
|
365
437
|
const now = this.#now();
|
|
366
438
|
const expired = [...this.#active.entries()]
|
|
367
|
-
.filter(([, lease]) => now - lease.touchedAt > this.#ttl
|
|
439
|
+
.filter(([, lease]) => now - lease.touchedAt > this.#ttl ||
|
|
440
|
+
(lease.detachedAt !== undefined && now - lease.detachedAt >= ORPHANED_LEASE_GRACE_MS))
|
|
368
441
|
.map(([sessionId]) => sessionId);
|
|
369
|
-
for (const sessionId of expired)
|
|
442
|
+
for (const sessionId of expired) {
|
|
370
443
|
await this.#release(sessionId);
|
|
444
|
+
}
|
|
371
445
|
this.#reapedLeases += expired.length;
|
|
372
446
|
for (const sessionId of expired) {
|
|
373
447
|
this.#reapedLeaseIds.push(sessionId);
|
|
374
|
-
if (this.#reapedLeaseIds.length > MAX_REMEMBERED_REAPED_LEASES)
|
|
448
|
+
if (this.#reapedLeaseIds.length > MAX_REMEMBERED_REAPED_LEASES) {
|
|
375
449
|
this.#reapedLeaseIds.shift();
|
|
450
|
+
}
|
|
376
451
|
}
|
|
377
452
|
return expired;
|
|
378
453
|
}
|
|
@@ -404,27 +479,43 @@ export class BrowserPool {
|
|
|
404
479
|
}
|
|
405
480
|
/**
|
|
406
481
|
* Lease an isolated context, navigate it to `url`, and return a handle. If the pool is at capacity,
|
|
407
|
-
* waits FIFO until a slot frees (or until `signal` aborts, if provided)
|
|
482
|
+
* waits FIFO until a slot frees (or until `signal` aborts, if provided), unless `whenFull` is
|
|
483
|
+
* `'refuse'`: then it throws {@link PoolAtCapacityError} at once and claims nothing.
|
|
408
484
|
*/
|
|
409
485
|
async acquire(url, opts = {}) {
|
|
410
|
-
if (this.#closed)
|
|
486
|
+
if (this.#closed) {
|
|
411
487
|
throw new Error('browser pool is shut down');
|
|
488
|
+
}
|
|
412
489
|
const slot = true === opts.headed ? this.#headed : this.#headless;
|
|
413
490
|
// Refused before a slot is claimed, so a refusal never holds one.
|
|
414
|
-
if (slot.launch === undefined)
|
|
491
|
+
if (slot.launch === undefined) {
|
|
415
492
|
throw new Error(NO_HEADED_BROWSER);
|
|
493
|
+
}
|
|
416
494
|
// #waitForSlot claims the slot synchronously (bumps #occupied) before returning, so the cap holds
|
|
417
|
-
// even when many acquires race through the gate in the same tick.
|
|
418
|
-
|
|
495
|
+
// even when many acquires race through the gate in the same tick. A refusing acquire claims the
|
|
496
|
+
// same way, or claims nothing and says so.
|
|
497
|
+
if ('refuse' === opts.whenFull) {
|
|
498
|
+
if (!this.#tryClaim()) {
|
|
499
|
+
throw new PoolAtCapacityError(this.#max);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
await this.#waitForSlot(opts.signal);
|
|
504
|
+
}
|
|
419
505
|
const sessionId = opts.sessionId ?? this.#genId();
|
|
420
506
|
let context;
|
|
421
507
|
let pending;
|
|
422
508
|
let pendingDialogMessage;
|
|
423
509
|
try {
|
|
424
510
|
const browser = await this.#ensureBrowser(slot);
|
|
425
|
-
|
|
426
|
-
|
|
511
|
+
const contextOpts = {
|
|
512
|
+
...(true === opts.ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : {}),
|
|
513
|
+
...opts.emulate,
|
|
514
|
+
};
|
|
515
|
+
context = await browser.newContext(0 < Object.keys(contextOpts).length ? contextOpts : undefined);
|
|
516
|
+
if (opts.permissions !== undefined) {
|
|
427
517
|
await grantLeasePermissions(context, opts.permissions, url);
|
|
518
|
+
}
|
|
428
519
|
const page = await context.newPage();
|
|
429
520
|
// A caller-supplied init script is a stub of the bindings a desktop shell injects, and it has
|
|
430
521
|
// to exist before the app's first module runs. Installed here — before goto — so the very
|
|
@@ -447,23 +538,28 @@ export class BrowserPool {
|
|
|
447
538
|
// retry backoff stretches out.
|
|
448
539
|
page.onConsole?.((text) => {
|
|
449
540
|
const dialled = unreachableUrlIn(text);
|
|
450
|
-
if (dialled === undefined)
|
|
541
|
+
if (dialled === undefined) {
|
|
451
542
|
return;
|
|
543
|
+
}
|
|
452
544
|
const active = this.#active.get(sessionId);
|
|
453
|
-
if (active !== undefined)
|
|
545
|
+
if (active !== undefined) {
|
|
454
546
|
active.dialFailureUrl = dialled;
|
|
455
|
-
|
|
456
|
-
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
pending = dialled;
|
|
550
|
+
} // logged during goto, before the lease is registered below
|
|
457
551
|
});
|
|
458
552
|
// Same "listen before goto" reasoning as onConsole above: a dialog opened during the initial
|
|
459
553
|
// navigation (an app that confirms something in an onload handler) must still be dismissed, not
|
|
460
554
|
// just ones opened after the lease is registered.
|
|
461
555
|
page.onDialog?.((dialog) => {
|
|
462
556
|
const active = this.#active.get(sessionId);
|
|
463
|
-
if (active !== undefined)
|
|
557
|
+
if (active !== undefined) {
|
|
464
558
|
active.lastDialogMessage = dialog.message;
|
|
465
|
-
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
466
561
|
pendingDialogMessage = dialog.message;
|
|
562
|
+
}
|
|
467
563
|
void dialog.dismiss();
|
|
468
564
|
});
|
|
469
565
|
const marker = leaseMarkerOf(url);
|
|
@@ -491,8 +587,9 @@ export class BrowserPool {
|
|
|
491
587
|
// #occupied. Registering the lease now would resurrect a dead entry against a crashed browser with
|
|
492
588
|
// the slot count out of sync (drifting below #active.size, eventually exceeding the cap). If we're
|
|
493
589
|
// no longer the live browser, bail — the catch below closes the context and returns the slot.
|
|
494
|
-
if (slot.browser !== browser)
|
|
590
|
+
if (slot.browser !== browser) {
|
|
495
591
|
throw new Error('browser crashed during navigation');
|
|
592
|
+
}
|
|
496
593
|
// Foreground it: a page opened beside other tabs can start hidden (#1351). Best-effort — a tab
|
|
497
594
|
// that stays hidden is reported by name at the lease, which failing here would lose.
|
|
498
595
|
await page.bringToFront?.().catch(() => undefined);
|
|
@@ -502,8 +599,9 @@ export class BrowserPool {
|
|
|
502
599
|
if ('function' === typeof s) {
|
|
503
600
|
try {
|
|
504
601
|
const code = s.call(navRes);
|
|
505
|
-
if ('number' === typeof code)
|
|
602
|
+
if ('number' === typeof code) {
|
|
506
603
|
navStatus = code;
|
|
604
|
+
}
|
|
507
605
|
}
|
|
508
606
|
catch {
|
|
509
607
|
// ignore
|
|
@@ -535,19 +633,24 @@ export class BrowserPool {
|
|
|
535
633
|
release: () => this.#release(sessionId),
|
|
536
634
|
injectReader: async () => {
|
|
537
635
|
const script = this.#zeroInstall?.();
|
|
538
|
-
if (script === undefined || page.evaluate === undefined)
|
|
636
|
+
if (script === undefined || page.evaluate === undefined) {
|
|
539
637
|
return false;
|
|
638
|
+
}
|
|
540
639
|
await page.addInitScript?.(script);
|
|
541
|
-
await page.evaluate(script)
|
|
640
|
+
if (READER_NOT_HTML === (await page.evaluate(script))) {
|
|
641
|
+
throw new NotHtmlDocumentError();
|
|
642
|
+
}
|
|
542
643
|
return true;
|
|
543
644
|
},
|
|
645
|
+
isLoading: () => pageLoading(page),
|
|
544
646
|
};
|
|
545
647
|
}
|
|
546
648
|
catch (err) {
|
|
547
649
|
// Setup failed after we claimed the slot — give it back so a queued acquire isn't stuck, and
|
|
548
650
|
// close the half-open context.
|
|
549
|
-
if (context !== undefined)
|
|
550
|
-
await context.close()
|
|
651
|
+
if (context !== undefined) {
|
|
652
|
+
await settledWithin(context.close(), LEASE_CLOSE_TIMEOUT_MS);
|
|
653
|
+
}
|
|
551
654
|
this.#releaseSlot();
|
|
552
655
|
throw err;
|
|
553
656
|
}
|
|
@@ -586,28 +689,65 @@ export class BrowserPool {
|
|
|
586
689
|
}
|
|
587
690
|
const browser = slot.browser;
|
|
588
691
|
slot.browser = undefined;
|
|
589
|
-
if (browser !== undefined)
|
|
692
|
+
if (browser !== undefined) {
|
|
590
693
|
await browser.close().catch(() => undefined);
|
|
694
|
+
}
|
|
591
695
|
}
|
|
592
|
-
for (const waiter of this.#waiters.splice(0))
|
|
696
|
+
for (const waiter of this.#waiters.splice(0)) {
|
|
593
697
|
waiter();
|
|
698
|
+
}
|
|
594
699
|
}
|
|
595
700
|
async #release(sessionId) {
|
|
596
701
|
const lease = this.#active.get(sessionId);
|
|
597
|
-
if (lease === undefined)
|
|
598
|
-
return;
|
|
702
|
+
if (lease === undefined) {
|
|
703
|
+
return;
|
|
704
|
+
} // already released or lost to a crash
|
|
599
705
|
this.#active.delete(sessionId);
|
|
600
706
|
// Drop any alias pointing here, or the map grows for the life of the daemon and a later lease
|
|
601
707
|
// reusing that registered name would resolve to a context that is already closed.
|
|
602
708
|
for (const [registered, leaseId] of this.#aliases) {
|
|
603
|
-
if (leaseId === sessionId)
|
|
709
|
+
if (leaseId === sessionId) {
|
|
604
710
|
this.#aliases.delete(registered);
|
|
711
|
+
}
|
|
605
712
|
}
|
|
606
|
-
//
|
|
607
|
-
|
|
608
|
-
await lease.hostStub?.dispose().catch(() => undefined);
|
|
609
|
-
await lease.context.close().catch(() => undefined);
|
|
713
|
+
// Bounded: a close that never returned held the slot and kept the tab open after the agent left.
|
|
714
|
+
const closed = await closeLeaseTab(lease, LEASE_CLOSE_TIMEOUT_MS);
|
|
610
715
|
this.#releaseSlot();
|
|
716
|
+
if (!closed) {
|
|
717
|
+
await this.#dropWedgedBrowser(sessionId, lease.headed);
|
|
718
|
+
}
|
|
719
|
+
await this.#closeIfIdle();
|
|
720
|
+
}
|
|
721
|
+
/** Close the browser of a tab that did not close in time, unless another lease still uses it. */
|
|
722
|
+
async #dropWedgedBrowser(sessionId, headed) {
|
|
723
|
+
const slot = headed ? this.#headed : this.#headless;
|
|
724
|
+
const shared = [...this.#active.values()].some((other) => other.headed === headed);
|
|
725
|
+
const browser = slot.browser;
|
|
726
|
+
const drop = !shared && browser !== undefined && slot.launching === undefined;
|
|
727
|
+
log(LEASE_CLOSE_TIMED_OUT, { lease: sessionId, browserClosed: drop });
|
|
728
|
+
if (!drop) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
slot.browser = undefined;
|
|
732
|
+
await settledWithin(browser.close(), LEASE_CLOSE_TIMEOUT_MS);
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Close the browsers once nothing holds or waits for a lease. An idle Chromium kept its memory
|
|
736
|
+
* until the daemon itself went idle. Every acquire claims a slot before it touches a browser, so
|
|
737
|
+
* nothing occupied means no acquire is between launching a browser and using it.
|
|
738
|
+
*/
|
|
739
|
+
async #closeIfIdle() {
|
|
740
|
+
if (this.#active.size > 0 || this.#occupied > 0 || this.#waiters.length > 0) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
for (const slot of [this.#headless, this.#headed]) {
|
|
744
|
+
const browser = slot.browser;
|
|
745
|
+
if (browser === undefined || slot.launching !== undefined) {
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
slot.browser = undefined;
|
|
749
|
+
await settledWithin(browser.close(), LEASE_CLOSE_TIMEOUT_MS);
|
|
750
|
+
}
|
|
611
751
|
}
|
|
612
752
|
/** Synchronously claim a slot if under the cap. Returns true on success (caller proceeds). */
|
|
613
753
|
#tryClaim() {
|
|
@@ -618,21 +758,24 @@ export class BrowserPool {
|
|
|
618
758
|
return false;
|
|
619
759
|
}
|
|
620
760
|
#waitForSlot(signal) {
|
|
621
|
-
if (this.#tryClaim())
|
|
761
|
+
if (this.#tryClaim()) {
|
|
622
762
|
return Promise.resolve();
|
|
763
|
+
}
|
|
623
764
|
return new Promise((resolve, reject) => {
|
|
624
765
|
const onFree = () => {
|
|
625
766
|
// The pool shut down while we waited → reject rather than relaunch a browser.
|
|
626
767
|
if (this.#closed) {
|
|
627
|
-
if (signal !== undefined)
|
|
768
|
+
if (signal !== undefined) {
|
|
628
769
|
signal.removeEventListener('abort', onAbort);
|
|
770
|
+
}
|
|
629
771
|
reject(new Error('browser pool is shut down'));
|
|
630
772
|
return;
|
|
631
773
|
}
|
|
632
774
|
// Claim the freed slot; if another woken waiter beat us to it, re-queue.
|
|
633
775
|
if (this.#tryClaim()) {
|
|
634
|
-
if (signal !== undefined)
|
|
776
|
+
if (signal !== undefined) {
|
|
635
777
|
signal.removeEventListener('abort', onAbort);
|
|
778
|
+
}
|
|
636
779
|
resolve();
|
|
637
780
|
}
|
|
638
781
|
else {
|
|
@@ -641,8 +784,9 @@ export class BrowserPool {
|
|
|
641
784
|
};
|
|
642
785
|
const onAbort = () => {
|
|
643
786
|
const i = this.#waiters.indexOf(onFree);
|
|
644
|
-
if (i >= 0)
|
|
787
|
+
if (i >= 0) {
|
|
645
788
|
this.#waiters.splice(i, 1);
|
|
789
|
+
}
|
|
646
790
|
reject(new Error('acquire aborted'));
|
|
647
791
|
};
|
|
648
792
|
if (signal !== undefined) {
|
|
@@ -657,20 +801,25 @@ export class BrowserPool {
|
|
|
657
801
|
}
|
|
658
802
|
/** Give back one occupied slot and hand it to the next waiter (which re-claims it synchronously). */
|
|
659
803
|
#releaseSlot() {
|
|
660
|
-
if (this.#occupied > 0)
|
|
804
|
+
if (this.#occupied > 0) {
|
|
661
805
|
this.#occupied -= 1;
|
|
806
|
+
}
|
|
662
807
|
const next = this.#waiters.shift();
|
|
663
|
-
if (next !== undefined)
|
|
808
|
+
if (next !== undefined) {
|
|
664
809
|
next();
|
|
810
|
+
}
|
|
665
811
|
}
|
|
666
812
|
async #ensureBrowser(slot) {
|
|
667
|
-
if (this.#closed)
|
|
813
|
+
if (this.#closed) {
|
|
668
814
|
throw new Error('browser pool is shut down');
|
|
669
|
-
|
|
815
|
+
}
|
|
816
|
+
if (slot.browser !== undefined && slot.browser.isConnected()) {
|
|
670
817
|
return slot.browser;
|
|
818
|
+
}
|
|
671
819
|
const launch = slot.launch;
|
|
672
|
-
if (launch === undefined)
|
|
820
|
+
if (launch === undefined) {
|
|
673
821
|
throw new Error(NO_HEADED_BROWSER);
|
|
822
|
+
}
|
|
674
823
|
// De-dupe concurrent launches: the first acquire to find no browser starts one; the rest await it.
|
|
675
824
|
if (slot.launching === undefined) {
|
|
676
825
|
// The in-flight promise MUST be cleared on failure as well as success. It used to be cleared only
|
|
@@ -695,21 +844,24 @@ export class BrowserPool {
|
|
|
695
844
|
* acquire of that kind relaunches it. The other browser's leases live on.
|
|
696
845
|
*/
|
|
697
846
|
#onCrash(slot, crashed) {
|
|
698
|
-
if (slot.browser !== crashed)
|
|
699
|
-
return;
|
|
847
|
+
if (slot.browser !== crashed) {
|
|
848
|
+
return;
|
|
849
|
+
} // a stale handler from a prior browser
|
|
700
850
|
slot.browser = undefined;
|
|
701
851
|
let freed = 0;
|
|
702
852
|
for (const [id, lease] of this.#active) {
|
|
703
|
-
if (lease.headed !== slot.headed)
|
|
853
|
+
if (lease.headed !== slot.headed) {
|
|
704
854
|
continue;
|
|
855
|
+
}
|
|
705
856
|
this.#active.delete(id);
|
|
706
857
|
freed += 1;
|
|
707
858
|
}
|
|
708
859
|
// A lease mid-acquire on the dead browser holds a slot too; it frees it in its own catch. When
|
|
709
860
|
// every lease belonged to this browser, nothing else can be holding one.
|
|
710
861
|
this.#occupied = 0 === this.#active.size ? 0 : Math.max(0, this.#occupied - freed);
|
|
711
|
-
for (const waiter of this.#waiters.splice(0))
|
|
712
|
-
waiter();
|
|
862
|
+
for (const waiter of this.#waiters.splice(0)) {
|
|
863
|
+
waiter();
|
|
864
|
+
} // let them re-claim + relaunch
|
|
713
865
|
}
|
|
714
866
|
/** Whether this lease's context skips certificate checks. A context cannot change it later. */
|
|
715
867
|
ignoresHTTPSErrors(sessionId) {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* `denied`. A page that gates its UI on the second never sees the grant, so a lease that reported
|
|
16
16
|
* "granted" there would be reporting something the page cannot see.
|
|
17
17
|
*/
|
|
18
|
-
import type { PooledContext } from './pool-contract.js';
|
|
18
|
+
import type { PooledContext, PooledPage } from './pool-contract.js';
|
|
19
19
|
/** The permission whose grant the headless shell does not pass on to `Notification.permission`. */
|
|
20
20
|
export declare const NOTIFICATIONS_PERMISSION = "notifications";
|
|
21
21
|
/** Evaluated in the leased page. A string, because the pool's page surface evaluates scripts. */
|
|
@@ -31,3 +31,11 @@ export declare class LeasePermissionError extends Error {
|
|
|
31
31
|
}
|
|
32
32
|
/** Grant `permissions` on the origin of `url`. Nothing to grant is a no-op, not a refusal. */
|
|
33
33
|
export declare function grantLeasePermissions(context: PooledContext, permissions: readonly string[], url: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* What the page's `Notification.permission` says, or undefined when it cannot be asked.
|
|
36
|
+
*
|
|
37
|
+
* Read back rather than inferred from the grant, because the headless shell answers `denied`
|
|
38
|
+
* after a notifications grant. Bounded, so a page that never answers reads as "could not ask"
|
|
39
|
+
* instead of holding the caller.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readNotificationPermission(page: PooledPage): Promise<string | undefined>;
|
|
@@ -31,3 +31,29 @@ export async function grantLeasePermissions(context, permissions, url) {
|
|
|
31
31
|
throw new LeasePermissionError(`could not grant ${permissions.join(', ')}: ${reasonOf(err)}`);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* What the page's `Notification.permission` says, or undefined when it cannot be asked.
|
|
36
|
+
*
|
|
37
|
+
* Read back rather than inferred from the grant, because the headless shell answers `denied`
|
|
38
|
+
* after a notifications grant. Bounded, so a page that never answers reads as "could not ask"
|
|
39
|
+
* instead of holding the caller.
|
|
40
|
+
*/
|
|
41
|
+
export async function readNotificationPermission(page) {
|
|
42
|
+
if (page.evaluate === undefined) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
let timer;
|
|
46
|
+
const gaveUp = new Promise((resolve) => {
|
|
47
|
+
timer = setTimeout(() => resolve(undefined), NOTIFICATION_READ_TIMEOUT_MS);
|
|
48
|
+
});
|
|
49
|
+
try {
|
|
50
|
+
const value = await Promise.race([page.evaluate(NOTIFICATION_PERMISSION_READ), gaveUp]);
|
|
51
|
+
return 'string' === typeof value ? value : undefined;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
}
|
|
59
|
+
}
|