machine-bridge-mcp 2.0.0 → 3.0.0-beta.102
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/CHANGELOG.md +1142 -0
- package/CONTRIBUTING.md +23 -25
- package/GOVERNANCE.md +2 -2
- package/README.md +58 -20
- package/SECURITY.md +161 -120
- package/SUPPORT.md +1 -1
- package/browser-extension/broker-auth.js +83 -0
- package/browser-extension/broker-liveness.js +35 -0
- package/browser-extension/browser-error-boundary.js +96 -0
- package/browser-extension/browser-operations.js +1456 -101
- package/browser-extension/devtools-input.js +195 -34
- package/browser-extension/devtools-observation.js +429 -0
- package/browser-extension/devtools-session.js +48 -0
- package/browser-extension/manifest.json +4 -3
- package/browser-extension/page-automation.js +578 -118
- package/browser-extension/pairing-bootstrap.js +42 -0
- package/browser-extension/pairing.js +28 -12
- package/browser-extension/service-worker.js +126 -106
- package/docs/AGENT_CONTEXT.md +13 -10
- package/docs/ARCHITECTURE.md +159 -67
- package/docs/AUDIT.md +1302 -4
- package/docs/CLIENTS.md +19 -6
- package/docs/COMPUTER_USE.md +297 -0
- package/docs/ENGINEERING.md +72 -13
- package/docs/GETTING_STARTED.md +18 -20
- package/docs/LOCAL_AUTHORIZATION.md +115 -72
- package/docs/LOCAL_AUTOMATION.md +27 -21
- package/docs/LOGGING.md +40 -19
- package/docs/MANAGED_JOBS.md +37 -34
- package/docs/MULTI_ACCOUNT.md +98 -37
- package/docs/OPERATIONS.md +145 -51
- package/docs/OVERVIEW.md +14 -11
- package/docs/POLICY_REFERENCE.md +71 -3
- package/docs/PRIVACY.md +41 -2
- package/docs/PROJECT_STANDARDS.md +29 -6
- package/docs/RELEASING.md +238 -64
- package/docs/TESTING.md +150 -39
- package/docs/THREAT_MODEL.md +164 -67
- package/docs/TOOL_REFERENCE.md +653 -60
- package/docs/UPGRADING.md +78 -18
- package/native/macos/MachineBridgeBackgroundInput.swift +1051 -0
- package/native/macos/MachineBridgeBackgroundInputSmokeFixture.swift +295 -0
- package/native/macos/MachineBridgeTrustBroker.swift +233 -0
- package/package.json +94 -16
- package/scripts/accepted-candidate-tarball.mjs +77 -0
- package/scripts/candidate-runtime-store.mjs +93 -0
- package/scripts/check-plan.mjs +73 -3
- package/scripts/check-runner.mjs +129 -17
- package/scripts/commit-message-check.mjs +5 -2
- package/scripts/consumer-package-security.mjs +293 -0
- package/scripts/coverage-check.mjs +251 -15
- package/scripts/coverage-generation.mjs +41 -0
- package/scripts/coverage-range-merge.mjs +41 -0
- package/scripts/foreground-daemon-recovery.mjs +88 -0
- package/scripts/generate-policy-reference.mjs +28 -5
- package/scripts/generate-tool-reference.mjs +1 -1
- package/scripts/generate-worker-types.mjs +21 -11
- package/scripts/github-backlog.mjs +11 -10
- package/scripts/github-push.mjs +62 -16
- package/scripts/github-release-asset.mjs +58 -0
- package/scripts/github-release.mjs +241 -132
- package/scripts/global-package-installation.mjs +68 -0
- package/scripts/hardened-npm-session.mjs +42 -0
- package/scripts/install-published-prerelease.mjs +198 -0
- package/scripts/local-release-acceptance.mjs +103 -45
- package/scripts/macos-background-input-smoke.mjs +515 -0
- package/scripts/network-retry.mjs +8 -1
- package/scripts/npm-global-prefix.mjs +27 -0
- package/scripts/npm-publication-policy.mjs +17 -0
- package/scripts/official-mcp-conformance.mjs +267 -0
- package/scripts/persistent-activation-process.mjs +101 -0
- package/scripts/prepare-pinned-npm.mjs +12 -69
- package/scripts/prerelease-activation.mjs +160 -0
- package/scripts/privacy-check.mjs +14 -6
- package/scripts/promotion-digest.mjs +128 -0
- package/scripts/publish-npm.mjs +260 -0
- package/scripts/published-release.mjs +132 -0
- package/scripts/release-acceptance.mjs +62 -54
- package/scripts/release-browser-extension-store.mjs +172 -0
- package/scripts/release-candidate-manifest.mjs +52 -0
- package/scripts/release-channel.mjs +106 -0
- package/scripts/release-diagnostic.mjs +41 -0
- package/scripts/release-impact-check.mjs +12 -22
- package/scripts/release-oauth-canary-core.mjs +325 -0
- package/scripts/release-oauth-canary-evidence.mjs +100 -0
- package/scripts/release-oauth-canary.mjs +127 -0
- package/scripts/release-publication-guard.mjs +67 -0
- package/scripts/release-soak.mjs +284 -0
- package/scripts/release-state.mjs +1 -1
- package/scripts/run-checks.mjs +44 -7
- package/scripts/run-worker-dry-run.mjs +21 -0
- package/scripts/sarif-security-gate.mjs +2 -1
- package/scripts/sbom-check.mjs +106 -0
- package/scripts/start-release-candidate.mjs +267 -44
- package/scripts/syntax-check.mjs +36 -14
- package/scripts/verification-generation-guard.mjs +15 -0
- package/scripts/verification-idle-sleep-guard.mjs +37 -0
- package/scripts/verification-state.mjs +108 -0
- package/scripts/wrangler-command-lifecycle.mjs +115 -0
- package/src/local/account-access.mjs +13 -1
- package/src/local/account-admin-response.mjs +92 -0
- package/src/local/account-admin.mjs +52 -19
- package/src/local/agent-context-projection.mjs +26 -7
- package/src/local/agent-context.mjs +88 -49
- package/src/local/agent-skill-discovery.mjs +16 -5
- package/src/local/app-automation-macos-jxa.mjs +350 -0
- package/src/local/app-automation.mjs +873 -163
- package/src/local/application-capability-projection.mjs +24 -0
- package/src/local/authority-context.mjs +106 -0
- package/src/local/autostart-log-maintenance.mjs +36 -0
- package/src/local/browser-bridge-http.mjs +5 -3
- package/src/local/browser-bridge.mjs +109 -31
- package/src/local/browser-broker-auth-http.mjs +67 -0
- package/src/local/browser-broker-auth.mjs +98 -0
- package/src/local/browser-broker-routes.mjs +63 -20
- package/src/local/browser-broker-server.mjs +17 -7
- package/src/local/browser-command.mjs +79 -30
- package/src/local/browser-computer-observation-service.mjs +266 -0
- package/src/local/browser-extension-identity.mjs +50 -0
- package/src/local/browser-extension-path.mjs +22 -0
- package/src/local/browser-extension-protocol.mjs +50 -11
- package/src/local/browser-operation-service.mjs +147 -117
- package/src/local/browser-pairing-grant.mjs +109 -0
- package/src/local/browser-pairing-http.mjs +36 -0
- package/src/local/browser-pairing-launch.mjs +58 -0
- package/src/local/browser-pairing-store.mjs +85 -48
- package/src/local/browser-request-registry.mjs +40 -14
- package/src/local/browser-request-settlement.mjs +78 -0
- package/src/local/browser-resource-input.mjs +74 -0
- package/src/local/browser-trusted-input-health.mjs +63 -0
- package/src/local/call-authority.mjs +21 -0
- package/src/local/call-capacity.mjs +45 -0
- package/src/local/call-registry-drain.mjs +54 -0
- package/src/local/call-registry.mjs +30 -15
- package/src/local/capability-observer.mjs +5 -0
- package/src/local/capability-ranking.mjs +3 -2
- package/src/local/child-process-settlement.mjs +132 -0
- package/src/local/cli-account-admin.mjs +40 -6
- package/src/local/cli-activate.mjs +162 -0
- package/src/local/cli-local-admin.mjs +20 -45
- package/src/local/cli-options.mjs +12 -13
- package/src/local/cli-ready-output.mjs +52 -0
- package/src/local/cli-service.mjs +91 -11
- package/src/local/cli.mjs +232 -150
- package/src/local/computer-use-application-observation.mjs +224 -0
- package/src/local/computer-use-arguments.mjs +270 -0
- package/src/local/computer-use-deadline.mjs +50 -0
- package/src/local/computer-use-dispatch-settlement.mjs +40 -0
- package/src/local/computer-use-expectation.mjs +171 -0
- package/src/local/computer-use-observation.mjs +769 -0
- package/src/local/computer-use-recovery.mjs +82 -0
- package/src/local/computer-use-result-budget.mjs +145 -0
- package/src/local/computer-use-snapshot-store.mjs +67 -0
- package/src/local/computer-use.mjs +2255 -0
- package/src/local/daemon-http-relay-auth.mjs +26 -0
- package/src/local/daemon-http-relay-connection.mjs +298 -0
- package/src/local/daemon-http-relay-request.mjs +64 -0
- package/src/local/daemon-http-relay-sequence.mjs +49 -0
- package/src/local/daemon-process.mjs +40 -7
- package/src/local/delegated-process-sandbox.mjs +148 -0
- package/src/local/device-identity.mjs +156 -49
- package/src/local/device-root-provider.mjs +81 -0
- package/src/local/directory-metadata.mjs +47 -0
- package/src/local/doctor-reporting.mjs +23 -0
- package/src/local/durable-process-spec.mjs +80 -0
- package/src/local/errors.mjs +25 -15
- package/src/local/exclusive-file.mjs +74 -26
- package/src/local/execution-limits.mjs +9 -0
- package/src/local/execution-routing.mjs +276 -0
- package/src/local/execution-surface.mjs +23 -0
- package/src/local/file-mutation-coordinator.mjs +47 -0
- package/src/local/file-snapshot-preservation.mjs +24 -0
- package/src/local/filesystem-identity.mjs +54 -0
- package/src/local/fixed-process-environment.mjs +21 -0
- package/src/local/full-access-test.mjs +31 -13
- package/src/local/git-commit.mjs +69 -0
- package/src/local/git-config-safety.mjs +55 -0
- package/src/local/git-log-parser.mjs +41 -0
- package/src/local/git-metadata-boundary.mjs +77 -0
- package/src/local/git-metadata-tree-safety.mjs +59 -0
- package/src/local/git-operation-state.mjs +19 -0
- package/src/local/git-service.mjs +165 -30
- package/src/local/hardened-npm-download-timeout.mjs +46 -0
- package/src/local/hardened-npm-download.mjs +109 -0
- package/src/local/hardened-npm-extract.mjs +20 -0
- package/src/local/hardened-npm-verification.mjs +104 -0
- package/src/local/hardened-npm.mjs +199 -0
- package/src/local/job-runner.mjs +244 -89
- package/src/local/lifecycle.mjs +6 -0
- package/src/local/log.mjs +19 -45
- package/src/local/macos-background-input.mjs +398 -0
- package/src/local/macos-idle-sleep-assertion.mjs +65 -0
- package/src/local/macos-trust-broker.mjs +404 -0
- package/src/local/managed-job-cancellation.mjs +42 -0
- package/src/local/managed-job-capacity.mjs +16 -0
- package/src/local/managed-job-directory-generation.mjs +60 -0
- package/src/local/managed-job-directory.mjs +75 -0
- package/src/local/managed-job-durable-process.mjs +49 -0
- package/src/local/managed-job-lock.mjs +46 -14
- package/src/local/managed-job-plan.mjs +38 -14
- package/src/local/managed-job-projection.mjs +23 -15
- package/src/local/managed-job-retention.mjs +128 -0
- package/src/local/managed-job-runner-claim.mjs +70 -0
- package/src/local/managed-job-runner.mjs +35 -10
- package/src/local/managed-job-storage.mjs +22 -6
- package/src/local/managed-job-terminal-maintenance.mjs +40 -0
- package/src/local/managed-job-terminal.mjs +150 -0
- package/src/local/managed-jobs.mjs +424 -287
- package/src/local/npm-cli.mjs +42 -0
- package/src/local/npm-environment.mjs +37 -0
- package/src/local/operation-authorization.mjs +68 -324
- package/src/local/operation-risk.mjs +51 -4
- package/src/local/owner-state-lock.mjs +118 -0
- package/src/local/package-identity.mjs +9 -0
- package/src/local/patch.mjs +20 -18
- package/src/local/path-inspection.mjs +23 -0
- package/src/local/policy.mjs +19 -0
- package/src/local/private-toolchain-integrity.mjs +54 -0
- package/src/local/process-execution.mjs +87 -75
- package/src/local/process-foreground-timeout.mjs +50 -0
- package/src/local/process-identity.mjs +41 -7
- package/src/local/process-nonreplayable-settlement.mjs +50 -0
- package/src/local/process-result-projection.mjs +22 -0
- package/src/local/process-session-events.mjs +47 -0
- package/src/local/process-session-remote-activity.mjs +10 -0
- package/src/local/process-session-termination.mjs +73 -0
- package/src/local/process-sessions.mjs +144 -112
- package/src/local/process-tracker.mjs +140 -13
- package/src/local/process-tree-ownership-types.d.ts +37 -0
- package/src/local/process-tree-ownership.mjs +57 -0
- package/src/local/process-tree-signal.mjs +34 -0
- package/src/local/process-tree-snapshot.mjs +77 -0
- package/src/local/process-tree-supervisor.mjs +69 -0
- package/src/local/process-tree.mjs +11 -53
- package/src/local/project-metadata.mjs +19 -6
- package/src/local/relay-call-recovery.mjs +58 -38
- package/src/local/relay-connection-classification.mjs +131 -0
- package/src/local/relay-connection-support.mjs +91 -0
- package/src/local/relay-connection.mjs +179 -272
- package/src/local/relay-diagnostics.mjs +91 -0
- package/src/local/relay-heartbeat.mjs +119 -0
- package/src/local/relay-liveness.mjs +109 -0
- package/src/local/relay-peer-diagnostics.mjs +29 -0
- package/src/local/release-runtime-lock.mjs +18 -0
- package/src/local/remote-activity-idle-sleep-guard.mjs +77 -0
- package/src/local/remote-configuration.mjs +48 -0
- package/src/local/resilient-relay-connection.mjs +178 -0
- package/src/local/resource-admission-diagnostics.mjs +52 -0
- package/src/local/resource-admission-policy.mjs +115 -0
- package/src/local/resource-admission.mjs +339 -0
- package/src/local/resource-build-root.mjs +83 -0
- package/src/local/resource-cargo-concurrency.mjs +38 -0
- package/src/local/resource-cmake-concurrency.mjs +42 -0
- package/src/local/resource-command-concurrency.mjs +37 -0
- package/src/local/resource-command-profile.mjs +176 -0
- package/src/local/resource-coordinator-accounting.mjs +21 -0
- package/src/local/resource-cpu-window.mjs +20 -0
- package/src/local/resource-disk-headroom.mjs +13 -0
- package/src/local/resource-elastic-memory.mjs +19 -0
- package/src/local/resource-elastic-request.mjs +36 -0
- package/src/local/resource-foreground-wait.mjs +29 -0
- package/src/local/resource-go-concurrency.mjs +43 -0
- package/src/local/resource-gradle-concurrency.mjs +57 -0
- package/src/local/resource-host-cache.mjs +39 -0
- package/src/local/resource-host-darwin.mjs +54 -0
- package/src/local/resource-host-linux.mjs +35 -0
- package/src/local/resource-host-sample-file.mjs +23 -0
- package/src/local/resource-host-snapshot.mjs +99 -0
- package/src/local/resource-lease-accounting.mjs +119 -0
- package/src/local/resource-light-command.mjs +6 -0
- package/src/local/resource-make-concurrency.mjs +42 -0
- package/src/local/resource-maven-concurrency.mjs +38 -0
- package/src/local/resource-ninja-command-concurrency.mjs +20 -0
- package/src/local/resource-ninja-concurrency.mjs +28 -0
- package/src/local/resource-operations.mjs +26 -10
- package/src/local/resource-pressure.mjs +67 -0
- package/src/local/resource-probe-command.mjs +35 -0
- package/src/local/resource-process-admission.mjs +59 -0
- package/src/local/resource-process-ancestry-cache.mjs +43 -0
- package/src/local/resource-process-ancestry.mjs +32 -0
- package/src/local/resource-process-priority.mjs +15 -0
- package/src/local/resource-project-key.mjs +53 -0
- package/src/local/resource-pytest-concurrency.mjs +33 -0
- package/src/local/resource-release-control-classification.mjs +14 -0
- package/src/local/resource-release-control-executable.mjs +70 -0
- package/src/local/resource-release-control-workspace.mjs +41 -0
- package/src/local/resource-request-contract.mjs +30 -0
- package/src/local/resource-script-classification.mjs +44 -0
- package/src/local/resource-shell-analysis.mjs +58 -0
- package/src/local/resource-staging-recovery.mjs +97 -0
- package/src/local/resource-swift-concurrency.mjs +31 -0
- package/src/local/resource-transaction-lock.mjs +189 -0
- package/src/local/resource-wait.mjs +48 -0
- package/src/local/resource-waiters.mjs +139 -0
- package/src/local/resource-xcode-command.mjs +7 -0
- package/src/local/resource-xcode-concurrency.mjs +34 -0
- package/src/local/resource-xcode-non-build.mjs +19 -0
- package/src/local/runtime-activation.mjs +700 -0
- package/src/local/runtime-activity-projection.mjs +48 -0
- package/src/local/runtime-capabilities.mjs +48 -15
- package/src/local/runtime-diagnostic-state.mjs +14 -0
- package/src/local/runtime-diagnostics.mjs +40 -13
- package/src/local/runtime-info-projection.mjs +60 -0
- package/src/local/runtime-info-relay-projection.mjs +21 -0
- package/src/local/runtime-paths.mjs +2 -1
- package/src/local/runtime-process-routing.mjs +33 -0
- package/src/local/runtime-relay-connection-options.mjs +44 -0
- package/src/local/runtime-relay-control.mjs +98 -0
- package/src/local/runtime-relay.mjs +26 -50
- package/src/local/runtime-reporting.mjs +37 -21
- package/src/local/runtime-resource-service.mjs +77 -0
- package/src/local/runtime-tool-handlers.mjs +13 -10
- package/src/local/runtime.mjs +244 -208
- package/src/local/secure-file.mjs +73 -20
- package/src/local/security-audit-dispatch.mjs +35 -0
- package/src/local/security-audit-log.mjs +215 -0
- package/src/local/security-audit-state.mjs +139 -0
- package/src/local/security-audit-storage.mjs +135 -0
- package/src/local/security-audit-warning.mjs +31 -0
- package/src/local/security-audit-worker.mjs +87 -0
- package/src/local/service-convergence.mjs +42 -1
- package/src/local/service-definition.mjs +24 -0
- package/src/local/service-environment.mjs +18 -10
- package/src/local/service-owner.mjs +146 -0
- package/src/local/service-ownership.mjs +17 -0
- package/src/local/service-restart-handoff.mjs +64 -0
- package/src/local/service-restart-scheduler.mjs +49 -0
- package/src/local/service-runtime-convergence.mjs +52 -0
- package/src/local/service-runtime.mjs +105 -0
- package/src/local/service-status.mjs +60 -0
- package/src/local/service.mjs +385 -92
- package/src/local/shell.mjs +39 -19
- package/src/local/ssh-key.mjs +158 -42
- package/src/local/state-inventory.mjs +50 -24
- package/src/local/state-owner-lock-inventory.mjs +23 -0
- package/src/local/state-root-owned-namespaces.mjs +83 -0
- package/src/local/state-root-retirement.mjs +89 -0
- package/src/local/state.mjs +386 -96
- package/src/local/stdio.mjs +96 -66
- package/src/local/support-state-projection.mjs +30 -0
- package/src/local/system-network-route.mjs +76 -0
- package/src/local/systemd-removal.mjs +20 -0
- package/src/local/tool-executor.mjs +85 -20
- package/src/local/tool-result-boundary.mjs +51 -0
- package/src/local/toolchain-operation-lock.mjs +41 -0
- package/src/local/tools.mjs +7 -5
- package/src/local/trusted-executable.mjs +53 -0
- package/src/local/trusted-git-executable.mjs +34 -0
- package/src/local/trusted-github-cli.mjs +24 -0
- package/src/local/windows-service-convergence.mjs +49 -0
- package/src/local/windows-service.mjs +52 -45
- package/src/local/worker-deployment-fingerprint.mjs +102 -0
- package/src/local/worker-deployment.mjs +48 -60
- package/src/local/worker-health.mjs +12 -4
- package/src/local/worker-secret-file.mjs +37 -17
- package/src/local/workspace-file-service.mjs +156 -239
- package/src/local/workspace-file-transaction.mjs +189 -0
- package/src/local/workspace-search.mjs +55 -0
- package/src/local/wrangler-toolchain/package-lock.json +1505 -0
- package/src/local/wrangler-toolchain/package.json +23 -0
- package/src/local/wrangler-toolchain-verification.mjs +135 -0
- package/src/local/wrangler-toolchain.mjs +188 -0
- package/src/shared/access-contract.json +7 -1
- package/src/shared/activation-recovery.mjs +49 -0
- package/src/shared/admin-auth.d.mts +2 -1
- package/src/shared/admin-auth.mjs +3 -3
- package/src/shared/authority-revocation.d.mts +9 -0
- package/src/shared/authority-revocation.mjs +28 -0
- package/src/shared/daemon-auth.d.mts +11 -0
- package/src/shared/daemon-auth.mjs +17 -0
- package/src/shared/device-session-auth.d.mts +20 -0
- package/src/shared/device-session-auth.mjs +51 -0
- package/src/shared/foreground-timeout.d.mts +15 -0
- package/src/shared/foreground-timeout.mjs +70 -0
- package/src/shared/log-redaction.d.mts +9 -0
- package/src/shared/log-redaction.mjs +51 -0
- package/src/shared/mcp-protocol.d.mts +27 -0
- package/src/shared/mcp-protocol.mjs +275 -0
- package/src/shared/project-overview-projection.d.mts +4 -0
- package/src/shared/project-overview-projection.mjs +96 -0
- package/src/shared/relay-contract.json +28 -0
- package/src/shared/result-projection.d.mts +2 -1
- package/src/shared/result-projection.mjs +13 -2
- package/src/shared/server-metadata.json +15 -9
- package/src/shared/tool-argument-validation.d.mts +17 -0
- package/src/shared/tool-argument-validation.mjs +325 -0
- package/src/shared/tool-call-capacity.d.mts +41 -0
- package/src/shared/tool-call-capacity.mjs +59 -0
- package/src/shared/tool-catalog.json +626 -57
- package/src/worker/access.ts +12 -0
- package/src/worker/account-admin.ts +55 -33
- package/src/worker/authority-revocations.ts +174 -0
- package/src/worker/authority.ts +52 -6
- package/src/worker/daemon-auth.ts +49 -68
- package/src/worker/daemon-channel.ts +36 -0
- package/src/worker/daemon-http-auth.ts +63 -0
- package/src/worker/daemon-http-channel.ts +120 -0
- package/src/worker/daemon-http-controller.ts +163 -0
- package/src/worker/daemon-http-protocol.ts +62 -0
- package/src/worker/daemon-http-registry.ts +82 -0
- package/src/worker/daemon-last-observation.ts +30 -0
- package/src/worker/daemon-liveness.ts +8 -6
- package/src/worker/daemon-ready-dispatch.ts +17 -0
- package/src/worker/daemon-ready-messages.ts +73 -0
- package/src/worker/daemon-ready-waiters.ts +79 -0
- package/src/worker/daemon-recovery-budget.ts +13 -0
- package/src/worker/daemon-registry.ts +58 -0
- package/src/worker/daemon-relay-diagnostics.ts +127 -0
- package/src/worker/daemon-socket-attachment.ts +55 -0
- package/src/worker/daemon-sockets.ts +72 -62
- package/src/worker/daemon-status.ts +38 -0
- package/src/worker/device-session-verifier.ts +129 -0
- package/src/worker/dpop.ts +157 -0
- package/src/worker/durable-process-timeout.ts +29 -0
- package/src/worker/errors.ts +57 -0
- package/src/worker/http.ts +112 -26
- package/src/worker/index.ts +446 -440
- package/src/worker/mcp-access.ts +57 -0
- package/src/worker/mcp-controller.ts +188 -0
- package/src/worker/mcp-http-accept.ts +11 -0
- package/src/worker/mcp-http-contract.ts +322 -0
- package/src/worker/mcp-initialization-compat.ts +141 -0
- package/src/worker/mcp-jsonrpc.ts +11 -29
- package/src/worker/mcp-removed-protocol.ts +17 -0
- package/src/worker/mcp-response-proxy.ts +135 -0
- package/src/worker/mcp-response-stream.ts +65 -0
- package/src/worker/mcp-stale-schema-compat.ts +57 -0
- package/src/worker/mcp-stream-proxy-contract.ts +48 -0
- package/src/worker/mcp-tool-call-input.ts +23 -0
- package/src/worker/nonce-store.ts +33 -35
- package/src/worker/oauth-authorization-page.ts +21 -1
- package/src/worker/oauth-client-admin.ts +52 -0
- package/src/worker/oauth-client-contract.ts +38 -0
- package/src/worker/oauth-controller.ts +107 -47
- package/src/worker/oauth-field-contract.ts +31 -0
- package/src/worker/oauth-record-contract.ts +7 -0
- package/src/worker/oauth-refresh-authority.ts +27 -0
- package/src/worker/oauth-refresh-exchange.ts +162 -0
- package/src/worker/oauth-refresh-families.ts +130 -27
- package/src/worker/oauth-refresh-persistence.ts +116 -0
- package/src/worker/oauth-state.ts +33 -18
- package/src/worker/oauth-store-validation.ts +131 -0
- package/src/worker/oauth-token-derivation.ts +33 -0
- package/src/worker/oauth-token-issuance.ts +119 -0
- package/src/worker/oauth-tokens.ts +56 -190
- package/src/worker/observability.ts +38 -5
- package/src/worker/pending-admission.ts +15 -0
- package/src/worker/pending-call-capacity.ts +108 -0
- package/src/worker/pending-call-contract.ts +42 -5
- package/src/worker/pending-call-deadlines.ts +75 -0
- package/src/worker/pending-calls.ts +131 -125
- package/src/worker/runtime-alarm-storage.ts +37 -0
- package/src/worker/runtime-alarm.ts +136 -0
- package/src/worker/server-info-activity.ts +36 -0
- package/src/worker/server-info-tool-delivery.ts +15 -0
- package/src/worker/server-info.ts +168 -0
- package/src/worker/tool-catalog.ts +47 -1
- package/src/worker/tool-timeout.ts +75 -14
- package/src/worker/websocket-protocol.ts +26 -2
- package/src/worker/worker-edge-guard.ts +90 -0
- package/src/worker/worker-edge-log.ts +65 -0
- package/src/worker/worker-entry.ts +52 -0
- package/src/worker/worker-mcp-config.ts +23 -0
- package/src/worker/worker-metadata.ts +48 -0
- package/src/worker/worker-rate-limit-key.ts +30 -0
- package/src/worker/worker-runtime-config.ts +20 -0
- package/src/worker/worker-static-routes.ts +54 -0
- package/src/worker/worker-task-lifetime.ts +18 -0
- package/tsconfig.local.json +17 -1
- package/wrangler.jsonc +13 -0
- package/src/local/cli-approval.mjs +0 -117
- package/src/local/operation-state-lock.mjs +0 -92
- package/src/worker/mcp-session.ts +0 -72
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
(() => {
|
|
2
|
-
|
|
2
|
+
const PAGE_AUTOMATION_VERSION = 4;
|
|
3
|
+
if (globalThis.__machineBridgePageAutomation?.version === PAGE_AUTOMATION_VERSION) return;
|
|
3
4
|
|
|
4
5
|
const INTERACTIVE_SELECTOR = "a,button,input,select,textarea,[role],[contenteditable]:not([contenteditable='false']),summary";
|
|
5
6
|
const MAX_SHADOW_ROOTS = 200;
|
|
@@ -9,6 +10,16 @@
|
|
|
9
10
|
const MAX_PAGE_URL_CHARS = 8192;
|
|
10
11
|
const MAX_WAIT_TEXT_CHARS = 2 * 1024 * 1024;
|
|
11
12
|
const MAX_ELEMENT_REFS = 10000;
|
|
13
|
+
const PAGE_ACTIONS = new Set([
|
|
14
|
+
"click", "double_click", "hover", "fill", "type_text", "select", "check", "uncheck", "focus", "press", "submit", "scroll_into_view",
|
|
15
|
+
]);
|
|
16
|
+
const FORM_ACTIONS = new Set(["fill", "select", "check", "uncheck", "click"]);
|
|
17
|
+
const SELECTOR_FIELDS = new Set(["ref", "css", "id", "name", "label", "text", "role", "placeholder", "index"]);
|
|
18
|
+
const SNAPSHOT_IDENTITY_BOOLEAN_FIELDS = new Set(["sensitive", "in_shadow_dom"]);
|
|
19
|
+
const SNAPSHOT_IDENTITY_FIELDS = new Set([
|
|
20
|
+
"tag", "type", "role", "name", "id", "field_name", "label", "placeholder", "href", "sensitive", "in_shadow_dom",
|
|
21
|
+
]);
|
|
22
|
+
const DOCUMENT_EPOCH = `doc_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 14)}`;
|
|
12
23
|
const elementRefs = new WeakMap();
|
|
13
24
|
const refElements = new Map();
|
|
14
25
|
let nextRef = 1;
|
|
@@ -82,6 +93,54 @@
|
|
|
82
93
|
return results;
|
|
83
94
|
}
|
|
84
95
|
|
|
96
|
+
function normalizedFocusQuery(value) {
|
|
97
|
+
if (value === undefined || value === "") return "";
|
|
98
|
+
if (typeof value !== "string" || value.length > 1000 || value.includes("\0")) throw new Error("browser inspection focusQuery is invalid");
|
|
99
|
+
return value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function inspectBoolean(value, label, fallback) {
|
|
103
|
+
if (value === undefined) return fallback;
|
|
104
|
+
if (typeof value !== "boolean") throw new Error(`browser inspection ${label} is invalid`);
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function focusMatchScore(element, focusQuery) {
|
|
109
|
+
if (!focusQuery) return 0;
|
|
110
|
+
const identity = [
|
|
111
|
+
element.getAttribute?.("aria-label"), element.getAttribute?.("title"), element.getAttribute?.("placeholder"),
|
|
112
|
+
element.getAttribute?.("name"), element.id, boundedNodeText(element, 500), element.getAttribute?.("role"),
|
|
113
|
+
].filter(Boolean).join(" ").toLowerCase().replace(/\s+/g, " ");
|
|
114
|
+
if (identity === focusQuery) return 240;
|
|
115
|
+
if (identity.includes(focusQuery)) return 180;
|
|
116
|
+
const tokens = focusQuery.split(" ").filter((token) => token.length > 1);
|
|
117
|
+
return Math.min(120, tokens.filter((token) => identity.includes(token)).length * 30);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function elementSalience(element, focusQuery) {
|
|
121
|
+
let score = 0;
|
|
122
|
+
const tag = String(element.tagName || "").toLowerCase();
|
|
123
|
+
if (["button", "input", "select", "textarea", "a", "summary"].includes(tag)) score += 20;
|
|
124
|
+
if (element === document.activeElement || element.focused === true) score += 80;
|
|
125
|
+
if (element.disabled === true || String(element.getAttribute?.("aria-disabled") || "").toLowerCase() === "true") score -= 40;
|
|
126
|
+
const rect = safeBoundingRect(element);
|
|
127
|
+
if (rect && rect.width > 0 && rect.height > 0) {
|
|
128
|
+
score += 20;
|
|
129
|
+
if (rect.x + rect.width > 0 && rect.y + rect.height > 0 && rect.x < Number(globalThis.innerWidth) && rect.y < Number(globalThis.innerHeight)) score += 50;
|
|
130
|
+
}
|
|
131
|
+
if (Number(element.tabIndex) >= 0) score += 10;
|
|
132
|
+
score += focusMatchScore(element, focusQuery);
|
|
133
|
+
return score;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function safeBoundingRect(element) {
|
|
137
|
+
try {
|
|
138
|
+
const rect = element?.getBoundingClientRect?.();
|
|
139
|
+
if (!rect || ![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) return null;
|
|
140
|
+
return rect;
|
|
141
|
+
} catch { return null; }
|
|
142
|
+
}
|
|
143
|
+
|
|
85
144
|
function isInteractiveElement(element) {
|
|
86
145
|
const tag = String(element.tagName || "").toLowerCase();
|
|
87
146
|
return ["a", "button", "input", "select", "textarea", "summary"].includes(tag)
|
|
@@ -124,24 +183,39 @@
|
|
|
124
183
|
|
|
125
184
|
function inspect(params) {
|
|
126
185
|
pruneDetachedRefs();
|
|
127
|
-
|
|
128
|
-
const
|
|
186
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("browser inspection payload is invalid");
|
|
187
|
+
const maxElements = params.maxElements === undefined ? 0 : params.maxElements;
|
|
188
|
+
if (!Number.isSafeInteger(maxElements) || maxElements < 0) throw new Error("browser inspection maxElements is invalid");
|
|
189
|
+
const includeValues = inspectBoolean(params.includeValues, "includeValues", false);
|
|
190
|
+
const includePrivateHistory = inspectBoolean(params.includePrivateHistory, "includePrivateHistory", false);
|
|
191
|
+
const focusQuery = normalizedFocusQuery(params.focusQuery);
|
|
129
192
|
const scan = scanPageElements();
|
|
130
193
|
const candidates = [];
|
|
131
194
|
let interactiveCount = 0;
|
|
132
195
|
let formCount = 0;
|
|
196
|
+
let sourceIndex = 0;
|
|
133
197
|
for (const entry of scan.entries) {
|
|
134
198
|
for (const element of entry.elements) {
|
|
135
199
|
if (String(element.tagName || "").toLowerCase() === "form") formCount += 1;
|
|
136
|
-
if (!isInteractiveElement(element)) continue;
|
|
200
|
+
if (!isInteractiveElement(element)) { sourceIndex += 1; continue; }
|
|
137
201
|
interactiveCount += 1;
|
|
138
|
-
|
|
202
|
+
candidates.push({ element, score: elementSalience(element, focusQuery), focusScore: focusMatchScore(element, focusQuery), sourceIndex });
|
|
203
|
+
sourceIndex += 1;
|
|
139
204
|
}
|
|
140
205
|
}
|
|
141
|
-
|
|
206
|
+
candidates.sort((left, right) => right.score - left.score || left.sourceIndex - right.sourceIndex);
|
|
207
|
+
const selected = candidates.slice(0, maxElements);
|
|
208
|
+
const queryMatches = focusQuery ? candidates.filter((candidate) => candidate.focusScore > 0) : [];
|
|
209
|
+
const describedElements = selected.map(({ element, score, focusScore }, index) => ({
|
|
210
|
+
...describeElement(element, index, includeValues),
|
|
211
|
+
salience_score: Math.round(score),
|
|
212
|
+
...(focusQuery ? { focus_match_score: Math.round(focusScore) } : {}),
|
|
213
|
+
}));
|
|
142
214
|
return {
|
|
143
|
-
snapshot_version:
|
|
215
|
+
snapshot_version: 3,
|
|
144
216
|
document: {
|
|
217
|
+
epoch: DOCUMENT_EPOCH,
|
|
218
|
+
...(includePrivateHistory ? { _machine_history_entry_key: historyEntryKey() } : {}),
|
|
145
219
|
title: boundedPageText(document.title, 1000),
|
|
146
220
|
url: safePageUrl(location.href),
|
|
147
221
|
language: boundedPageText(document.documentElement.lang, 100),
|
|
@@ -154,111 +228,424 @@
|
|
|
154
228
|
tracked_refs: refElements.size,
|
|
155
229
|
ref_limit: MAX_ELEMENT_REFS,
|
|
156
230
|
refs_evicted: evictedRefs,
|
|
231
|
+
selection_strategy: focusQuery ? "salience+focus_query" : "salience",
|
|
232
|
+
focus_query_applied: Boolean(focusQuery),
|
|
233
|
+
focus_query_matched: focusQuery ? queryMatches.length > 0 : null,
|
|
234
|
+
focus_query_match_count: focusQuery ? queryMatches.length : null,
|
|
235
|
+
focus_query_search_exhaustive: focusQuery ? scan.truncated !== true : null,
|
|
157
236
|
},
|
|
158
237
|
elements: describedElements,
|
|
159
238
|
truncated: scan.truncated || interactiveCount > describedElements.length,
|
|
160
239
|
};
|
|
161
240
|
}
|
|
162
241
|
|
|
242
|
+
function historyEntryKey() {
|
|
243
|
+
try {
|
|
244
|
+
const value = globalThis.navigation?.currentEntry?.key;
|
|
245
|
+
return typeof value === "string" && value && !value.includes("\0") ? value.slice(0, 512) : "";
|
|
246
|
+
} catch { return ""; }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function documentState() {
|
|
250
|
+
const viewport = globalThis.visualViewport;
|
|
251
|
+
return {
|
|
252
|
+
epoch: DOCUMENT_EPOCH,
|
|
253
|
+
_machine_history_entry_key: historyEntryKey(),
|
|
254
|
+
title: boundedPageText(document.title, 1000),
|
|
255
|
+
url: safePageUrl(location.href),
|
|
256
|
+
ready_state: document.readyState,
|
|
257
|
+
viewport: {
|
|
258
|
+
width: finiteNumber(viewport?.width, finiteNumber(globalThis.innerWidth, 0)),
|
|
259
|
+
height: finiteNumber(viewport?.height, finiteNumber(globalThis.innerHeight, 0)),
|
|
260
|
+
offset_left: finiteNumber(viewport?.offsetLeft, 0),
|
|
261
|
+
offset_top: finiteNumber(viewport?.offsetTop, 0),
|
|
262
|
+
scale: finiteNumber(viewport?.scale, 1),
|
|
263
|
+
scroll_x: finiteNumber(globalThis.scrollX, 0),
|
|
264
|
+
scroll_y: finiteNumber(globalThis.scrollY, 0),
|
|
265
|
+
device_pixel_ratio: finiteNumber(globalThis.devicePixelRatio, 1),
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function exactOptionalAuthorityString(value, maxLength) {
|
|
271
|
+
if (value === undefined || value === "") return "";
|
|
272
|
+
if (typeof value !== "string" || value.length > maxLength || value.includes("\0")) {
|
|
273
|
+
throw new Error("snapshot authority string is invalid before dispatch");
|
|
274
|
+
}
|
|
275
|
+
return value;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function historyAction(params) {
|
|
279
|
+
const action = exactOptionalAuthorityString(params?.action, 32);
|
|
280
|
+
if (!["reload", "back", "forward"].includes(action)) throw new Error("history action is invalid before dispatch");
|
|
281
|
+
const expectedUrl = exactOptionalAuthorityString(params?.expectedTabUrl, 32768);
|
|
282
|
+
const expectedDocument = exactOptionalAuthorityString(params?.expectedDocumentEpoch, 9000);
|
|
283
|
+
const expectedHistoryEntry = exactOptionalAuthorityString(params?.expectedHistoryEntryKey, 512);
|
|
284
|
+
const currentUrl = safePageUrl(location.href);
|
|
285
|
+
if (expectedUrl && currentUrl !== expectedUrl) {
|
|
286
|
+
throw new Error("snapshot browser tab changed before navigation dispatch; observe again");
|
|
287
|
+
}
|
|
288
|
+
if (expectedDocument && DOCUMENT_EPOCH !== expectedDocument) {
|
|
289
|
+
throw new Error("snapshot history document changed before dispatch; observe again");
|
|
290
|
+
}
|
|
291
|
+
if (expectedHistoryEntry) {
|
|
292
|
+
const currentHistoryEntry = historyEntryKey();
|
|
293
|
+
if (!currentHistoryEntry) throw new Error("snapshot history entry could not be verified before dispatch; observe again");
|
|
294
|
+
if (currentHistoryEntry !== expectedHistoryEntry) throw new Error("snapshot history entry changed before dispatch; observe again");
|
|
295
|
+
}
|
|
296
|
+
const navigation = globalThis.navigation;
|
|
297
|
+
if (action === "back" && navigation?.canGoBack !== true) {
|
|
298
|
+
throw new Error("snapshot browser history has no back entry before dispatch; observe again");
|
|
299
|
+
}
|
|
300
|
+
if (action === "forward" && navigation?.canGoForward !== true) {
|
|
301
|
+
throw new Error("snapshot browser history has no forward entry before dispatch; observe again");
|
|
302
|
+
}
|
|
303
|
+
const mutate = action === "reload" ? navigation?.reload : action === "back" ? navigation?.back : navigation?.forward;
|
|
304
|
+
if (typeof mutate !== "function") throw new Error("snapshot history mutation API is unavailable before dispatch; observe again");
|
|
305
|
+
try { mutate.call(navigation); }
|
|
306
|
+
catch {
|
|
307
|
+
throw new Error("browser action may have been dispatched; the action outcome is unknown because the renderer history mutation API failed. Inspect the page before retrying.");
|
|
308
|
+
}
|
|
309
|
+
return { dispatched: true };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function refIdentity(params) {
|
|
313
|
+
const ref = params?.ref;
|
|
314
|
+
if (typeof ref !== "string" || !ref || ref.length > 100 || ref.includes("\0")) throw new Error("element reference is invalid");
|
|
315
|
+
const elements = findElements({ ref }, { allowStaleRef: true });
|
|
316
|
+
const element = elements.length === 1 ? elements[0] : null;
|
|
317
|
+
return {
|
|
318
|
+
attached: Boolean(element),
|
|
319
|
+
matched: Boolean(element && snapshotIdentityMatches(element, params?.expectedIdentity)),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function pointProbe(params) {
|
|
324
|
+
const x = exactFiniteNumber(params?.x);
|
|
325
|
+
const y = exactFiniteNumber(params?.y);
|
|
326
|
+
const width = exactPositiveFiniteNumber(globalThis.innerWidth);
|
|
327
|
+
const height = exactPositiveFiniteNumber(globalThis.innerHeight);
|
|
328
|
+
if (x === null || y === null || width === null || height === null || x < 0 || y < 0 || x >= width || y >= height) {
|
|
329
|
+
throw new Error("visual point is outside the current viewport");
|
|
330
|
+
}
|
|
331
|
+
const raw = document.elementFromPoint(x, y);
|
|
332
|
+
if (!raw) return { point: { x, y }, hit: null };
|
|
333
|
+
const target = isInteractiveElement(raw) ? raw : raw.closest?.(INTERACTIVE_SELECTOR) || raw;
|
|
334
|
+
return { point: { x, y }, hit: describeElement(target, 0, false), raw_tag: String(raw.tagName || "").toLowerCase() };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function exactFiniteNumber(value) {
|
|
338
|
+
return typeof value === "number" && Number.isFinite(value) ? (Object.is(value, -0) ? 0 : value) : null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function exactPositiveFiniteNumber(value) {
|
|
342
|
+
const number = exactFiniteNumber(value);
|
|
343
|
+
return number !== null && number > 0 ? number : null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function exactElementTimeoutMs(value) {
|
|
347
|
+
if (value === undefined) return 10000;
|
|
348
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 120000) throw new Error("element timeout is invalid before dispatch");
|
|
349
|
+
return value;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function requiredMutationText(value, label, maxLength = 131072) {
|
|
353
|
+
if (typeof value !== "string" || value.includes("\0") || value.length > maxLength) {
|
|
354
|
+
throw new Error(`${label} must be a string of at most ${maxLength} characters without NUL bytes before dispatch`);
|
|
355
|
+
}
|
|
356
|
+
return value;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function validatePageSelector(value) {
|
|
360
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("selector must be an object before dispatch");
|
|
361
|
+
const output = {};
|
|
362
|
+
for (const key of Object.keys(value)) if (!SELECTOR_FIELDS.has(key)) throw new Error(`unknown selector field before dispatch: ${key}`);
|
|
363
|
+
for (const key of ["ref", "css", "id", "name", "label", "text", "role", "placeholder"]) {
|
|
364
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
365
|
+
const maxLength = key === "ref" ? 100 : 2000;
|
|
366
|
+
output[key] = requiredMutationText(value[key], `selector.${key}`, maxLength);
|
|
367
|
+
}
|
|
368
|
+
if (Object.hasOwn(value, "index")) {
|
|
369
|
+
if (!Number.isSafeInteger(value.index) || value.index < 0 || value.index > 10000) throw new Error("selector.index must be an integer from 0 to 10000 before dispatch");
|
|
370
|
+
output.index = value.index;
|
|
371
|
+
}
|
|
372
|
+
if (!Object.keys(output).length) throw new Error("selector requires at least one field before dispatch");
|
|
373
|
+
if (output.ref !== undefined && Object.keys(output).length !== 1) throw new Error("selector.ref cannot be combined with other fields before dispatch");
|
|
374
|
+
return output;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function validateExpectedIdentity(value) {
|
|
378
|
+
if (value === undefined || value === null) return null;
|
|
379
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("snapshot identity must be an object before dispatch");
|
|
380
|
+
const output = {};
|
|
381
|
+
for (const [field, expected] of Object.entries(value)) {
|
|
382
|
+
if (!SNAPSHOT_IDENTITY_FIELDS.has(field)) throw new Error(`unknown snapshot identity field before dispatch: ${field}`);
|
|
383
|
+
if (SNAPSHOT_IDENTITY_BOOLEAN_FIELDS.has(field)) {
|
|
384
|
+
if (typeof expected !== "boolean") throw new Error(`snapshot identity ${field} must be boolean before dispatch`);
|
|
385
|
+
output[field] = expected;
|
|
386
|
+
} else {
|
|
387
|
+
output[field] = requiredMutationText(expected, `snapshot identity ${field}`, field === "href" ? 8192 : 2000);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return output;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function requiredPageAction(value) {
|
|
394
|
+
if (typeof value !== "string" || !PAGE_ACTIONS.has(value)) throw new Error("page action is invalid before dispatch");
|
|
395
|
+
return value;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function validateActionPayload(params) {
|
|
399
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("page action payload is invalid before dispatch");
|
|
400
|
+
const allowed = new Set(["action", "selector", "value", "key", "elementTimeoutMs", "expectedIdentity"]);
|
|
401
|
+
for (const key of Object.keys(params)) if (!allowed.has(key)) throw new Error(`unknown page action property before dispatch: ${key}`);
|
|
402
|
+
const action = requiredPageAction(params.action);
|
|
403
|
+
const selector = validatePageSelector(params.selector);
|
|
404
|
+
const timeoutMs = exactElementTimeoutMs(params.elementTimeoutMs);
|
|
405
|
+
const expectedIdentity = validateExpectedIdentity(params.expectedIdentity);
|
|
406
|
+
let value = params.value;
|
|
407
|
+
let key = params.key;
|
|
408
|
+
if (["fill", "select", "type_text"].includes(action)) {
|
|
409
|
+
value = requiredMutationText(value, "value");
|
|
410
|
+
if (key !== undefined && key !== "") throw new Error("key is not valid for this page action before dispatch");
|
|
411
|
+
} else if (action === "press") {
|
|
412
|
+
if (value !== undefined && value !== null) value = requiredMutationText(value, "value", 100);
|
|
413
|
+
if (key !== undefined && key !== "") key = requiredMutationText(key, "key", 100);
|
|
414
|
+
keyboardEventInit(key || value || "Enter");
|
|
415
|
+
} else {
|
|
416
|
+
if (value !== undefined && value !== null) throw new Error("value is not valid for this page action before dispatch");
|
|
417
|
+
if (key !== undefined && key !== "") throw new Error("key is not valid for this page action before dispatch");
|
|
418
|
+
}
|
|
419
|
+
return { action, selector, timeoutMs, expectedIdentity, value, key };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function rendererUtf8ByteLength(value) {
|
|
423
|
+
let bytes = 0;
|
|
424
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
425
|
+
const code = value.charCodeAt(index);
|
|
426
|
+
if (code < 0x80) bytes += 1;
|
|
427
|
+
else if (code < 0x800) bytes += 2;
|
|
428
|
+
else if (code >= 0xd800 && code <= 0xdbff && index + 1 < value.length) {
|
|
429
|
+
const next = value.charCodeAt(index + 1);
|
|
430
|
+
if (next >= 0xdc00 && next <= 0xdfff) { bytes += 4; index += 1; } else bytes += 3;
|
|
431
|
+
} else bytes += 3;
|
|
432
|
+
}
|
|
433
|
+
return bytes;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function rendererDecodedBase64Bytes(value) {
|
|
437
|
+
if (typeof value !== "string" || value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) return null;
|
|
438
|
+
const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
|
|
439
|
+
return (value.length / 4) * 3 - padding;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function validateFormPayload(params) {
|
|
443
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("form payload is invalid before dispatch");
|
|
444
|
+
if (!Array.isArray(params.fields) || params.fields.length < 1 || params.fields.length > 200) throw new Error("form fields are invalid before dispatch");
|
|
445
|
+
if (typeof params.submit !== "boolean") throw new Error("form submit must be boolean before dispatch");
|
|
446
|
+
const timeoutMs = exactElementTimeoutMs(params.elementTimeoutMs);
|
|
447
|
+
const submitSelector = params.submitSelector === undefined || params.submitSelector === null ? null : validatePageSelector(params.submitSelector);
|
|
448
|
+
let totalValueBytes = 0;
|
|
449
|
+
const fields = params.fields.map((field, index) => {
|
|
450
|
+
if (!field || typeof field !== "object" || Array.isArray(field)) throw new Error(`form field ${index} is invalid before dispatch`);
|
|
451
|
+
for (const key of Object.keys(field)) if (!["selector", "action", "value", "sensitive"].includes(key)) throw new Error(`unknown form field ${index} property before dispatch: ${key}`);
|
|
452
|
+
if (typeof field.action !== "string" || !FORM_ACTIONS.has(field.action)) throw new Error(`form field ${index} action is invalid before dispatch`);
|
|
453
|
+
if (typeof field.sensitive !== "boolean") throw new Error(`form field ${index} sensitive flag must be boolean before dispatch`);
|
|
454
|
+
const selector = validatePageSelector(field.selector);
|
|
455
|
+
let value = field.value;
|
|
456
|
+
if (["fill", "select"].includes(field.action)) {
|
|
457
|
+
value = requiredMutationText(value, `form field ${index} value`);
|
|
458
|
+
totalValueBytes += rendererUtf8ByteLength(value);
|
|
459
|
+
if (totalValueBytes > 4 * 1024 * 1024) throw new Error("form values exceed the 4 MiB aggregate budget before dispatch");
|
|
460
|
+
}
|
|
461
|
+
else if (value !== undefined && value !== null) throw new Error(`form field ${index} value is not valid before dispatch`);
|
|
462
|
+
return { selector, action: field.action, value, sensitive: field.sensitive };
|
|
463
|
+
});
|
|
464
|
+
return { fields, submit: params.submit, submitSelector, timeoutMs };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function validateUploadPayload(params) {
|
|
468
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("upload payload is invalid before dispatch");
|
|
469
|
+
const selector = validatePageSelector(params.selector);
|
|
470
|
+
const timeoutMs = exactElementTimeoutMs(params.elementTimeoutMs);
|
|
471
|
+
if (!Array.isArray(params.files) || params.files.length < 1 || params.files.length > 8) throw new Error("upload files are invalid before dispatch");
|
|
472
|
+
let totalBytes = 0;
|
|
473
|
+
const files = params.files.map((item, index) => {
|
|
474
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error(`upload file ${index} is invalid before dispatch`);
|
|
475
|
+
const filename = requiredMutationText(item.filename, `upload file ${index} filename`, 255);
|
|
476
|
+
if (!filename || filename === "." || filename === ".." || /[\/\\\u0000-\u001f\u007f]/.test(filename)) throw new Error(`upload file ${index} filename is invalid before dispatch`);
|
|
477
|
+
const mime = requiredMutationText(item.mime, `upload file ${index} mime`, 200);
|
|
478
|
+
if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(mime)) throw new Error(`upload file ${index} mime is invalid before dispatch`);
|
|
479
|
+
const data = requiredMutationText(item.data, `upload file ${index} data`, 8 * 1024 * 1024);
|
|
480
|
+
const bytes = rendererDecodedBase64Bytes(data);
|
|
481
|
+
if (bytes === null) throw new Error(`upload file ${index} data is invalid before dispatch`);
|
|
482
|
+
totalBytes += bytes;
|
|
483
|
+
if (totalBytes > 5 * 1024 * 1024) throw new Error("upload files exceed the 5 MiB aggregate budget before dispatch");
|
|
484
|
+
return { filename, mime, data };
|
|
485
|
+
});
|
|
486
|
+
return { selector, timeoutMs, files };
|
|
487
|
+
}
|
|
488
|
+
|
|
163
489
|
async function prepareAction(params) {
|
|
164
490
|
const element = await prepareElementForAction(params);
|
|
165
|
-
|
|
166
|
-
|
|
491
|
+
try {
|
|
492
|
+
const result = actionTarget(element);
|
|
493
|
+
return { ok: true, element: describeElement(element, 0, false), point: result.point };
|
|
494
|
+
} catch (error) {
|
|
495
|
+
throw domMutationUnknown(params.action, error);
|
|
496
|
+
}
|
|
167
497
|
}
|
|
168
498
|
|
|
169
499
|
async function action(params) {
|
|
170
|
-
const
|
|
171
|
-
await
|
|
172
|
-
|
|
500
|
+
const prepared = validateActionPayload(params);
|
|
501
|
+
const element = await waitForActionable(prepared.selector, prepared.action, prepared.timeoutMs);
|
|
502
|
+
assertSnapshotIdentity(element, prepared.expectedIdentity);
|
|
503
|
+
const mutationStarted = await applyOne(element, prepared.action, prepared.value, prepared.key, prepared.timeoutMs);
|
|
504
|
+
try {
|
|
505
|
+
return { ok: true, element: describeElement(element, 0, false), input_mode: "dom" };
|
|
506
|
+
} catch (error) {
|
|
507
|
+
if (mutationStarted) throw domMutationUnknown(prepared.action, error);
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
173
510
|
}
|
|
174
511
|
|
|
175
512
|
async function prepareElementForAction(params) {
|
|
176
|
-
const
|
|
177
|
-
const element = await waitForActionable(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
513
|
+
const prepared = validateActionPayload(params);
|
|
514
|
+
const element = await waitForActionable(prepared.selector, prepared.action, prepared.timeoutMs);
|
|
515
|
+
assertSnapshotIdentity(element, prepared.expectedIdentity);
|
|
516
|
+
let mutationStarted = false;
|
|
517
|
+
try {
|
|
518
|
+
if (["press", "type_text", "focus"].includes(prepared.action)) {
|
|
519
|
+
mutationStarted = true;
|
|
520
|
+
element.focus();
|
|
521
|
+
}
|
|
522
|
+
if (["click", "double_click", "hover"].includes(prepared.action)) {
|
|
523
|
+
mutationStarted = true;
|
|
524
|
+
scrollElementIntoView(element);
|
|
525
|
+
await waitForStableBox(element, prepared.timeoutMs);
|
|
526
|
+
await waitForPointerTarget(element, prepared.timeoutMs);
|
|
527
|
+
} else if (prepared.action === "scroll_into_view") {
|
|
528
|
+
mutationStarted = true;
|
|
529
|
+
scrollElementIntoView(element);
|
|
530
|
+
}
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (mutationStarted) throw domMutationUnknown(prepared.action, error);
|
|
533
|
+
throw error;
|
|
185
534
|
}
|
|
186
535
|
return element;
|
|
187
536
|
}
|
|
188
537
|
|
|
189
538
|
async function fillForm(params) {
|
|
539
|
+
const prepared = validateFormPayload(params);
|
|
190
540
|
const results = [];
|
|
191
|
-
const timeoutMs =
|
|
192
|
-
for (let index = 0; index <
|
|
193
|
-
const field =
|
|
541
|
+
const timeoutMs = prepared.timeoutMs;
|
|
542
|
+
for (let index = 0; index < prepared.fields.length; index += 1) {
|
|
543
|
+
const field = prepared.fields[index];
|
|
194
544
|
try {
|
|
195
545
|
const element = await waitForActionable(field.selector, field.action, timeoutMs);
|
|
196
|
-
await applyOne(element, field.action, field.value, "");
|
|
197
|
-
|
|
546
|
+
const mutationStarted = await applyOne(element, field.action, field.value, "", timeoutMs);
|
|
547
|
+
let description;
|
|
548
|
+
try { description = describeElement(element, index, false); }
|
|
549
|
+
catch (error) { if (mutationStarted) throw domMutationUnknown("fill_form", error); throw error; }
|
|
550
|
+
results.push({ index, action: field.action, sensitive: field.sensitive === true, element: description });
|
|
198
551
|
} catch (error) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
552
|
+
if (index > 0 || String(error?.message || error).includes("outcome is unknown")) {
|
|
553
|
+
throw domMutationUnknown("fill_form", error);
|
|
554
|
+
}
|
|
555
|
+
const prefix = `form field 0 (${field.action}) failed before any earlier field changed`;
|
|
202
556
|
throw new Error(`${prefix}: ${boundedPageText(error?.message || error, 500)}`);
|
|
203
557
|
}
|
|
204
558
|
}
|
|
205
|
-
if (
|
|
559
|
+
if (prepared.submit) {
|
|
560
|
+
let submissionStarted = false;
|
|
206
561
|
try {
|
|
207
|
-
const submitter =
|
|
208
|
-
if (submitter)
|
|
562
|
+
const submitter = prepared.submitSelector ? await waitForActionable(prepared.submitSelector, "click", timeoutMs) : deepQuerySelectorAll("button[type='submit'],input[type='submit']", 1)[0];
|
|
563
|
+
if (submitter) {
|
|
564
|
+
submissionStarted = true;
|
|
565
|
+
submitter.click();
|
|
566
|
+
}
|
|
209
567
|
else {
|
|
210
|
-
const field =
|
|
568
|
+
const field = prepared.fields.length ? findOne(prepared.fields[0].selector) : null;
|
|
211
569
|
const form = field?.form || field?.closest?.("form") || deepQuerySelectorAll("form", 1)[0];
|
|
212
570
|
if (!form) throw new Error("no form or submit control found");
|
|
571
|
+
submissionStarted = true;
|
|
213
572
|
if (typeof form.requestSubmit === "function") form.requestSubmit();
|
|
214
573
|
else form.submit();
|
|
215
574
|
}
|
|
216
575
|
} catch (error) {
|
|
576
|
+
if (submissionStarted || results.length > 0) throw domMutationUnknown("form_submission", error);
|
|
217
577
|
throw new Error(`form submission failed after ${results.length} field(s) changed: ${boundedPageText(error?.message || error, 500)}`);
|
|
218
578
|
}
|
|
219
579
|
}
|
|
220
|
-
return { ok: true, fields: results, submitted:
|
|
580
|
+
return { ok: true, fields: results, submitted: prepared.submit, values_exposed: false };
|
|
221
581
|
}
|
|
222
582
|
|
|
223
583
|
async function uploadFiles(params) {
|
|
224
|
-
const
|
|
584
|
+
const prepared = validateUploadPayload(params);
|
|
585
|
+
const input = await waitForActionable(prepared.selector, "upload", prepared.timeoutMs);
|
|
225
586
|
if (!(input instanceof HTMLInputElement) || input.type !== "file") throw new Error("matched element is not a file input");
|
|
226
587
|
const transfer = new DataTransfer();
|
|
227
|
-
for (const item of
|
|
588
|
+
for (const item of prepared.files) {
|
|
228
589
|
const binary = atob(item.data);
|
|
229
590
|
const bytes = new Uint8Array(binary.length);
|
|
230
591
|
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
231
592
|
transfer.items.add(new File([bytes], item.filename, { type: item.mime || "application/octet-stream" }));
|
|
232
593
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
594
|
+
try {
|
|
595
|
+
input.files = transfer.files;
|
|
596
|
+
dispatchValueEvents(input);
|
|
597
|
+
return { ok: true, file_count: transfer.files.length, element: describeElement(input, 0, false), values_exposed: false };
|
|
598
|
+
} catch (error) {
|
|
599
|
+
throw domMutationUnknown("upload_files", error);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function validateWaitPayload(params) {
|
|
604
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("browser wait payload is invalid");
|
|
605
|
+
const allowed = new Set(["selector", "state", "text", "loadState"]);
|
|
606
|
+
for (const key of Object.keys(params)) if (!allowed.has(key)) throw new Error(`unknown browser wait property: ${key}`);
|
|
607
|
+
const selector = params.selector === undefined || params.selector === null ? null : validatePageSelector(params.selector);
|
|
608
|
+
const state = exactWaitString(params.state, "state", 32);
|
|
609
|
+
if (state && !["attached", "detached", "visible", "hidden", "enabled", "editable", "checked", "unchecked"].includes(state)) throw new Error("browser wait state is invalid");
|
|
610
|
+
if (state && !selector) throw new Error("browser wait state requires selector");
|
|
611
|
+
const text = exactWaitString(params.text, "text", 4000);
|
|
612
|
+
const loadState = exactWaitString(params.loadState, "loadState", 32);
|
|
613
|
+
if (loadState && !["domcontentloaded", "complete"].includes(loadState)) throw new Error("browser wait loadState is invalid");
|
|
614
|
+
if (!selector && !text && !loadState) throw new Error("browser wait payload requires a page condition");
|
|
615
|
+
return { selector, state, text, loadState };
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function exactWaitString(value, label, maxLength) {
|
|
619
|
+
if (value === undefined || value === "") return "";
|
|
620
|
+
if (typeof value !== "string" || value.includes("\0") || value.length > maxLength) throw new Error(`browser wait ${label} is invalid`);
|
|
621
|
+
return value;
|
|
236
622
|
}
|
|
237
623
|
|
|
238
624
|
function checkWait(params) {
|
|
239
|
-
const
|
|
625
|
+
const prepared = validateWaitPayload(params);
|
|
626
|
+
const elements = prepared.selector ? findElements(prepared.selector, { allowStaleRef: true }) : [];
|
|
240
627
|
const visible = elements.filter(isVisible);
|
|
241
628
|
const enabled = elements.filter(isEnabled);
|
|
242
629
|
const editable = elements.filter(isEditable);
|
|
243
|
-
const stateMatched = !
|
|
244
|
-
|| (
|
|
245
|
-
|| (
|
|
246
|
-
|| (
|
|
247
|
-
|| (
|
|
248
|
-
|| (
|
|
249
|
-
|| (
|
|
250
|
-
|| (
|
|
251
|
-
|| (
|
|
252
|
-
const textSearch =
|
|
630
|
+
const stateMatched = !prepared.state
|
|
631
|
+
|| (prepared.state === "attached" && elements.length > 0)
|
|
632
|
+
|| (prepared.state === "detached" && elements.length === 0)
|
|
633
|
+
|| (prepared.state === "visible" && visible.length > 0)
|
|
634
|
+
|| (prepared.state === "hidden" && visible.length === 0)
|
|
635
|
+
|| (prepared.state === "enabled" && enabled.length > 0)
|
|
636
|
+
|| (prepared.state === "editable" && editable.length > 0)
|
|
637
|
+
|| (prepared.state === "checked" && elements.some((element) => "checked" in element && element.checked === true))
|
|
638
|
+
|| (prepared.state === "unchecked" && elements.some((element) => "checked" in element && element.checked === false));
|
|
639
|
+
const textSearch = prepared.text ? pageContainsText(prepared.text) : { found: true, truncated: false, scanned_chars: 0 };
|
|
253
640
|
const textMatched = textSearch.found;
|
|
254
|
-
const loadMatched = !
|
|
255
|
-
|| (
|
|
256
|
-
|| (
|
|
641
|
+
const loadMatched = !prepared.loadState
|
|
642
|
+
|| (prepared.loadState === "domcontentloaded" && ["interactive", "complete"].includes(document.readyState))
|
|
643
|
+
|| (prepared.loadState === "complete" && document.readyState === "complete");
|
|
257
644
|
return {
|
|
258
645
|
matched: stateMatched && textMatched && loadMatched,
|
|
259
646
|
selector_count: elements.length,
|
|
260
647
|
visible_count: visible.length,
|
|
261
|
-
state:
|
|
648
|
+
state: prepared.state || "",
|
|
262
649
|
text_found: textMatched,
|
|
263
650
|
text_scan_truncated: textSearch.truncated,
|
|
264
651
|
text_scanned_chars: textSearch.scanned_chars,
|
|
@@ -297,6 +684,27 @@
|
|
|
297
684
|
return item;
|
|
298
685
|
}
|
|
299
686
|
|
|
687
|
+
function snapshotIdentityMatches(element, expected) {
|
|
688
|
+
if (expected === undefined || expected === null) return true;
|
|
689
|
+
if (!expected || typeof expected !== "object" || Array.isArray(expected)) return false;
|
|
690
|
+
const actual = describeElement(element, 0, false);
|
|
691
|
+
for (const [field, expectedValue] of Object.entries(expected)) {
|
|
692
|
+
if (!SNAPSHOT_IDENTITY_FIELDS.has(field)) return false;
|
|
693
|
+
if (SNAPSHOT_IDENTITY_BOOLEAN_FIELDS.has(field)) {
|
|
694
|
+
if (typeof expectedValue !== "boolean" || actual[field] !== expectedValue) return false;
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
if (typeof expectedValue !== "string") return false;
|
|
698
|
+
const actualValue = typeof actual[field] === "string" ? actual[field] : "";
|
|
699
|
+
if (actualValue !== expectedValue) return false;
|
|
700
|
+
}
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function assertSnapshotIdentity(element, expected) {
|
|
705
|
+
if (!snapshotIdentityMatches(element, expected)) throw new Error("snapshot_ref_identity_changed_before_dispatch");
|
|
706
|
+
}
|
|
707
|
+
|
|
300
708
|
function isSensitiveElement(element, tag, type) {
|
|
301
709
|
if (tag !== "input" && tag !== "textarea" && !element.isContentEditable) return false;
|
|
302
710
|
if (["password", "hidden"].includes(type)) return true;
|
|
@@ -390,79 +798,117 @@
|
|
|
390
798
|
function actionTarget(element) {
|
|
391
799
|
const box = boundingBox(element);
|
|
392
800
|
if (!box) return { point: null };
|
|
393
|
-
const viewportWidth =
|
|
394
|
-
const viewportHeight =
|
|
801
|
+
const viewportWidth = exactPositiveFiniteNumber(globalThis.innerWidth);
|
|
802
|
+
const viewportHeight = exactPositiveFiniteNumber(globalThis.innerHeight);
|
|
803
|
+
if (viewportWidth === null || viewportHeight === null) throw new Error("browser viewport dimensions are unavailable before dispatch");
|
|
395
804
|
const left = Math.max(0, box.x);
|
|
396
805
|
const top = Math.max(0, box.y);
|
|
397
|
-
const right = Math.min(
|
|
398
|
-
const bottom = Math.min(
|
|
806
|
+
const right = Math.min(viewportWidth, box.x + box.width);
|
|
807
|
+
const bottom = Math.min(viewportHeight, box.y + box.height);
|
|
399
808
|
if (right <= left || bottom <= top) return { point: null };
|
|
400
809
|
return { point: { x: left + (right - left) / 2, y: top + (bottom - top) / 2 } };
|
|
401
810
|
}
|
|
402
811
|
|
|
403
|
-
async function applyOne(element, operation, value, key) {
|
|
404
|
-
|
|
405
|
-
if (
|
|
406
|
-
|
|
407
|
-
element.click();
|
|
408
|
-
element.click();
|
|
409
|
-
element.dispatchEvent(new MouseEvent("dblclick", { bubbles: true, composed: true, detail: 2 }));
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
if (operation === "hover") { dispatchHoverEvents(element); return; }
|
|
413
|
-
if (operation === "focus") { element.focus(); return; }
|
|
414
|
-
if (operation === "scroll_into_view") return;
|
|
415
|
-
if (operation === "submit") {
|
|
416
|
-
const form = element.form || element.closest("form");
|
|
417
|
-
if (!form) throw new Error("matched element is not associated with a form");
|
|
418
|
-
if (typeof form.requestSubmit === "function") form.requestSubmit(); else form.submit();
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
if (operation === "check" || operation === "uncheck") {
|
|
422
|
-
const wanted = operation === "check";
|
|
423
|
-
if (Boolean(element.checked) !== wanted) element.click();
|
|
424
|
-
if (Boolean(element.checked) !== wanted) throw new Error(`checkable control did not reach the requested ${wanted ? "checked" : "unchecked"} state`);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
812
|
+
async function applyOne(element, operation, value, key, timeoutMs = 10000) {
|
|
813
|
+
const wanted = operation === "check" ? true : operation === "uncheck" ? false : null;
|
|
814
|
+
if (wanted !== null && Boolean(element.checked) === wanted) return false;
|
|
815
|
+
let selectedOption = null;
|
|
427
816
|
if (operation === "select") {
|
|
428
|
-
const text =
|
|
429
|
-
|
|
430
|
-
if (!
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
817
|
+
const text = requiredMutationText(value, "select value");
|
|
818
|
+
selectedOption = [...element.options].find((item) => item.value === text || item.text.trim() === text) || null;
|
|
819
|
+
if (!selectedOption) throw new Error("select option was not found");
|
|
820
|
+
}
|
|
821
|
+
const keyEvent = operation === "press" ? keyboardEventInit(key || value || "Enter") : null;
|
|
822
|
+
let mutationStarted = false;
|
|
823
|
+
try {
|
|
824
|
+
if (["click", "double_click", "hover"].includes(operation)) {
|
|
825
|
+
mutationStarted = true;
|
|
826
|
+
scrollElementIntoView(element);
|
|
827
|
+
await waitForStableBox(element, timeoutMs);
|
|
828
|
+
await waitForPointerTarget(element, timeoutMs);
|
|
829
|
+
if (operation === "click") element.click();
|
|
830
|
+
else if (operation === "double_click") {
|
|
831
|
+
element.click();
|
|
832
|
+
element.click();
|
|
833
|
+
element.dispatchEvent(new MouseEvent("dblclick", { bubbles: true, composed: true, detail: 2 }));
|
|
834
|
+
} else dispatchHoverEvents(element);
|
|
835
|
+
return true;
|
|
836
|
+
}
|
|
837
|
+
if (operation === "focus") { mutationStarted = true; element.focus(); return true; }
|
|
838
|
+
if (operation === "scroll_into_view") { mutationStarted = true; scrollElementIntoView(element); return true; }
|
|
839
|
+
if (operation === "submit") {
|
|
840
|
+
const form = element.form || element.closest("form");
|
|
841
|
+
if (!form) throw new Error("matched element is not associated with a form");
|
|
842
|
+
mutationStarted = true;
|
|
843
|
+
if (typeof form.requestSubmit === "function") form.requestSubmit(); else form.submit();
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
if (wanted !== null) {
|
|
847
|
+
mutationStarted = true;
|
|
848
|
+
element.click();
|
|
849
|
+
if (Boolean(element.checked) !== wanted) throw new Error(`checkable control did not reach the requested ${wanted ? "checked" : "unchecked"} state`);
|
|
850
|
+
return true;
|
|
851
|
+
}
|
|
852
|
+
if (operation === "select") {
|
|
853
|
+
mutationStarted = true;
|
|
854
|
+
setNativeValue(element, selectedOption.value);
|
|
855
|
+
dispatchValueEvents(element);
|
|
856
|
+
return true;
|
|
857
|
+
}
|
|
858
|
+
if (operation === "fill") {
|
|
859
|
+
const text = requiredMutationText(value, "fill value");
|
|
860
|
+
mutationStarted = true;
|
|
861
|
+
element.focus();
|
|
862
|
+
if (element.isContentEditable) element.textContent = text;
|
|
863
|
+
else setNativeValue(element, text);
|
|
864
|
+
dispatchValueEvents(element);
|
|
865
|
+
return true;
|
|
866
|
+
}
|
|
867
|
+
if (operation === "type_text") {
|
|
868
|
+
const text = requiredMutationText(value, "type_text value");
|
|
869
|
+
mutationStarted = true;
|
|
870
|
+
element.focus();
|
|
871
|
+
if (element.isContentEditable) element.textContent = `${element.textContent || ""}${text}`;
|
|
872
|
+
else if (typeof element.setRangeText === "function" && Number.isInteger(element.selectionStart) && Number.isInteger(element.selectionEnd)) {
|
|
873
|
+
element.setRangeText(text, element.selectionStart, element.selectionEnd, "end");
|
|
874
|
+
} else setNativeValue(element, `${String(element.value || "")}${text}`);
|
|
875
|
+
dispatchValueEvents(element);
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
if (operation === "press") {
|
|
879
|
+
mutationStarted = true;
|
|
880
|
+
element.focus();
|
|
881
|
+
element.dispatchEvent(new KeyboardEvent("keydown", { ...keyEvent, bubbles: true, composed: true }));
|
|
882
|
+
element.dispatchEvent(new KeyboardEvent("keyup", { ...keyEvent, bubbles: true, composed: true }));
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
885
|
+
throw new Error(`unsupported element action: ${operation}`);
|
|
886
|
+
} catch (error) {
|
|
887
|
+
if (mutationStarted) throw domMutationUnknown(operation, error);
|
|
888
|
+
throw error;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function domMutationUnknown(operation, error) {
|
|
893
|
+
const detail = boundedPageText(error?.message || error, 500).replace(/\s+/g, " ");
|
|
894
|
+
return new Error(`browser action may have been dispatched; the action outcome is unknown because DOM ${operation} failed after a side-effecting step. Inspect the page before retrying.${detail ? ` (${detail})` : ""}`);
|
|
460
895
|
}
|
|
461
896
|
|
|
462
897
|
function keyboardEventInit(value) {
|
|
463
|
-
const
|
|
898
|
+
const text = value === undefined || value === "" ? "Enter" : requiredMutationText(value, "key", 100);
|
|
899
|
+
const parts = text.split("+").map((part) => part.trim()).filter(Boolean);
|
|
464
900
|
const rawKey = parts.pop() || "Enter";
|
|
465
|
-
const modifiers = new Set(
|
|
901
|
+
const modifiers = new Set();
|
|
902
|
+
for (const part of parts) {
|
|
903
|
+
const normalized = part === "Ctrl" ? "Control" : part === "Cmd" || part === "Command" ? "Meta" : part;
|
|
904
|
+
if (!["Alt", "Control", "Meta", "Shift"].includes(normalized)) throw new Error(`unsupported key modifier: ${part}`);
|
|
905
|
+
modifiers.add(normalized);
|
|
906
|
+
}
|
|
907
|
+
const namedKeys = new Set([
|
|
908
|
+
"Enter", "Tab", "Escape", "Backspace", "Delete", "ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown",
|
|
909
|
+
"Home", "End", "PageUp", "PageDown", "Space",
|
|
910
|
+
]);
|
|
911
|
+
if (!namedKeys.has(rawKey) && [...rawKey].length !== 1) throw new Error(`unsupported key: ${rawKey}`);
|
|
466
912
|
return {
|
|
467
913
|
key: rawKey === "Space" ? " " : rawKey,
|
|
468
914
|
altKey: modifiers.has("Alt"),
|
|
@@ -509,7 +955,8 @@
|
|
|
509
955
|
|
|
510
956
|
function findElements(selector, { allowStaleRef = false } = {}) {
|
|
511
957
|
if (selector.ref) {
|
|
512
|
-
|
|
958
|
+
if (typeof selector.ref !== "string") return [];
|
|
959
|
+
const element = refElements.get(selector.ref);
|
|
513
960
|
if (!element) return [];
|
|
514
961
|
if (!element.isConnected) {
|
|
515
962
|
if (allowStaleRef) return [];
|
|
@@ -702,12 +1149,25 @@
|
|
|
702
1149
|
return "";
|
|
703
1150
|
}
|
|
704
1151
|
|
|
1152
|
+
function finiteNumber(value, fallback) {
|
|
1153
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
705
1156
|
function delay(ms) {
|
|
1157
|
+
const runtime = globalThis.chrome?.runtime;
|
|
1158
|
+
if (runtime && typeof runtime.sendMessage === "function") {
|
|
1159
|
+
const delayMs = Number.isFinite(ms) ? Math.ceil(ms) : 0;
|
|
1160
|
+
if (delayMs < 1 || delayMs > 250) return Promise.reject(new Error("browser action delay is invalid"));
|
|
1161
|
+
return Promise.resolve(runtime.sendMessage({ type: "machine_bridge_internal_delay", delay_ms: delayMs }))
|
|
1162
|
+
.then((response) => {
|
|
1163
|
+
if (response?.ok !== true) throw new Error("browser action timing service unavailable");
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
706
1166
|
return new Promise((resolve) => { setTimeout(resolve, ms); });
|
|
707
1167
|
}
|
|
708
1168
|
|
|
709
1169
|
Object.defineProperty(globalThis, "__machineBridgePageAutomation", {
|
|
710
|
-
value: Object.freeze({ inspect, prepareAction, action, fillForm, uploadFiles, checkWait }),
|
|
1170
|
+
value: Object.freeze({ version: PAGE_AUTOMATION_VERSION, inspect, documentState, historyAction, refIdentity, pointProbe, prepareAction, action, fillForm, uploadFiles, checkWait }),
|
|
711
1171
|
configurable: true,
|
|
712
1172
|
});
|
|
713
1173
|
})();
|