@reticlehq/server 3.9.0 → 3.10.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/SKILL.md +13 -12
- package/dist/command/cli/adhoc-suite.js +2 -0
- package/dist/command/cli/adhoc-verdict.d.ts +2 -43
- package/dist/command/cli/adhoc-verdict.js +50 -187
- package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
- package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
- package/dist/command/cli/cli-doctor.js +1 -1
- package/dist/command/cli/cli-flow-commands.js +2 -9
- package/dist/command/cli/cli-kill.js +1 -1
- package/dist/command/cli/cli-parse-compare.d.ts +22 -0
- package/dist/command/cli/cli-parse-compare.js +75 -0
- package/dist/command/cli/cli-parse-grade.d.ts +36 -0
- package/dist/command/cli/cli-parse-grade.js +75 -0
- package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
- package/dist/command/cli/cli-parse-grammar.js +18 -20
- package/dist/command/cli/cli-parse-verify.d.ts +10 -0
- package/dist/command/cli/cli-parse-verify.js +63 -14
- package/dist/command/cli/cli-parse.d.ts +17 -15
- package/dist/command/cli/cli-parse.js +44 -27
- package/dist/command/cli/cli-update-commands.d.ts +1 -1
- package/dist/command/cli/cli-update-commands.js +29 -20
- package/dist/command/cli/cli-usage.js +58 -29
- package/dist/command/cli/cli-verify.d.ts +11 -2
- package/dist/command/cli/cli-verify.js +27 -8
- package/dist/command/cli/cloud-cli.js +2 -119
- package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
- package/dist/command/cli/daemon-link/daemon-client.js +205 -0
- package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
- package/dist/command/cli/doctor-repairs.js +1 -1
- package/dist/command/cli/ensure-daemon.d.ts +26 -0
- package/dist/command/cli/ensure-daemon.js +57 -0
- package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
- package/dist/command/cli/finished-run/grade-command.js +180 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +1 -1
- package/dist/command/cli/ports/port-holder.d.ts +9 -0
- package/dist/command/cli/ports/port-holder.js +27 -3
- package/dist/command/cli/status/status-lines.d.ts +1 -0
- package/dist/command/cli/status/status-lines.js +22 -0
- package/dist/command/cli/status-fields.d.ts +5 -1
- package/dist/command/cli/status-fields.js +7 -3
- package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
- package/dist/command/cli/two-builds/compare-command.js +314 -0
- package/dist/command/cli/verify-against.d.ts +61 -0
- package/dist/command/cli/verify-against.js +187 -0
- package/dist/command/cli/verify-no-session.d.ts +2 -0
- package/dist/command/cli/verify-no-session.js +13 -0
- package/dist/command/cli/verify-results.d.ts +35 -2
- package/dist/command/cli/verify-results.js +76 -10
- package/dist/command/cli.js +46 -41
- package/dist/command/daemon/daemon-resolve.d.ts +8 -1
- package/dist/command/daemon/daemon-resolve.js +27 -1
- package/dist/command/daemon/daemon.d.ts +12 -1
- package/dist/command/daemon/daemon.js +35 -4
- package/dist/command/daemon/lifetime/death-cause.js +21 -2
- package/dist/command/daemon/lifetime/heartbeat.js +7 -2
- package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
- package/dist/command/daemon/lifetime/startup-failure.js +62 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
- package/dist/command/setup/bringup/dev-server-files.js +17 -0
- package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
- package/dist/command/setup/bringup/static-page-server.js +43 -9
- package/dist/command/setup/init/init-runtime.js +6 -0
- package/dist/command/setup/node-effects.d.ts +3 -2
- package/dist/command/setup/node-effects.js +9 -3
- package/dist/command/setup/remaining-steps.d.ts +8 -0
- package/dist/command/setup/remaining-steps.js +10 -0
- package/dist/command/setup/run-setup.d.ts +2 -0
- package/dist/command/setup/run-setup.js +9 -1
- package/dist/command/setup/setup-command.js +6 -3
- package/dist/command/update/updater.js +6 -0
- package/dist/command/version/version-skew.js +1 -1
- package/dist/features/compare/action-plan.d.ts +260 -0
- package/dist/features/compare/action-plan.js +543 -0
- package/dist/features/compare/behaviour-compare.d.ts +24 -0
- package/dist/features/compare/behaviour-compare.js +85 -0
- package/dist/features/compare/behaviour-diff.d.ts +97 -0
- package/dist/features/compare/behaviour-diff.js +202 -0
- package/dist/features/compare/behaviour-report.d.ts +40 -0
- package/dist/features/compare/behaviour-report.js +128 -0
- package/dist/features/compare/build-readings.d.ts +106 -0
- package/dist/features/compare/build-readings.js +250 -0
- package/dist/features/compare/compare-diff.d.ts +62 -0
- package/dist/features/compare/compare-diff.js +177 -0
- package/dist/features/compare/compare-report-html.d.ts +6 -0
- package/dist/features/compare/compare-report-html.js +102 -0
- package/dist/features/compare/compare-report.d.ts +15 -0
- package/dist/features/compare/compare-report.js +73 -0
- package/dist/features/compare/compare-run.d.ts +60 -0
- package/dist/features/compare/compare-run.js +123 -0
- package/dist/features/compare/outside/effect-lines.d.ts +26 -0
- package/dist/features/compare/outside/effect-lines.js +103 -0
- package/dist/features/compare/outside/state-sources.d.ts +76 -0
- package/dist/features/compare/outside/state-sources.js +164 -0
- package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
- package/dist/features/compare/outside/value-dictionary.js +227 -0
- package/dist/features/compare/visible-content.d.ts +20 -0
- package/dist/features/compare/visible-content.js +125 -0
- package/dist/features/crawl/crawl-empty.js +2 -1
- package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
- package/dist/features/crawl/crawl-exhaustive.js +138 -0
- package/dist/features/crawl/crawl-tools.d.ts +9 -2
- package/dist/features/crawl/crawl-tools.js +28 -12
- package/dist/features/crawl/crawl.d.ts +8 -9
- package/dist/features/crawl/crawl.js +61 -65
- package/dist/features/exhaust/app-coverage.d.ts +5 -1
- package/dist/features/exhaust/app-coverage.js +3 -1
- package/dist/features/exhaust/code-coverage.js +8 -4
- package/dist/features/exhaust/edge-cases.d.ts +133 -0
- package/dist/features/exhaust/edge-cases.js +310 -0
- package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
- package/dist/features/exhaust/edges/edge-drive.js +364 -0
- package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
- package/dist/features/exhaust/edges/edge-run.js +169 -0
- package/dist/features/exhaust/explorer.d.ts +87 -14
- package/dist/features/exhaust/explorer.js +159 -25
- package/dist/features/exhaust/failure-branches.d.ts +5 -0
- package/dist/features/exhaust/failure-branches.js +29 -11
- package/dist/features/exhaust/invariants.d.ts +112 -0
- package/dist/features/exhaust/invariants.js +188 -0
- package/dist/features/exhaust/ledger.d.ts +68 -0
- package/dist/features/exhaust/ledger.js +121 -13
- package/dist/features/exhaust/session-fold.d.ts +10 -0
- package/dist/features/exhaust/session-fold.js +46 -3
- package/dist/features/exhaust/session-port.d.ts +3 -1
- package/dist/features/exhaust/session-port.js +97 -28
- package/dist/features/grade/grade-answer.d.ts +99 -0
- package/dist/features/grade/grade-answer.js +112 -0
- package/dist/features/grade/grade-noise.d.ts +27 -0
- package/dist/features/grade/grade-noise.js +55 -0
- package/dist/features/grade/grade-renumber.d.ts +12 -0
- package/dist/features/grade/grade-renumber.js +86 -0
- package/dist/features/grade/grade-state.d.ts +50 -0
- package/dist/features/grade/grade-state.js +93 -0
- package/dist/features/grade/grade-suggest.d.ts +33 -0
- package/dist/features/grade/grade-suggest.js +98 -0
- package/dist/features/visual/visual-tools.js +66 -19
- package/dist/index.js +7 -4
- package/dist/judgement/capsule/causal-summary.js +9 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +16 -2
- package/dist/judgement/runs/runner-port.d.ts +2 -0
- package/dist/judgement/runs/runner-port.js +23 -5
- package/dist/language/flows/affected-tools.d.ts +1 -1
- package/dist/language/flows/affected-tools.js +49 -30
- package/dist/language/flows/change/git-changed.d.ts +2 -1
- package/dist/language/flows/change/git-changed.js +19 -2
- package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
- package/dist/language/flows/fields/flow-secret-field.js +77 -21
- package/dist/language/flows/flow-anchor.d.ts +60 -2
- package/dist/language/flows/flow-anchor.js +125 -20
- package/dist/language/flows/flow-classify.d.ts +20 -0
- package/dist/language/flows/flow-classify.js +90 -12
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +25 -1
- package/dist/language/flows/flow-journey.d.ts +38 -4
- package/dist/language/flows/flow-journey.js +138 -8
- package/dist/language/flows/flow-replay-run.d.ts +0 -16
- package/dist/language/flows/flow-replay-run.js +76 -14
- package/dist/language/flows/flow-replay-types.d.ts +4 -2
- package/dist/language/flows/flow-replay.d.ts +19 -1
- package/dist/language/flows/flow-replay.js +169 -52
- package/dist/language/flows/flow-replayable.js +7 -1
- package/dist/language/flows/flow-start-path.d.ts +34 -5
- package/dist/language/flows/flow-start-path.js +202 -15
- package/dist/language/flows/flow-step-runners.d.ts +14 -13
- package/dist/language/flows/flow-step-runners.js +184 -109
- package/dist/language/flows/flow-store-for-session.d.ts +26 -25
- package/dist/language/flows/flow-store-for-session.js +44 -0
- package/dist/language/flows/flow-tools.js +46 -33
- package/dist/language/flows/flows.js +176 -76
- package/dist/language/flows/recording/tape/recordings.d.ts +19 -3
- package/dist/language/flows/recording/tape/recordings.js +71 -5
- package/dist/language/flows/replay.d.ts +19 -5
- package/dist/language/flows/replay.js +99 -7
- package/dist/language/flows/steps/page-steps.d.ts +68 -0
- package/dist/language/flows/steps/page-steps.js +198 -0
- package/dist/language/flows/suite/sequential-run.d.ts +68 -0
- package/dist/language/flows/suite/sequential-run.js +96 -0
- package/dist/language/flows/verify-change-tools.js +26 -15
- package/dist/memory/cloud/background-sync.d.ts +13 -0
- package/dist/memory/cloud/background-sync.js +24 -3
- package/dist/memory/cloud/sync-daemon.js +26 -27
- package/dist/memory/journal/attach-journal.js +2 -1
- package/dist/memory/journal/drive-flow.d.ts +7 -0
- package/dist/memory/journal/drive-flow.js +44 -13
- package/dist/memory/journal/journal-recorder.d.ts +4 -2
- package/dist/memory/journal/session-journal.d.ts +7 -0
- package/dist/memory/journal/session-journal.js +58 -10
- package/dist/memory/project/root-claims.d.ts +9 -0
- package/dist/memory/project/root-claims.js +9 -4
- package/dist/portal/input/download-capture.d.ts +7 -0
- package/dist/portal/input/download-capture.js +92 -0
- package/dist/portal/input/network-detail.d.ts +6 -0
- package/dist/portal/input/network-detail.js +12 -1
- package/dist/portal/input/network-mock-tools.js +49 -13
- package/dist/portal/input/network-mock.d.ts +8 -0
- package/dist/portal/input/network-mock.js +28 -9
- package/dist/portal/input/page-capture.d.ts +24 -0
- package/dist/portal/input/page-capture.js +33 -0
- package/dist/portal/input/real-input.d.ts +22 -20
- package/dist/portal/input/real-input.js +41 -31
- package/dist/portal/input/viewport-tools.d.ts +8 -0
- package/dist/portal/input/viewport-tools.js +75 -18
- package/dist/portal/pool/browser-pool.d.ts +11 -29
- package/dist/portal/pool/browser-pool.js +35 -17
- package/dist/portal/pool/context-permissions.d.ts +17 -3
- package/dist/portal/pool/context-permissions.js +36 -5
- package/dist/portal/pool/determinism.d.ts +52 -0
- package/dist/portal/pool/determinism.js +259 -0
- package/dist/portal/pool/lease-emulation.d.ts +34 -1
- package/dist/portal/pool/lease-emulation.js +18 -0
- package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
- package/dist/portal/pool/lease-marker-stamp.js +15 -1
- package/dist/portal/pool/lease-page-error.d.ts +44 -17
- package/dist/portal/pool/lease-page-error.js +147 -5
- package/dist/portal/pool/page-error-journal.d.ts +7 -1
- package/dist/portal/pool/page-error-journal.js +27 -0
- package/dist/portal/pool/playwright-launcher.js +51 -8
- package/dist/portal/pool/pool-contract.d.ts +27 -1
- package/dist/portal/pool/pool-lease.d.ts +36 -1
- package/dist/portal/pool/zero-install.d.ts +8 -0
- package/dist/portal/pool/zero-install.js +23 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +7 -6
- package/dist/portal/session/dev-server/dev-command.js +61 -15
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
- package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
- package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
- package/dist/portal/session/dev-server/project-ports.js +62 -0
- package/dist/portal/session/facts/departed-network.d.ts +66 -0
- package/dist/portal/session/facts/departed-network.js +142 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
- package/dist/portal/session/no-session-diagnosis.js +67 -25
- package/dist/portal/session/no-session-next-action.d.ts +2 -0
- package/dist/portal/session/no-session-next-action.js +12 -3
- package/dist/portal/session/no-session-watch.d.ts +6 -0
- package/dist/portal/session/no-session-watch.js +38 -3
- package/dist/portal/session/serving-directory.d.ts +10 -0
- package/dist/portal/session/serving-directory.js +39 -8
- package/dist/portal/session/session-manager.d.ts +10 -0
- package/dist/portal/session/session-manager.js +174 -63
- package/dist/start-wiring.d.ts +2 -1
- package/dist/start-wiring.js +2 -1
- package/dist/surface/mcp/mcp.js +3 -2
- package/dist/surface/mcp/shared-params.d.ts +2 -0
- package/dist/surface/mcp/shared-params.js +31 -0
- package/dist/surface/tools/act/act-observation.d.ts +40 -2
- package/dist/surface/tools/act/act-observation.js +118 -12
- package/dist/surface/tools/act/act-preflight.js +1 -1
- package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
- package/dist/surface/tools/act/act-sequence-retry.js +61 -5
- package/dist/surface/tools/act/after-match.d.ts +4 -1
- package/dist/surface/tools/act/after-match.js +8 -1
- package/dist/surface/tools/act/already-true.js +1 -1
- package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
- package/dist/surface/tools/act/navigation/reload-result.js +25 -2
- package/dist/surface/tools/act/resolve-target.d.ts +15 -1
- package/dist/surface/tools/act/resolve-target.js +24 -1
- package/dist/surface/tools/act/sequence-grade.d.ts +12 -0
- package/dist/surface/tools/act/sequence-grade.js +37 -26
- package/dist/surface/tools/act/settle-in-flight.js +27 -15
- package/dist/surface/tools/act-command.d.ts +23 -0
- package/dist/surface/tools/act-command.js +50 -0
- package/dist/surface/tools/act-sequence-tool.js +78 -50
- package/dist/surface/tools/act-tools.d.ts +1 -13
- package/dist/surface/tools/act-tools.js +74 -86
- package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
- package/dist/surface/tools/agent/build-staleness.js +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +16 -37
- package/dist/surface/tools/browser-tools.js +12 -6
- package/dist/surface/tools/compare-tools.d.ts +11 -0
- package/dist/surface/tools/compare-tools.js +94 -0
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.js +9 -2
- package/dist/surface/tools/error-recovery.d.ts +12 -1
- package/dist/surface/tools/error-recovery.js +91 -21
- package/dist/surface/tools/explore-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +3 -2
- package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
- package/dist/surface/tools/lease-coverage-tools.js +128 -0
- package/dist/surface/tools/lease-hint.d.ts +35 -1
- package/dist/surface/tools/lease-hint.js +42 -0
- package/dist/surface/tools/lease-readiness.d.ts +26 -0
- package/dist/surface/tools/lease-readiness.js +79 -0
- package/dist/surface/tools/lease-seed.d.ts +14 -1
- package/dist/surface/tools/lease-seed.js +35 -3
- package/dist/surface/tools/lease-tools.d.ts +3 -41
- package/dist/surface/tools/lease-tools.js +62 -110
- package/dist/surface/tools/lease-url-identity.d.ts +46 -0
- package/dist/surface/tools/lease-url-identity.js +97 -0
- package/dist/surface/tools/merge-tools.js +99 -2
- package/dist/surface/tools/merged-name-redirect.js +5 -1
- package/dist/surface/tools/observe-tools.js +15 -9
- package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
- package/dist/surface/tools/read/console-pre-attach.js +23 -0
- package/dist/surface/tools/read-tools.js +33 -30
- package/dist/surface/tools/real-input-attempt.d.ts +0 -8
- package/dist/surface/tools/real-input-attempt.js +143 -28
- package/dist/surface/tools/session-ports.d.ts +14 -0
- package/dist/surface/tools/session-ports.js +53 -0
- package/dist/surface/tools/tool-kit.d.ts +6 -0
- package/dist/surface/tools/tool-kit.js +2 -3
- package/dist/surface/tools/tool-surface.js +0 -1
- package/dist/surface/tools/tools.js +28 -5
- package/dist/surface/tools/type-spelling.d.ts +13 -0
- package/dist/surface/tools/type-spelling.js +36 -0
- package/dist/telemetry/agent-client.d.ts +6 -0
- package/dist/telemetry/agent-client.js +10 -0
- package/dist/telemetry/argument-shape.js +1 -0
- package/dist/telemetry/tool-refused.d.ts +6 -3
- package/dist/telemetry/tool-refused.js +8 -3
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/capabilities.mdx +61 -1
- package/docs/cli/affected.mdx +9 -7
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/compare.mdx +94 -0
- package/docs/cli/doctor.mdx +1 -1
- package/docs/cli/grade.mdx +97 -0
- package/docs/cli/license.mdx +9 -7
- package/docs/cli/restart.mdx +7 -5
- package/docs/cli/rollback.mdx +5 -5
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/update.mdx +2 -2
- package/docs/cli/verify.mdx +23 -1
- package/docs/cli.mdx +33 -23
- package/docs/deploy-checks.md +1 -1
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +2 -2
- package/docs/environment.mdx +1 -1
- package/docs/flows.md +11 -1
- package/docs/frameworks.mdx +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/license-activation.mdx +3 -3
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +1 -1
- package/docs/quickstart.mdx +68 -49
- package/docs/skill-setup.md +1 -1
- package/docs/skill.md +14 -13
- package/docs/telemetry.md +2 -2
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/verify.mdx +4 -1
- package/docs/troubleshooting.mdx +1 -1
- package/docs/usage.md +17 -14
- package/package.json +13 -13
- package/dist/features/exhaust/exhaust-run.d.ts +0 -33
- package/dist/features/exhaust/exhaust-run.js +0 -82
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* The page-error side of a lease: listen for the throws the SDK will never see, and tell the
|
|
3
|
+
* wiring when the lease is gone.
|
|
4
|
+
*
|
|
5
|
+
* An app whose module graph throws during the first navigation fires its `error` event while the
|
|
6
|
+
* page still has no Reticle SDK in it — the zero-install reader is only evaluated after `goto`
|
|
7
|
+
* resolves — so nothing on the page records it, and a blank page reported as a bare console-error
|
|
8
|
+
* count (#1966). The server's Playwright handle is attached before the navigation, so it is the
|
|
9
|
+
* one witness that cannot miss the throw. This module owns that wiring for `BrowserPool`; the
|
|
10
|
+
* journal that buffers and journals what arrives lives in `page-error-journal.ts`.
|
|
6
11
|
*/
|
|
7
|
-
|
|
12
|
+
import { RETICLE_EARLY_CAPTURE } from '@reticlehq/core';
|
|
13
|
+
/**
|
|
14
|
+
* Install the lease's uncaught-error listener and its early console queue. Call BEFORE the first
|
|
15
|
+
* navigation — the timing is the whole fix: an import-time throw lands during `goto`, and one
|
|
16
|
+
* attached after it reads a broken page as healthy. Forwarded with the lease id, because the
|
|
17
|
+
* session for this lease usually does not exist yet, so the sink buffers.
|
|
18
|
+
*/
|
|
19
|
+
export async function listenForPageErrors(page, leaseId, hooks) {
|
|
8
20
|
page.onPageError?.((error) => {
|
|
9
21
|
hooks?.onPageError?.(leaseId, error);
|
|
10
22
|
});
|
|
23
|
+
await page.addInitScript?.(queueEarlyConsole, EARLY_CAPTURE_ARG);
|
|
11
24
|
}
|
|
12
25
|
/**
|
|
13
26
|
* Report a closed lease — manual release, reaper and crash reclaim all funnel through the pool's
|
|
@@ -16,3 +29,132 @@ export function listenForPageErrors(page, leaseId, hooks) {
|
|
|
16
29
|
export function reportLeaseClosed(hooks, leaseId) {
|
|
17
30
|
hooks?.onLeaseClosed?.(leaseId);
|
|
18
31
|
}
|
|
32
|
+
/** How many console calls and error events a page keeps for an SDK that has not installed yet. */
|
|
33
|
+
export const EARLY_CAPTURE_LIMIT = 200;
|
|
34
|
+
/**
|
|
35
|
+
* The argument for `queueEarlyConsole`. Every driven page gets the same one: a pool lease, and
|
|
36
|
+
* `verify`'s own page. It looks for the SDK every `pollMs`, for at most `maxPolls` looks (30 s).
|
|
37
|
+
*/
|
|
38
|
+
export const EARLY_CAPTURE_ARG = {
|
|
39
|
+
key: RETICLE_EARLY_CAPTURE,
|
|
40
|
+
limit: EARLY_CAPTURE_LIMIT,
|
|
41
|
+
pollMs: 50,
|
|
42
|
+
maxPolls: 600,
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Init script: keep the console errors and warnings a page logs before the SDK installs.
|
|
46
|
+
*
|
|
47
|
+
* An app connects the SDK from a module script or a bundle, and both run after the page's own
|
|
48
|
+
* classic scripts. An error such a script logs while the page loads was lost, so "no console
|
|
49
|
+
* errors" passed on a page that logged one on every load. Playwright's own console event is not a
|
|
50
|
+
* substitute: it also reports messages the browser writes itself (a failed WebSocket, which is how
|
|
51
|
+
* the SDK's own first dial can fail), and the SDK's console channel has never counted those.
|
|
52
|
+
*
|
|
53
|
+
* So this keeps the RAW calls and events. Once the SDK has wrapped the console, this hands them to
|
|
54
|
+
* it: each call goes through the console again and each event is dispatched again, so the SDK's own
|
|
55
|
+
* handlers report them and the meaning of "console error" stays the SDK's. The SDK needs no code for
|
|
56
|
+
* this, so a page that Reticle does not drive downloads nothing for it. Entries the bound did not
|
|
57
|
+
* keep are declared with one console warning. Must stay self-contained: Playwright serialises the
|
|
58
|
+
* function source into the page.
|
|
59
|
+
*/
|
|
60
|
+
export function queueEarlyConsole(arg) {
|
|
61
|
+
const win = globalThis.window;
|
|
62
|
+
if (win === undefined || win[arg.key] !== undefined) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const capture = { queue: [], dropped: 0 };
|
|
66
|
+
win[arg.key] = capture;
|
|
67
|
+
const keep = (item) => {
|
|
68
|
+
if (win[arg.key] !== capture) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (capture.queue.length < arg.limit) {
|
|
72
|
+
capture.queue.push(item);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
capture.dropped += 1;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const mine = {};
|
|
79
|
+
for (const method of ['error', 'warn']) {
|
|
80
|
+
const original = win.console[method];
|
|
81
|
+
const wrapper = (...args) => {
|
|
82
|
+
keep({ method, args });
|
|
83
|
+
original.apply(win.console, args);
|
|
84
|
+
};
|
|
85
|
+
mine[method] = wrapper;
|
|
86
|
+
win.console[method] = wrapper;
|
|
87
|
+
}
|
|
88
|
+
win.addEventListener('error', (event) => keep({ event }), true);
|
|
89
|
+
win.addEventListener('unhandledrejection', (event) => keep({ event }), true);
|
|
90
|
+
// A copy of a kept event: an event that has been dispatched once is copied, not sent again.
|
|
91
|
+
const copy = (event) => {
|
|
92
|
+
if ('reason' in event) {
|
|
93
|
+
return new win.PromiseRejectionEvent('unhandledrejection', {
|
|
94
|
+
promise: event['promise'],
|
|
95
|
+
reason: event['reason'],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const init = {};
|
|
99
|
+
for (const field of ['message', 'filename', 'lineno', 'colno', 'error']) {
|
|
100
|
+
if (event[field] !== undefined) {
|
|
101
|
+
init[field] = event[field];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return new win.ErrorEvent('error', init);
|
|
105
|
+
};
|
|
106
|
+
// A `<script>`, `<img>` or `<link>` that failed to load dispatches a plain `Event` on its element,
|
|
107
|
+
// and only the element names the URL. A copy on `window` loses it, and the SDK reported an error
|
|
108
|
+
// with no message, no source and line 0 (#2220). So such an event is sent again from the same
|
|
109
|
+
// element, and the SDK's capture listener on `window` reads the tag and the URL. A listener added
|
|
110
|
+
// after the SDK's stops it on `window`, so the page's own handlers on the element do not run twice.
|
|
111
|
+
// An element that left the page cannot reach `window`, so it gets the copy.
|
|
112
|
+
const resend = (event) => {
|
|
113
|
+
const target = event['target'];
|
|
114
|
+
if ('message' in event || 'reason' in event || true !== target?.isConnected) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const again = new win.Event('error');
|
|
118
|
+
const stop = (seen) => {
|
|
119
|
+
if (seen === again) {
|
|
120
|
+
again.stopPropagation();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
win.addEventListener('error', stop, true);
|
|
124
|
+
try {
|
|
125
|
+
target.dispatchEvent?.(again);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
win.removeEventListener('error', stop, true);
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
};
|
|
132
|
+
let polls = 0;
|
|
133
|
+
const timer = win.setInterval(() => {
|
|
134
|
+
polls += 1;
|
|
135
|
+
// The SDK has installed when the console is no longer this script's own wrapper.
|
|
136
|
+
const installed = win.console.error !== mine.error;
|
|
137
|
+
if (!installed && polls < arg.maxPolls) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
win.clearInterval(timer);
|
|
141
|
+
win[arg.key] = null;
|
|
142
|
+
if (!installed) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
for (const item of capture.queue) {
|
|
146
|
+
if ('method' in item) {
|
|
147
|
+
win.console[item['method']](...item['args']);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const event = item['event'];
|
|
151
|
+
if (!resend(event)) {
|
|
152
|
+
win.dispatchEvent(copy(event));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (capture.dropped > 0) {
|
|
157
|
+
win.console.warn(`reticle: ${String(capture.dropped)} early console entries were not kept`);
|
|
158
|
+
}
|
|
159
|
+
}, arg.pollMs);
|
|
160
|
+
}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* only the redundant second copy is cost, not wrong evidence.
|
|
26
26
|
*/
|
|
27
27
|
import { type ReticleEvent } from '@reticlehq/core';
|
|
28
|
-
import type { PooledPageError } from './pool-contract.js';
|
|
28
|
+
import type { PooledDialog, PooledPageError } from './pool-contract.js';
|
|
29
29
|
/** The session surface the journal needs. The real `Session` satisfies this structurally. */
|
|
30
30
|
export interface JournalableSession {
|
|
31
31
|
readonly id: string;
|
|
@@ -43,6 +43,12 @@ export interface PageErrorJournal {
|
|
|
43
43
|
onLeaseClosed(leaseId: string): void;
|
|
44
44
|
/** The bridge hook — register with `bridge.attachSessionEnd`. */
|
|
45
45
|
onSessionEnd(sessionId: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* The pool's dialog sink — hand it to `BrowserPoolOptions.onDialog`. Journals the dismissal as
|
|
48
|
+
* `dialog.opened` on the lease's session, so a verdict reads it as the SDK's own answer. A dialog
|
|
49
|
+
* before the session connects is dropped: no action window can hold it.
|
|
50
|
+
*/
|
|
51
|
+
onPoolDialog(leaseId: string, dialog: Pick<PooledDialog, 'kind' | 'message'>): void;
|
|
46
52
|
}
|
|
47
53
|
/**
|
|
48
54
|
* Fold the two spellings of one throw into one identity. Chromium prefixes a window-reported
|
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
* only the redundant second copy is cost, not wrong evidence.
|
|
26
26
|
*/
|
|
27
27
|
import { CONSOLE_EVENT, EventType, RETICLE_URL_PARAM } from '@reticlehq/core';
|
|
28
|
+
/**
|
|
29
|
+
* What a dismissal answers, per dialog kind the wire knows. The same replies the SDK gives
|
|
30
|
+
* (`observers/dialogs.ts`): `confirm` is "no", `prompt` is no value, `alert` has no answer.
|
|
31
|
+
* A kind not in this map (`beforeunload`) is not a `dialog.opened` and is not reported.
|
|
32
|
+
*/
|
|
33
|
+
const DISMISSED_ANSWER = {
|
|
34
|
+
alert: {},
|
|
35
|
+
confirm: { answered: false },
|
|
36
|
+
prompt: { answered: null },
|
|
37
|
+
};
|
|
28
38
|
/** How many errors wait per lease for a session that may never connect. Older ones give way. */
|
|
29
39
|
const MAX_BUFFERED_PER_LEASE = 8;
|
|
30
40
|
/** How many recent error identities a session tracks — the dedupe memory for both sources. */
|
|
@@ -177,5 +187,22 @@ export function makePageErrorJournal(deps) {
|
|
|
177
187
|
onSessionEnd(sessionId) {
|
|
178
188
|
seen.delete(sessionId);
|
|
179
189
|
},
|
|
190
|
+
onPoolDialog(leaseId, dialog) {
|
|
191
|
+
// An own key only: an inherited name such as `toString` is not a dialog kind.
|
|
192
|
+
const kind = dialog.kind;
|
|
193
|
+
const answer = kind !== undefined && Object.hasOwn(DISMISSED_ANSWER, kind)
|
|
194
|
+
? DISMISSED_ANSWER[kind]
|
|
195
|
+
: undefined;
|
|
196
|
+
const session = deps.resolveSession(leaseId);
|
|
197
|
+
if (answer === undefined || session === undefined) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
session.pushEvent({
|
|
201
|
+
t: 0, // pushEvent re-stamps with the session's own clock
|
|
202
|
+
type: EventType.DIALOG_OPENED,
|
|
203
|
+
sessionId: session.id,
|
|
204
|
+
data: { kind: dialog.kind, message: dialog.message, ...answer },
|
|
205
|
+
});
|
|
206
|
+
},
|
|
180
207
|
};
|
|
181
208
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Headless by default — the pool exists for fault-tolerant headless multi-agent testing.
|
|
7
7
|
*/
|
|
8
|
+
import { startJsCoverage, takeJsCoverage } from '../input/js-coverage.js';
|
|
8
9
|
import { BrowserLaunchKind } from '@reticlehq/core/telemetry';
|
|
9
10
|
import { ActionType, HIDE_RETICLE_CHROME_CSS, RETICLE_OVERLAY_SELECTOR } from '@reticlehq/core';
|
|
10
11
|
import { launchChromium } from '../../launch-chromium.js';
|
|
@@ -15,6 +16,8 @@ import { chromiumInstallCommand, chromiumInstallDepsCommand, bundledPlaywrightVe
|
|
|
15
16
|
import { installNetworkMocks } from '../input/network-mock.js';
|
|
16
17
|
import { performGesture, withChromePassThrough, } from '../input/real-input.js';
|
|
17
18
|
import { widenDocumentCsp } from './csp-widen.js';
|
|
19
|
+
import { contextEmulationOf } from './lease-emulation.js';
|
|
20
|
+
import { determinismConfigOf, installDeterminism } from './determinism.js';
|
|
18
21
|
/** A key addresses focus, not a point; the gesture driver takes a box for every action. */
|
|
19
22
|
const NO_BOX = { x: 0, y: 0, width: 0, height: 0 };
|
|
20
23
|
const sleepFor = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -93,20 +96,48 @@ export function gotoOptions(timeoutMs) {
|
|
|
93
96
|
...(timeoutMs === undefined ? {} : { timeout: timeoutMs }),
|
|
94
97
|
};
|
|
95
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Reload with the cache ignored, as Cmd+Shift+R does (#1785).
|
|
101
|
+
*
|
|
102
|
+
* Playwright's `page.reload()` is a plain reload: a module with a fresh cache entry is not fetched
|
|
103
|
+
* again. The DevTools protocol's `Page.reload { ignoreCache: true }` is the browser's own hard
|
|
104
|
+
* reload. The pool launches Chromium, so the protocol is always there.
|
|
105
|
+
*/
|
|
106
|
+
async function reloadIgnoringCache(page) {
|
|
107
|
+
const cdp = await page.context().newCDPSession(page);
|
|
108
|
+
try {
|
|
109
|
+
await cdp.send('Page.reload', { ignoreCache: true });
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
await cdp.detach();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
96
115
|
function wrapBrowser(browser, headless) {
|
|
97
116
|
return {
|
|
98
117
|
isConnected: () => browser.isConnected(),
|
|
99
118
|
newContext: async (opts) => {
|
|
100
|
-
// Media and
|
|
101
|
-
|
|
119
|
+
// Media, viewport and pointer ride on the context, so the first paint of the first page has
|
|
120
|
+
// them. `pointer` becomes `hasTouch` here — see contextEmulationOf.
|
|
121
|
+
const { ignoreHTTPSErrors, jsCoverage, ...emulate } = opts ?? {};
|
|
122
|
+
const covering = new WeakSet();
|
|
123
|
+
// A deterministic context needs a fixed viewport: a density is not set on a window's own size.
|
|
124
|
+
const deterministic = emulate.deterministic;
|
|
102
125
|
const context = await browser.newContext({
|
|
103
126
|
...(true === ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : {}),
|
|
104
|
-
...emulate,
|
|
105
|
-
...pageViewportOptions(headless, emulate.viewport),
|
|
127
|
+
...contextEmulationOf(emulate),
|
|
128
|
+
...pageViewportOptions(headless || deterministic !== undefined, emulate.viewport),
|
|
106
129
|
});
|
|
130
|
+
// On the context, so every page and frame of the lease gets it before its first script.
|
|
131
|
+
if (deterministic !== undefined) {
|
|
132
|
+
await context.addInitScript(installDeterminism, determinismConfigOf(deterministic));
|
|
133
|
+
}
|
|
107
134
|
return {
|
|
108
135
|
newPage: async () => {
|
|
109
136
|
const page = await context.newPage();
|
|
137
|
+
// Before the first navigation, so the code that runs while the page loads is counted.
|
|
138
|
+
if (true === jsCoverage) {
|
|
139
|
+
await startJsCoverage(page, covering);
|
|
140
|
+
}
|
|
110
141
|
return {
|
|
111
142
|
goto: (url, opts) => page.goto(url, gotoOptions(opts?.timeoutMs)),
|
|
112
143
|
close: () => page.close(),
|
|
@@ -129,6 +160,7 @@ function wrapBrowser(browser, headless) {
|
|
|
129
160
|
await page.mouse.move(x + 1, y);
|
|
130
161
|
await page.mouse.move(x, y);
|
|
131
162
|
}),
|
|
163
|
+
reloadIgnoringCache: () => reloadIgnoringCache(page),
|
|
132
164
|
// The same driver `reticle drive` presses with, so a held key and a chord read alike.
|
|
133
165
|
emulateMedia: (media) => page.emulateMedia(media),
|
|
134
166
|
press: (args) => performGesture(page, ActionType.PRESS, NO_BOX, args, sleepFor),
|
|
@@ -148,6 +180,10 @@ function wrapBrowser(browser, headless) {
|
|
|
148
180
|
});
|
|
149
181
|
},
|
|
150
182
|
setViewport: (size) => page.setViewportSize(size),
|
|
183
|
+
jsCoverage: {
|
|
184
|
+
start: () => startJsCoverage(page, covering),
|
|
185
|
+
take: () => covering.has(page) ? takeJsCoverage(page, covering) : Promise.resolve(undefined),
|
|
186
|
+
},
|
|
151
187
|
bringToFront: () => page.bringToFront(),
|
|
152
188
|
onCrash: (handler) => page.on('crash', handler),
|
|
153
189
|
onConsole: (handler) => page.on('console', (msg) => handler(msg.text())),
|
|
@@ -157,7 +193,11 @@ function wrapBrowser(browser, headless) {
|
|
|
157
193
|
message: error.message,
|
|
158
194
|
...(error.stack === undefined ? {} : { stack: error.stack }),
|
|
159
195
|
})),
|
|
160
|
-
onDialog: (handler) => page.on('dialog', (dialog) => handler({
|
|
196
|
+
onDialog: (handler) => page.on('dialog', (dialog) => handler({
|
|
197
|
+
message: dialog.message(),
|
|
198
|
+
kind: dialog.type(),
|
|
199
|
+
dismiss: () => dialog.dismiss(),
|
|
200
|
+
})),
|
|
161
201
|
addInitScript: async (script, arg) => {
|
|
162
202
|
const handle = (await page.addInitScript(script, arg));
|
|
163
203
|
const dispose = handle?.dispose;
|
|
@@ -216,11 +256,13 @@ function pathFromPlaywrightError(msg) {
|
|
|
216
256
|
// Playwright puts the path at the end of the line, and a Windows path can hold a space
|
|
217
257
|
// (for example C:\Users\Jo Doe). Read to the end of the line and trim the result.
|
|
218
258
|
const found = /executable doesn.?t exist at (.+)$/im.exec(msg);
|
|
219
|
-
if (null === found)
|
|
259
|
+
if (null === found) {
|
|
220
260
|
return undefined;
|
|
261
|
+
}
|
|
221
262
|
const captured = found[1];
|
|
222
|
-
if (undefined === captured)
|
|
263
|
+
if (undefined === captured) {
|
|
223
264
|
return undefined;
|
|
265
|
+
}
|
|
224
266
|
const path = captured.trim();
|
|
225
267
|
return '' === path ? undefined : path;
|
|
226
268
|
}
|
|
@@ -267,8 +309,9 @@ export function chromiumLaunchHint(msg) {
|
|
|
267
309
|
// Checked FIRST: Playwright's host-requirement validation error also contains
|
|
268
310
|
// "browserType.launch", so the generic check below would otherwise misdiagnose it as a
|
|
269
311
|
// missing binary and send the user through a fix that changes nothing.
|
|
270
|
-
if (/host system is missing dependencies/i.test(msg))
|
|
312
|
+
if (/host system is missing dependencies/i.test(msg)) {
|
|
271
313
|
return CHROMIUM_MISSING_DEPS_HINT;
|
|
314
|
+
}
|
|
272
315
|
if (/executable doesn.?t exist|playwright install|browsertype\.launch/i.test(msg)) {
|
|
273
316
|
return chromiumMissingHint(pathFromPlaywrightError(msg));
|
|
274
317
|
}
|
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { RealInputArgs, RealInputResult } from '../input/real-input.js';
|
|
9
9
|
import type { LeaseEmulation, LeaseMedia } from './lease-emulation.js';
|
|
10
|
+
import type { ScriptCoverage } from '../input/js-coverage.js';
|
|
11
|
+
/** JS coverage on one page: start it, and take what ran since the previous take. */
|
|
12
|
+
export interface PooledCoverage {
|
|
13
|
+
/** False when the page cannot measure coverage. */
|
|
14
|
+
start(): Promise<boolean>;
|
|
15
|
+
/** Undefined when coverage was not running; the first take does not start it. */
|
|
16
|
+
take(): Promise<ScriptCoverage[] | undefined>;
|
|
17
|
+
}
|
|
10
18
|
/** The minimal page surface the pool drives. Real Playwright `Page` satisfies this. */
|
|
11
19
|
export interface PooledPage {
|
|
12
20
|
goto(url: string, opts?: {
|
|
@@ -47,6 +55,11 @@ export interface PooledPage {
|
|
|
47
55
|
* page without it keeps the in-page dispatcher, whose synthetic Tab moves no focus.
|
|
48
56
|
*/
|
|
49
57
|
press?(args: RealInputArgs): Promise<RealInputResult>;
|
|
58
|
+
/**
|
|
59
|
+
* Reload the page with the browser cache ignored for the document and every subresource, as
|
|
60
|
+
* Cmd+Shift+R does. OPTIONAL: without it a hard reload can only re-fetch the document (#1785).
|
|
61
|
+
*/
|
|
62
|
+
reloadIgnoringCache?(): Promise<void>;
|
|
50
63
|
/** Set prefers-color-scheme and prefers-reduced-motion. OPTIONAL: without it the media stay. */
|
|
51
64
|
emulateMedia?(media: LeaseMedia): Promise<void>;
|
|
52
65
|
/** Evaluate a script in the page's current document. OPTIONAL: without it no reader is injected. */
|
|
@@ -92,11 +105,18 @@ export interface PooledPage {
|
|
|
92
105
|
* hidden, and a hidden tab throttles timers and rAF so nothing on it verifies (#1351).
|
|
93
106
|
*/
|
|
94
107
|
bringToFront?(): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* JS coverage of this page. OPTIONAL, like `installMocks`: a page without it makes the
|
|
110
|
+
* coverage tool refuse rather than report that no code ran.
|
|
111
|
+
*/
|
|
112
|
+
jsCoverage?: PooledCoverage;
|
|
95
113
|
}
|
|
96
114
|
/** A native dialog the page opened, handed to the pool so it can be dismissed instead of left blocking. */
|
|
97
115
|
export interface PooledDialog {
|
|
98
116
|
/** The dialog's message text — kept for diagnostics (`BrowserPool#lastDialogMessage`). */
|
|
99
117
|
readonly message: string;
|
|
118
|
+
/** `alert`, `confirm`, `prompt` or `beforeunload`, as the engine names it. Absent: not known. */
|
|
119
|
+
readonly kind?: string;
|
|
100
120
|
dismiss(): Promise<void>;
|
|
101
121
|
}
|
|
102
122
|
/**
|
|
@@ -133,6 +153,8 @@ export interface PooledMockRule {
|
|
|
133
153
|
contentType?: string;
|
|
134
154
|
delayMs?: number;
|
|
135
155
|
abort?: boolean;
|
|
156
|
+
/** Send the request on to this origin; the path and query stay (see network-mock.ts). */
|
|
157
|
+
forwardTo?: string;
|
|
136
158
|
}
|
|
137
159
|
/** An isolated browsing context (cookies/storage). Real Playwright `BrowserContext` satisfies this. */
|
|
138
160
|
export interface PooledContext {
|
|
@@ -159,9 +181,13 @@ export interface PooledContext {
|
|
|
159
181
|
/** The launched browser. Real Playwright `Browser` satisfies this. */
|
|
160
182
|
export interface PooledBrowser {
|
|
161
183
|
isConnected(): boolean;
|
|
162
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* `ignoreHTTPSErrors` accepts a self-signed or local-CA certificate for this context only.
|
|
186
|
+
* `jsCoverage` starts JS coverage on each page before its first script runs.
|
|
187
|
+
*/
|
|
163
188
|
newContext(opts?: {
|
|
164
189
|
ignoreHTTPSErrors?: boolean;
|
|
190
|
+
jsCoverage?: boolean;
|
|
165
191
|
} & LeaseEmulation): Promise<PooledContext>;
|
|
166
192
|
close(): Promise<void>;
|
|
167
193
|
/** Fires when the browser process dies/crashes so the pool can relaunch on the next acquire. */
|
|
@@ -2,7 +2,37 @@
|
|
|
2
2
|
* The pool's lease handle and lease record, its options, and its limits: the types and constants
|
|
3
3
|
* `BrowserPool` is built from.
|
|
4
4
|
*/
|
|
5
|
-
import type { InitScriptHandle, Launcher, PooledBrowser, PooledContext, PooledPage, PooledPageError } from './pool-contract.js';
|
|
5
|
+
import type { InitScriptHandle, Launcher, PooledBrowser, PooledContext, PooledDialog, PooledPage, PooledPageError } from './pool-contract.js';
|
|
6
|
+
import type { SeedStorage } from '@reticlehq/core';
|
|
7
|
+
import type { LeaseEmulation } from './lease-emulation.js';
|
|
8
|
+
/** What one `BrowserPool#acquire` asks for. Every field is optional. */
|
|
9
|
+
export interface AcquireOptions {
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
/**
|
|
12
|
+
* `'wait'` (the default) queues behind the cap. `'refuse'` is for a caller that has a person
|
|
13
|
+
* or an agent waiting on the answer and no way to see a queue: it is told the pool is full.
|
|
14
|
+
*/
|
|
15
|
+
whenFull?: 'wait' | 'refuse';
|
|
16
|
+
sessionId?: string;
|
|
17
|
+
seedStorage?: SeedStorage;
|
|
18
|
+
owner?: string;
|
|
19
|
+
/** In a browser with a window the person can watch. */
|
|
20
|
+
headed?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* JavaScript installed before any page script runs, for this lease only. A view built for a
|
|
23
|
+
* desktop shell calls bindings the shell injects (`window.go`, `window.runtime`, a preload
|
|
24
|
+
* object) on mount; a stub here is what lets it reach its data path in a leased tab.
|
|
25
|
+
*/
|
|
26
|
+
initScript?: string;
|
|
27
|
+
/** Granted on the lease's origin before the first navigation, so the first render sees them. */
|
|
28
|
+
permissions?: readonly string[];
|
|
29
|
+
/** Accept a self-signed or local-CA dev certificate for this lease's context only (#1255). */
|
|
30
|
+
ignoreHTTPSErrors?: boolean;
|
|
31
|
+
/** Media and viewport set on the context, so the first paint already has them. */
|
|
32
|
+
emulate?: LeaseEmulation;
|
|
33
|
+
/** Start JS coverage before the page's first script, so load-time code is counted. */
|
|
34
|
+
jsCoverage?: boolean;
|
|
35
|
+
}
|
|
6
36
|
/** A leased context+page for one flow. `release` frees the slot and closes the context. */
|
|
7
37
|
export interface Lease {
|
|
8
38
|
readonly sessionId: string;
|
|
@@ -67,6 +97,11 @@ export interface BrowserPoolOptions {
|
|
|
67
97
|
* holding it for the life of the daemon. Optional: no sink, nothing to clean up.
|
|
68
98
|
*/
|
|
69
99
|
onLeaseClosed?: (leaseId: string) => void;
|
|
100
|
+
/**
|
|
101
|
+
* Sink for each native dialog the pool dismissed, with the lease id. The dismissal is an answer
|
|
102
|
+
* of "no" on the app's behalf, so the wiring records it for the verdict (#1350). Optional.
|
|
103
|
+
*/
|
|
104
|
+
onDialog?: (leaseId: string, dialog: Pick<PooledDialog, 'kind' | 'message'>) => void;
|
|
70
105
|
/**
|
|
71
106
|
* Launches a browser with a window, for a lease somebody wants to watch. Launched only when the
|
|
72
107
|
* first headed lease is asked for. Absent: a headed lease is refused.
|
|
@@ -25,6 +25,14 @@ export declare function zeroInstallScript(bundle: string, opts: InjectedConnect)
|
|
|
25
25
|
export declare class NotHtmlDocumentError extends Error {
|
|
26
26
|
constructor();
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Run the reader in a leased page now, and again before the page's own scripts on each later load.
|
|
30
|
+
* False when there is no reader or the page cannot run one; throws when the page is not HTML.
|
|
31
|
+
*/
|
|
32
|
+
export declare function injectReaderInto(page: {
|
|
33
|
+
evaluate?(script: string): Promise<unknown>;
|
|
34
|
+
addInitScript?(script: string): unknown;
|
|
35
|
+
}, script: string | undefined): Promise<boolean>;
|
|
28
36
|
/**
|
|
29
37
|
* The single-file build, or undefined when this install has none.
|
|
30
38
|
*
|
|
@@ -35,6 +35,14 @@ const HTML_CONTENT_TYPES = ['text/html', 'application/xhtml+xml'];
|
|
|
35
35
|
* reads `document.contentType`, because an init script runs before the parser makes the root
|
|
36
36
|
* element. The check binds no name, so it cannot collide with a name that the bundle declares.
|
|
37
37
|
*/
|
|
38
|
+
/*
|
|
39
|
+
* Run in the top frame only. Playwright runs an init script in every frame, and a rich-text editor's
|
|
40
|
+
* editing area is an iframe whose <body> IS the document being edited: the SDK mounted its HUD there,
|
|
41
|
+
* and saving the form stored the HUD markup in the app's data (Dolibarr product and project
|
|
42
|
+
* descriptions). The top frame's SDK already reads same-origin iframes, so a child frame needs nothing.
|
|
43
|
+
* A context with no `top` (a test sandbox) counts as the top frame.
|
|
44
|
+
*/
|
|
45
|
+
const TOP_FRAME_ONLY = 'if (globalThis.top !== undefined && globalThis.self !== undefined && globalThis.top !== globalThis.self) return;';
|
|
38
46
|
const NOT_HTML_GUARD = `if (globalThis.document !== undefined && !${JSON.stringify(HTML_CONTENT_TYPES)}.includes(globalThis.document.contentType)) ` +
|
|
39
47
|
`return ${JSON.stringify(READER_NOT_HTML)};`;
|
|
40
48
|
/*
|
|
@@ -115,7 +123,7 @@ const ZOD_JITLESS = `(${ZOD_CONFIG} ??= {}).jitless ??= true;`;
|
|
|
115
123
|
*/
|
|
116
124
|
export function zeroInstallScript(bundle, opts) {
|
|
117
125
|
const args = JSON.stringify(injectedConnectArgs(opts));
|
|
118
|
-
return `(() => {\n${NOT_HTML_GUARD}\nif (!globalThis.__reticleInstance) {\n${EARLY_KEEP}\n${ZOD_JITLESS}\n${bundle}\n}\n${CONNECT_WHEN_DOM_READY(args)}\n})();`;
|
|
126
|
+
return `(() => {\n${TOP_FRAME_ONLY}\n${NOT_HTML_GUARD}\nif (!globalThis.__reticleInstance) {\n${EARLY_KEEP}\n${ZOD_JITLESS}\n${bundle}\n}\n${CONNECT_WHEN_DOM_READY(args)}\n})();`;
|
|
119
127
|
}
|
|
120
128
|
/** The page is not an HTML document, so the lease did not give it the zero-install reader. */
|
|
121
129
|
export class NotHtmlDocumentError extends Error {
|
|
@@ -124,6 +132,20 @@ export class NotHtmlDocumentError extends Error {
|
|
|
124
132
|
this.name = 'NotHtmlDocumentError';
|
|
125
133
|
}
|
|
126
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Run the reader in a leased page now, and again before the page's own scripts on each later load.
|
|
137
|
+
* False when there is no reader or the page cannot run one; throws when the page is not HTML.
|
|
138
|
+
*/
|
|
139
|
+
export async function injectReaderInto(page, script) {
|
|
140
|
+
if (script === undefined || page.evaluate === undefined) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
await page.addInitScript?.(script);
|
|
144
|
+
if (READER_NOT_HTML === (await page.evaluate(script))) {
|
|
145
|
+
throw new NotHtmlDocumentError();
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
127
149
|
let cached;
|
|
128
150
|
/**
|
|
129
151
|
* The single-file build, or undefined when this install has none.
|
|
@@ -20,13 +20,14 @@ export interface DevCommand {
|
|
|
20
20
|
directory?: string;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* The direct child folders of `directory` that are apps with a dev command, sorted by name
|
|
23
|
+
* The direct child folders of `directory` that are apps with a dev command, sorted by name: the web
|
|
24
|
+
* apps when there are any, every dev-script folder when there are none (see `preferWebApps`).
|
|
24
25
|
*
|
|
25
26
|
* Only for a folder with no package.json of its own. A task folder holds the app one level down, and
|
|
26
|
-
* no workspace file lists it, so `workspacePackageDirs` never finds it.
|
|
27
|
-
*
|
|
27
|
+
* no workspace file lists it, so `workspacePackageDirs` never finds it. `node_modules`, hidden
|
|
28
|
+
* folders and folders with no dev script are never candidates.
|
|
28
29
|
*/
|
|
29
|
-
export declare function childDevApps(directory: string, read?: (path: string) => string | undefined, listDirs?: (path: string) => readonly string[]): readonly string[];
|
|
30
|
+
export declare function childDevApps(directory: string, read?: (path: string) => string | undefined, listDirs?: (path: string) => readonly string[], writable?: (path: string) => boolean): readonly string[];
|
|
30
31
|
/**
|
|
31
32
|
* The dev command for the project in `directory`, or undefined when there is not one to be sure of.
|
|
32
33
|
*
|
|
@@ -43,7 +44,7 @@ export declare function detectDevCommand(directory: string, read?: (path: string
|
|
|
43
44
|
* init's own workspace listing answers which directories to consider; the root's lockfile picks the
|
|
44
45
|
* package manager, because a workspace package usually has no lockfile of its own.
|
|
45
46
|
*/
|
|
46
|
-
export declare function detectDevCommandInProject(directory: string, read?: (path: string) => string | undefined, listDirs?: (path: string) => readonly string[]): DevCommand | undefined;
|
|
47
|
+
export declare function detectDevCommandInProject(directory: string, read?: (path: string) => string | undefined, listDirs?: (path: string) => readonly string[], writable?: (path: string) => boolean): DevCommand | undefined;
|
|
47
48
|
/**
|
|
48
49
|
* The command that wires THIS project for good: `init`, in the app's own folder, through the
|
|
49
50
|
* one-off runner the project's package manager ships.
|
|
@@ -52,4 +53,4 @@ export declare function detectDevCommandInProject(directory: string, read?: (pat
|
|
|
52
53
|
* time: `init` run in a folder that is not the app stops on a missing package.json, and a runner the
|
|
53
54
|
* machine does not have is "command not found". npx whenever there is doubt — it ships with Node.
|
|
54
55
|
*/
|
|
55
|
-
export declare function initCommandIn(directory: string, read?: (path: string) => string | undefined, listDirs?: (path: string) => readonly string[]): string;
|
|
56
|
+
export declare function initCommandIn(directory: string, read?: (path: string) => string | undefined, listDirs?: (path: string) => readonly string[], writable?: (path: string) => boolean): string;
|