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
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const MAX_DELTA_ENTRIES = 24;
|
|
4
|
+
const MAX_APPLICATION_POST_ELEMENTS = 24;
|
|
5
|
+
const BROWSER_IDENTITY_FIELDS = Object.freeze([
|
|
6
|
+
"tag", "type", "role", "name", "id", "field_name", "label", "placeholder", "href", "sensitive", "in_shadow_dom",
|
|
7
|
+
]);
|
|
8
|
+
const BROWSER_COMPARABLE_FIELDS = Object.freeze([
|
|
9
|
+
...BROWSER_IDENTITY_FIELDS, "visible", "enabled", "editable", "checked", "selected", "focused", "bounding_box",
|
|
10
|
+
]);
|
|
11
|
+
const APPLICATION_COMPARABLE_FIELDS = Object.freeze([
|
|
12
|
+
"role", "subrole", "name", "title", "description", "identifier", "enabled", "focused", "checked", "selected", "expanded", "visible", "sensitive", "bounding_box", "_machine_owner_window_bounds",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export function extractBrowserPrivateBindings(captured) {
|
|
16
|
+
const frames = Array.isArray(captured?.semantic?.frames) ? captured.semantic.frames : [];
|
|
17
|
+
const semanticUrlCounts = browserSemanticUrlCounts(frames);
|
|
18
|
+
const cdpFrameUrls = browserCdpFrameUrls(captured);
|
|
19
|
+
const trustedBackends = browserTrustedBackends(captured, cdpFrameUrls);
|
|
20
|
+
const bindings = new Map();
|
|
21
|
+
for (const frame of frames) appendBrowserFrameBindings(bindings, frame, semanticUrlCounts, trustedBackends);
|
|
22
|
+
return {
|
|
23
|
+
browser_ref_bindings: bindings,
|
|
24
|
+
browser_history_entry_key: privateBrowserString(captured?._machine_history_entry_key, 512),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function browserSemanticUrlCounts(frames) {
|
|
29
|
+
const semanticUrlCounts = new Map();
|
|
30
|
+
for (const frame of frames) {
|
|
31
|
+
const url = privateBrowserString(frame?.document?.url, 8192);
|
|
32
|
+
if (url) semanticUrlCounts.set(url, (semanticUrlCounts.get(url) || 0) + 1);
|
|
33
|
+
}
|
|
34
|
+
return semanticUrlCounts;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function browserCdpFrameUrls(captured) {
|
|
38
|
+
const cdpFrameUrls = new Map();
|
|
39
|
+
for (const frame of Array.isArray(captured?.frame_tree) ? captured.frame_tree : []) {
|
|
40
|
+
const frameId = privateBrowserString(frame?.id, 32768);
|
|
41
|
+
const frameUrl = privateBrowserString(frame?.url, 8192);
|
|
42
|
+
if (!frameId || !frameUrl) continue;
|
|
43
|
+
if (cdpFrameUrls.has(frameId) && cdpFrameUrls.get(frameId) !== frameUrl) cdpFrameUrls.set(frameId, "");
|
|
44
|
+
else cdpFrameUrls.set(frameId, frameUrl);
|
|
45
|
+
}
|
|
46
|
+
return cdpFrameUrls;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function browserTrustedBackends(captured, cdpFrameUrls) {
|
|
50
|
+
const trustedBackends = new Map();
|
|
51
|
+
for (const node of Array.isArray(captured?.accessibility?.nodes) ? captured.accessibility.nodes : []) {
|
|
52
|
+
const ref = typeof node?.action_ref === "string" && node.action_ref && node.action_ref.length <= 100 && !node.action_ref.includes("\0")
|
|
53
|
+
? node.action_ref : "";
|
|
54
|
+
const backendNodeId = node?.backend_dom_node_id;
|
|
55
|
+
const frameUrl = cdpFrameUrls.get(privateBrowserString(node?.frame_id, 32768)) || "";
|
|
56
|
+
if (node?.action_ref_confidence !== "high" || !ref || !frameUrl
|
|
57
|
+
|| !Number.isSafeInteger(backendNodeId) || backendNodeId <= 0) continue;
|
|
58
|
+
const prior = trustedBackends.get(ref);
|
|
59
|
+
if (prior && (prior.backendNodeId !== backendNodeId || prior.frameUrl !== frameUrl)) trustedBackends.set(ref, null);
|
|
60
|
+
else if (!trustedBackends.has(ref)) trustedBackends.set(ref, { backendNodeId, frameUrl });
|
|
61
|
+
}
|
|
62
|
+
return trustedBackends;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function appendBrowserFrameBindings(bindings, frame, semanticUrlCounts, trustedBackends) {
|
|
66
|
+
const extensionFrameId = frame?.frame_id;
|
|
67
|
+
const extensionFrameEpoch = privateBrowserString(frame?.document?.epoch, 9000);
|
|
68
|
+
const extensionFrameUrl = privateBrowserString(frame?.document?.url, 8192);
|
|
69
|
+
if (!Number.isSafeInteger(extensionFrameId) || extensionFrameId < 0 || !extensionFrameEpoch || !extensionFrameUrl
|
|
70
|
+
|| semanticUrlCounts.get(extensionFrameUrl) !== 1) return;
|
|
71
|
+
for (const element of Array.isArray(frame?.elements) ? frame.elements : []) {
|
|
72
|
+
const ref = typeof element?.ref === "string" ? element.ref : "";
|
|
73
|
+
const backendNodeId = element?._machine_backend_node_id;
|
|
74
|
+
const trusted = trustedBackends.get(ref);
|
|
75
|
+
if (!ref || ref.length > 100 || ref.includes("\0") || !Number.isSafeInteger(backendNodeId) || backendNodeId <= 0
|
|
76
|
+
|| trusted?.backendNodeId !== backendNodeId || trusted?.frameUrl !== extensionFrameUrl) continue;
|
|
77
|
+
bindings.set(ref, {
|
|
78
|
+
backend_node_id: backendNodeId,
|
|
79
|
+
extension_frame_id: extensionFrameId,
|
|
80
|
+
extension_frame_epoch: extensionFrameEpoch,
|
|
81
|
+
extension_frame_url: extensionFrameUrl,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function buildBrowserObservation(captured, args = {}) {
|
|
87
|
+
const inspected = record(captured?.semantic);
|
|
88
|
+
const accessibility = recordOrNull(captured?.accessibility);
|
|
89
|
+
const capture = browserCaptureMetadata(captured, args);
|
|
90
|
+
return {
|
|
91
|
+
snapshot_id: "",
|
|
92
|
+
surface: "browser",
|
|
93
|
+
captured_at: new Date().toISOString(),
|
|
94
|
+
document_epoch: privateBrowserString(captured?.document_epoch, 9000),
|
|
95
|
+
target: {
|
|
96
|
+
tab_id: browserObservationTabId(captured?.tab_id) ?? browserObservationTabId(inspected.tab_id),
|
|
97
|
+
url: privateBrowserString(captured?.url, 32768) || privateBrowserString(inspected.url, 32768),
|
|
98
|
+
title: privateBrowserText(captured?.title, 32768) || privateBrowserText(inspected.title, 32768),
|
|
99
|
+
},
|
|
100
|
+
capture,
|
|
101
|
+
capabilities: browserCapabilities(captured, accessibility, capture),
|
|
102
|
+
semantic: browserSemanticProjection(captured, inspected, accessibility),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function observationDiff(before, after, beforePrivateState = null, afterPrivateState = null) {
|
|
107
|
+
const semanticDelta = before.surface === "browser"
|
|
108
|
+
? browserSemanticDelta(before, after)
|
|
109
|
+
: applicationSemanticDelta(before, after, beforePrivateState, afterPrivateState);
|
|
110
|
+
const processChanged = before.surface === "application"
|
|
111
|
+
? applicationProcessEpochChanged(beforePrivateState, afterPrivateState)
|
|
112
|
+
: null;
|
|
113
|
+
const semanticChanged = before.surface === "browser"
|
|
114
|
+
? semanticFingerprint(before) !== semanticFingerprint(after)
|
|
115
|
+
: processChanged === true || semanticDelta.added_count > 0 || semanticDelta.removed_count > 0 || semanticDelta.changed_count > 0;
|
|
116
|
+
return {
|
|
117
|
+
target_changed: JSON.stringify(before.target) !== JSON.stringify(after.target) || processChanged === true,
|
|
118
|
+
process_changed: processChanged,
|
|
119
|
+
url_changed: before.surface === "browser" ? boundedStringChanged(before.target?.url, after.target?.url, 32768, false) : null,
|
|
120
|
+
title_changed: before.surface === "browser" ? boundedStringChanged(before.target?.title, after.target?.title, 32768, true) : null,
|
|
121
|
+
document_epoch_changed: before.surface === "browser" ? browserDocumentEpochChanged(before, after) : null,
|
|
122
|
+
frame_epoch_changed: before.surface === "browser" ? browserFrameEpochsChanged(before, after) : null,
|
|
123
|
+
history_entry_changed: before.surface === "browser" ? browserHistoryEntryChanged(beforePrivateState, afterPrivateState) : null,
|
|
124
|
+
screenshot_changed: screenshotChange(before, after),
|
|
125
|
+
semantic_changed: semanticChanged,
|
|
126
|
+
semantic_delta: semanticDelta,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function buildContinuation(before, after, target, diff, beforePrivateState = null, afterPrivateState = null) {
|
|
131
|
+
if (!after) {
|
|
132
|
+
return {
|
|
133
|
+
available: false,
|
|
134
|
+
snapshot_id: null,
|
|
135
|
+
reobserve_recommended: true,
|
|
136
|
+
reason: "post_observation_unavailable",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (after.surface === "application") {
|
|
140
|
+
const refs = (after.semantic?.elements || []).slice(0, MAX_APPLICATION_POST_ELEMENTS)
|
|
141
|
+
.map((element) => typeof element?.ref === "string" ? element.ref : "").filter(Boolean);
|
|
142
|
+
const targetMapping = mapApplicationTargetToPostSnapshot(before, after, target, beforePrivateState, afterPrivateState);
|
|
143
|
+
const salientRefs = targetMapping.mapped_post_target_ref && refs.includes(targetMapping.mapped_post_target_ref)
|
|
144
|
+
? [targetMapping.mapped_post_target_ref, ...refs.filter((ref) => ref !== targetMapping.mapped_post_target_ref)]
|
|
145
|
+
: refs;
|
|
146
|
+
return {
|
|
147
|
+
available: true,
|
|
148
|
+
snapshot_id: after.snapshot_id,
|
|
149
|
+
surface: "application",
|
|
150
|
+
previous_refs_reusable: false,
|
|
151
|
+
previous_target_ref: targetMapping.previous_target_ref,
|
|
152
|
+
target_identity_continues: targetMapping.target_identity_continues,
|
|
153
|
+
mapped_post_target_ref: targetMapping.mapped_post_target_ref,
|
|
154
|
+
use_post_snapshot_ref: Boolean(targetMapping.mapped_post_target_ref),
|
|
155
|
+
target_identity_reason: targetMapping.reason,
|
|
156
|
+
post_snapshot_refs_available: refs.length > 0,
|
|
157
|
+
salient_post_refs: salientRefs.slice(0, 12),
|
|
158
|
+
reobserve_recommended: false,
|
|
159
|
+
reason: targetMapping.mapped_post_target_ref
|
|
160
|
+
? "application_target_identity_mapped_to_post_snapshot"
|
|
161
|
+
: "application_refs_are_snapshot_scoped_use_post_observation_refs",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const delta = diff?.semantic_delta || emptyDelta();
|
|
165
|
+
const sameEpoch = diff?.document_epoch_changed === false;
|
|
166
|
+
const targetRef = target?.kind === "ref" && typeof target.ref === "string" ? target.ref : "";
|
|
167
|
+
const removedRefs = semanticRefs(delta.removed);
|
|
168
|
+
const addedRefs = semanticRefs(delta.added);
|
|
169
|
+
const changedRefs = semanticRefs(delta.changed.map((entry) => entry?.after));
|
|
170
|
+
const frameEpochChanged = diff?.frame_epoch_changed;
|
|
171
|
+
const targetFrameEpochSame = targetRef ? browserTargetFrameEpochSame(before, after, target) : null;
|
|
172
|
+
const postTarget = targetRef ? browserElementAt(after, target.frame_id, targetRef) : null;
|
|
173
|
+
const targetIdentityContinues = targetRef && targetFrameEpochSame === true && postTarget
|
|
174
|
+
? browserElementIdentityMatches(target.element, postTarget)
|
|
175
|
+
: targetRef ? false : null;
|
|
176
|
+
const targetReusable = Boolean(
|
|
177
|
+
targetRef
|
|
178
|
+
&& sameEpoch
|
|
179
|
+
&& targetFrameEpochSame === true
|
|
180
|
+
&& targetIdentityContinues === true
|
|
181
|
+
&& !removedRefs.includes(targetRef),
|
|
182
|
+
);
|
|
183
|
+
const needsObserve = !sameEpoch || delta.truncated === true;
|
|
184
|
+
return {
|
|
185
|
+
available: true,
|
|
186
|
+
snapshot_id: after.snapshot_id,
|
|
187
|
+
surface: "browser",
|
|
188
|
+
document_epoch_same: sameEpoch,
|
|
189
|
+
frame_epoch_changed: typeof frameEpochChanged === "boolean" ? frameEpochChanged : null,
|
|
190
|
+
stable_ref_namespace: sameEpoch && frameEpochChanged === false,
|
|
191
|
+
previous_target_ref: targetRef || null,
|
|
192
|
+
previous_target_ref_reusable: targetReusable,
|
|
193
|
+
target_frame_epoch_same: targetFrameEpochSame,
|
|
194
|
+
target_identity_continues: targetIdentityContinues,
|
|
195
|
+
added_refs: addedRefs,
|
|
196
|
+
changed_refs: changedRefs,
|
|
197
|
+
removed_refs: removedRefs,
|
|
198
|
+
delta_truncated: delta.truncated === true,
|
|
199
|
+
reobserve_recommended: needsObserve,
|
|
200
|
+
reason: !sameEpoch
|
|
201
|
+
? "document_epoch_changed"
|
|
202
|
+
: delta.truncated === true
|
|
203
|
+
? "semantic_delta_truncated"
|
|
204
|
+
: targetRef && targetFrameEpochSame !== true
|
|
205
|
+
? "target_frame_epoch_changed"
|
|
206
|
+
: targetRef && targetIdentityContinues !== true
|
|
207
|
+
? "target_semantic_identity_changed"
|
|
208
|
+
: "post_snapshot_ready",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function mapApplicationTargetToPostSnapshot(before, after, target, beforePrivateState, afterPrivateState) {
|
|
213
|
+
const identity = applicationTargetIdentity(target, beforePrivateState);
|
|
214
|
+
if (!identity) {
|
|
215
|
+
return { previous_target_ref: null, target_identity_continues: null, mapped_post_target_ref: null, reason: "no_semantic_target" };
|
|
216
|
+
}
|
|
217
|
+
const processContinuity = applicationProcessContinuity(before, after, beforePrivateState, afterPrivateState);
|
|
218
|
+
if (processContinuity !== "same") {
|
|
219
|
+
return {
|
|
220
|
+
previous_target_ref: identity.ref,
|
|
221
|
+
target_identity_continues: false,
|
|
222
|
+
mapped_post_target_ref: null,
|
|
223
|
+
reason: processContinuity === "unknown" ? "application_process_identity_unavailable" : "application_process_changed",
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const bindings = afterPrivateState?.application_ref_bindings;
|
|
227
|
+
if (!bindings?.entries) {
|
|
228
|
+
return { previous_target_ref: identity.ref, target_identity_continues: false, mapped_post_target_ref: null, reason: "post_identity_bindings_unavailable" };
|
|
229
|
+
}
|
|
230
|
+
const selectorKey = JSON.stringify(identity.selector || {});
|
|
231
|
+
for (const [ref, binding] of bindings.entries()) {
|
|
232
|
+
if (binding?.occurrence !== identity.occurrence || JSON.stringify(binding?.selector || {}) !== selectorKey) continue;
|
|
233
|
+
if (!applicationOwnerWindowContinues(identity.owner_window_bounds, boundedBox(binding?.owner_window_bounds))) {
|
|
234
|
+
return { previous_target_ref: identity.ref, target_identity_continues: false, mapped_post_target_ref: null, reason: "owner_window_changed" };
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
previous_target_ref: identity.ref,
|
|
238
|
+
target_identity_continues: true,
|
|
239
|
+
mapped_post_target_ref: typeof ref === "string" ? ref : null,
|
|
240
|
+
reason: "selector_occurrence_and_owner_window_match",
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return { previous_target_ref: identity.ref, target_identity_continues: false, mapped_post_target_ref: null, reason: "target_identity_missing_from_post_snapshot" };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function applicationTargetIdentity(target, privateState) {
|
|
247
|
+
const semantic = target?.kind === "ref" ? target : target?.kind === "point" ? target.semantic_delivery : null;
|
|
248
|
+
if (!semantic) return null;
|
|
249
|
+
const ref = typeof semantic.ref === "string" && semantic.ref ? semantic.ref : null;
|
|
250
|
+
const privateBinding = ref ? privateState?.application_ref_bindings?.get?.(ref) || null : null;
|
|
251
|
+
const selector = semantic.selector || privateBinding?.selector || null;
|
|
252
|
+
const occurrence = Number.isInteger(semantic.occurrence) ? semantic.occurrence : privateBinding?.occurrence;
|
|
253
|
+
if (!selector || !Number.isInteger(occurrence)) return null;
|
|
254
|
+
return {
|
|
255
|
+
ref,
|
|
256
|
+
selector,
|
|
257
|
+
occurrence,
|
|
258
|
+
owner_window_bounds: boundedBox(semantic.owner_window_bounds || privateBinding?.owner_window_bounds),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function applicationProcessContinuity(before, after, beforePrivateState, afterPrivateState) {
|
|
263
|
+
const beforeApplication = nativeBoundedString(before?.target?.application, 300, false);
|
|
264
|
+
const afterApplication = nativeBoundedString(after?.target?.application, 300, false);
|
|
265
|
+
if (!beforeApplication || !afterApplication) return "unknown";
|
|
266
|
+
if (beforeApplication !== afterApplication) return "changed";
|
|
267
|
+
const left = nativeBoundedString(before?.target?.process_name, 1000, true);
|
|
268
|
+
const right = nativeBoundedString(after?.target?.process_name, 1000, true);
|
|
269
|
+
if (left === null || right === null) return "unknown";
|
|
270
|
+
if (left && right && left !== right) return "changed";
|
|
271
|
+
const changed = applicationProcessEpochChanged(beforePrivateState, afterPrivateState);
|
|
272
|
+
return changed === null ? "unknown" : changed ? "changed" : "same";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function browserHistoryEntryChanged(beforePrivateState, afterPrivateState) {
|
|
276
|
+
const beforeKey = privateBrowserString(beforePrivateState?.browser_history_entry_key, 512);
|
|
277
|
+
const afterKey = privateBrowserString(afterPrivateState?.browser_history_entry_key, 512);
|
|
278
|
+
if (!beforeKey || !afterKey) return null;
|
|
279
|
+
return beforeKey !== afterKey;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function applicationProcessEpochChanged(beforePrivateState, afterPrivateState) {
|
|
283
|
+
const beforeId = beforePrivateState?.application_process_id;
|
|
284
|
+
const afterId = afterPrivateState?.application_process_id;
|
|
285
|
+
const beforeGeneration = beforePrivateState?.application_process_generation;
|
|
286
|
+
const afterGeneration = afterPrivateState?.application_process_generation;
|
|
287
|
+
if (!Number.isSafeInteger(beforeId) || beforeId < 1 || !Number.isSafeInteger(afterId) || afterId < 1
|
|
288
|
+
|| typeof beforeGeneration !== "string" || !beforeGeneration || beforeGeneration.length > 2048 || /[\r\n\0]/.test(beforeGeneration)
|
|
289
|
+
|| typeof afterGeneration !== "string" || !afterGeneration || afterGeneration.length > 2048 || /[\r\n\0]/.test(afterGeneration)) return null;
|
|
290
|
+
return beforeId !== afterId || beforeGeneration !== afterGeneration;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function nativeBoundedString(value, maxLength, allowEmpty) {
|
|
294
|
+
if (typeof value !== "string" || value.length > maxLength || value.includes("\0") || (!allowEmpty && !value)) return null;
|
|
295
|
+
return value;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function privateBrowserString(value, maxLength) {
|
|
299
|
+
return nativeBoundedString(value, maxLength, false) || "";
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function privateBrowserText(value, maxLength) {
|
|
303
|
+
return nativeBoundedString(value, maxLength, true) ?? "";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function browserObservationTabId(value) {
|
|
307
|
+
return Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function boundedStringChanged(left, right, maxLength, allowEmpty) {
|
|
311
|
+
const a = nativeBoundedString(left, maxLength, allowEmpty);
|
|
312
|
+
const b = nativeBoundedString(right, maxLength, allowEmpty);
|
|
313
|
+
return a === null || b === null ? null : a !== b;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function browserObservationEpoch(observation) {
|
|
317
|
+
return privateBrowserString(observation?.capture?.semantic_epoch, 9000)
|
|
318
|
+
|| privateBrowserString(observation?.document_epoch, 9000);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function browserDocumentEpochChanged(before, after) {
|
|
322
|
+
const left = browserObservationEpoch(before);
|
|
323
|
+
const right = browserObservationEpoch(after);
|
|
324
|
+
return left && right ? left !== right : null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function applicationOwnerWindowContinues(beforeBounds, afterBounds) {
|
|
328
|
+
if (!beforeBounds && !afterBounds) return true;
|
|
329
|
+
if (!beforeBounds || !afterBounds) return false;
|
|
330
|
+
return ["x", "y", "width", "height"].every((key) => nearNumber(beforeBounds[key], afterBounds[key], 1));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function projectPostObservation(observation, detail) {
|
|
334
|
+
if (detail === "full") return observation;
|
|
335
|
+
const base = compactObservationBase(observation);
|
|
336
|
+
if (observation.surface === "browser") return compactBrowserObservation(observation, base);
|
|
337
|
+
return compactApplicationObservation(observation, base);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function browserCaptureMetadata(captured, args) {
|
|
341
|
+
const source = record(captured?.capture);
|
|
342
|
+
return {
|
|
343
|
+
semantic: true,
|
|
344
|
+
screenshot: args.include_screenshot !== false,
|
|
345
|
+
atomic: source.atomic === true,
|
|
346
|
+
navigation_coherent: source.navigation_coherent === true,
|
|
347
|
+
frame_epochs_coherent: source.frame_epochs_coherent === true,
|
|
348
|
+
coherence: privateBrowserString(source.coherence, 128) || "unknown",
|
|
349
|
+
semantic_epoch: privateBrowserString(source.semantic_epoch, 9000),
|
|
350
|
+
cdp_epoch: privateBrowserString(source.cdp_epoch, 9000),
|
|
351
|
+
cdp: source.cdp === true,
|
|
352
|
+
cdp_components: publicRecordOrNull(source.cdp_components),
|
|
353
|
+
screenshot_source: privateBrowserString(source.screenshot_source, 64) || "none",
|
|
354
|
+
screenshot_format: source.screenshot_format === "png" || source.screenshot_format === "jpeg" ? source.screenshot_format : "",
|
|
355
|
+
screenshot_quality: validScreenshotQuality(source.screenshot_quality),
|
|
356
|
+
screenshot_sha256: validSha256(source.screenshot_sha256),
|
|
357
|
+
...(privateBrowserText(source.fallback_reason, 500) ? { fallback_reason: privateBrowserText(source.fallback_reason, 500) } : {}),
|
|
358
|
+
...(privateBrowserText(source.screenshot_fallback_reason, 500) ? { screenshot_fallback_reason: privateBrowserText(source.screenshot_fallback_reason, 500) } : {}),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function browserCapabilities(captured, accessibility, capture) {
|
|
363
|
+
const fusedActionRefs = Array.isArray(accessibility?.nodes)
|
|
364
|
+
? accessibility.nodes.filter((node) => node?.action_ref_confidence === "high" && typeof node?.action_ref === "string").length
|
|
365
|
+
: 0;
|
|
366
|
+
return {
|
|
367
|
+
snapshot_bound_refs: true,
|
|
368
|
+
stable_backend_refs: true,
|
|
369
|
+
screenshot: true,
|
|
370
|
+
action_verification: true,
|
|
371
|
+
chromium_accessibility_tree: Boolean(accessibility && accessibility.available !== false),
|
|
372
|
+
fused_accessibility_action_refs: fusedActionRefs,
|
|
373
|
+
cdp_surface_screenshot: capture.screenshot_source === "cdp_surface",
|
|
374
|
+
document_epoch_preflight: Boolean(capture.semantic_epoch),
|
|
375
|
+
snapshot_bound_visual_points: capture.screenshot_source === "cdp_surface"
|
|
376
|
+
&& capture.navigation_coherent === true
|
|
377
|
+
&& Boolean(capture.screenshot_sha256)
|
|
378
|
+
&& isPositiveViewport(captured?.viewport),
|
|
379
|
+
visual_grounding: false,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function browserSemanticProjection(captured, inspected, accessibility) {
|
|
384
|
+
return {
|
|
385
|
+
kind: accessibility?.available !== false && accessibility ? "browser-hybrid" : "browser-dom",
|
|
386
|
+
frames: sanitizeBrowserFrames(inspected.frames),
|
|
387
|
+
total_elements: nonNegativeSafeInteger(inspected.total_elements),
|
|
388
|
+
max_elements: nonNegativeSafeInteger(inspected.max_elements),
|
|
389
|
+
frames_truncated: inspected.frames_truncated === true,
|
|
390
|
+
selection: publicRecordOrNull(inspected.selection),
|
|
391
|
+
accessibility: sanitizeAccessibility(accessibility),
|
|
392
|
+
viewport: publicRecordOrNull(captured?.viewport),
|
|
393
|
+
frame_tree: Array.isArray(captured?.frame_tree) ? captured.frame_tree.map((frame) => publicRecord(frame)) : [],
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function sanitizeAccessibility(accessibility) {
|
|
398
|
+
if (!accessibility) return null;
|
|
399
|
+
const publicAccessibility = publicRecord(accessibility);
|
|
400
|
+
delete publicAccessibility.nodes;
|
|
401
|
+
return {
|
|
402
|
+
...publicAccessibility,
|
|
403
|
+
nodes: (Array.isArray(accessibility.nodes) ? accessibility.nodes : [])
|
|
404
|
+
.map((node) => publicRecord(node, new Set(["backend_dom_node_id", "backendDOMNodeId"]))),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function sanitizeBrowserFrames(frames) {
|
|
409
|
+
return (Array.isArray(frames) ? frames : []).map((frame) => {
|
|
410
|
+
const publicFrame = publicRecord(frame);
|
|
411
|
+
delete publicFrame.document;
|
|
412
|
+
delete publicFrame.elements;
|
|
413
|
+
return {
|
|
414
|
+
...publicFrame,
|
|
415
|
+
document: publicRecord(frame?.document),
|
|
416
|
+
elements: (Array.isArray(frame?.elements) ? frame.elements : [])
|
|
417
|
+
.map((element) => publicRecord(element, new Set(["backend_dom_node_id", "backendDOMNodeId"]))),
|
|
418
|
+
};
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function browserSemanticDelta(before, after) {
|
|
423
|
+
return mapSemanticDelta(
|
|
424
|
+
browserElementMap(before),
|
|
425
|
+
browserElementMap(after),
|
|
426
|
+
browserElementSummary,
|
|
427
|
+
BROWSER_COMPARABLE_FIELDS,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function browserElementMap(observation) {
|
|
432
|
+
const map = new Map();
|
|
433
|
+
const topEpoch = browserObservationEpoch(observation);
|
|
434
|
+
if (!topEpoch) return map;
|
|
435
|
+
for (const frame of observation.semantic?.frames || []) {
|
|
436
|
+
if (!Number.isSafeInteger(frame?.frame_id) || frame.frame_id < 0) continue;
|
|
437
|
+
const frameEpoch = privateBrowserString(frame?.document?.epoch, 9000) || topEpoch;
|
|
438
|
+
const frameUrl = privateBrowserString(frame?.document?.url, 32768);
|
|
439
|
+
if (!frameEpoch || !frameUrl) continue;
|
|
440
|
+
for (const element of frame?.elements || []) {
|
|
441
|
+
if (typeof element?.ref !== "string" || !element.ref) continue;
|
|
442
|
+
map.set(`${topEpoch}|${frame.frame_id}|${frameEpoch}|${frameUrl}|${element.ref}`, { ...element, frame_id: frame.frame_id });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return map;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function browserFrameEpochsChanged(before, after) {
|
|
449
|
+
if (before.semantic?.frames_truncated === true || after.semantic?.frames_truncated === true) return null;
|
|
450
|
+
const left = browserFrameEpochMap(before);
|
|
451
|
+
const right = browserFrameEpochMap(after);
|
|
452
|
+
if (!left || !right) return null;
|
|
453
|
+
if (left.size !== right.size) return true;
|
|
454
|
+
for (const [frameId, identity] of left) if (right.get(frameId) !== identity) return true;
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function browserFrameEpochMap(observation) {
|
|
459
|
+
const map = new Map();
|
|
460
|
+
for (const frame of observation.semantic?.frames || []) {
|
|
461
|
+
if (!Number.isSafeInteger(frame?.frame_id) || frame.frame_id < 0) return null;
|
|
462
|
+
const epoch = privateBrowserString(frame?.document?.epoch, 9000);
|
|
463
|
+
const url = privateBrowserString(frame?.document?.url, 32768);
|
|
464
|
+
if (!epoch || !url) return null;
|
|
465
|
+
map.set(frame.frame_id, `${epoch}\u0000${url}`);
|
|
466
|
+
}
|
|
467
|
+
return map;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function browserTargetFrameEpochSame(before, after, target) {
|
|
471
|
+
if (!Number.isSafeInteger(target?.frame_id) || target.frame_id < 0) return false;
|
|
472
|
+
const beforeFrame = (before.semantic?.frames || []).find((frame) => frame?.frame_id === target.frame_id);
|
|
473
|
+
const afterFrame = (after.semantic?.frames || []).find((frame) => frame?.frame_id === target.frame_id);
|
|
474
|
+
if (!beforeFrame || !afterFrame) return false;
|
|
475
|
+
const beforeEpoch = privateBrowserString(beforeFrame.document?.epoch, 9000);
|
|
476
|
+
const afterEpoch = privateBrowserString(afterFrame.document?.epoch, 9000);
|
|
477
|
+
const beforeUrl = privateBrowserString(beforeFrame.document?.url, 32768);
|
|
478
|
+
const afterUrl = privateBrowserString(afterFrame.document?.url, 32768);
|
|
479
|
+
return Boolean(beforeEpoch && afterEpoch && beforeUrl && afterUrl)
|
|
480
|
+
&& beforeEpoch === afterEpoch
|
|
481
|
+
&& beforeUrl === afterUrl;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function browserElementAt(observation, frameId, ref) {
|
|
485
|
+
const frame = (observation.semantic?.frames || []).find((candidate) => candidate?.frame_id === frameId);
|
|
486
|
+
return (frame?.elements || []).find((element) => element?.ref === ref) || null;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function browserElementIdentityMatches(before, after) {
|
|
490
|
+
if (!before || !after) return false;
|
|
491
|
+
return BROWSER_IDENTITY_FIELDS.every((field) => !Object.hasOwn(before, field) || equivalentField(before[field], after[field], field));
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function applicationSemanticDelta(before, after, beforePrivateState, afterPrivateState) {
|
|
495
|
+
return mapSemanticDelta(
|
|
496
|
+
applicationElementMap(before, beforePrivateState),
|
|
497
|
+
applicationElementMap(after, afterPrivateState),
|
|
498
|
+
applicationElementSummary,
|
|
499
|
+
APPLICATION_COMPARABLE_FIELDS,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function applicationElementMap(observation, privateState = null) {
|
|
504
|
+
const map = new Map();
|
|
505
|
+
const bindings = privateState?.application_ref_bindings;
|
|
506
|
+
const fallbackOccurrences = new Map();
|
|
507
|
+
for (const element of observation.semantic?.elements || []) {
|
|
508
|
+
const binding = bindings?.get?.(element?.ref);
|
|
509
|
+
let key = "";
|
|
510
|
+
if (binding?.selector && Number.isInteger(binding.occurrence)) {
|
|
511
|
+
key = `${JSON.stringify(binding.selector)}#${binding.occurrence}`;
|
|
512
|
+
} else {
|
|
513
|
+
const identity = JSON.stringify([
|
|
514
|
+
element.identifier || "", element.role || "", element.subrole || "", element.name || "",
|
|
515
|
+
element.title || "", element.description || "",
|
|
516
|
+
]);
|
|
517
|
+
const occurrence = fallbackOccurrences.get(identity) || 0;
|
|
518
|
+
fallbackOccurrences.set(identity, occurrence + 1);
|
|
519
|
+
key = `${identity}#${occurrence}`;
|
|
520
|
+
}
|
|
521
|
+
map.set(key, {
|
|
522
|
+
...element,
|
|
523
|
+
_machine_owner_window_bounds: binding?.owner_window_bounds || null,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
return map;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function mapSemanticDelta(beforeMap, afterMap, summarize, comparableFields) {
|
|
530
|
+
const added = [];
|
|
531
|
+
const removed = [];
|
|
532
|
+
const changed = [];
|
|
533
|
+
let addedCount = 0;
|
|
534
|
+
let removedCount = 0;
|
|
535
|
+
let changedCount = 0;
|
|
536
|
+
for (const [key, value] of afterMap) {
|
|
537
|
+
if (!beforeMap.has(key)) {
|
|
538
|
+
addedCount += 1;
|
|
539
|
+
pushBounded(added, summarize(value));
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const previous = beforeMap.get(key);
|
|
543
|
+
const fields = comparableFields
|
|
544
|
+
.filter((field) => !equivalentField(previous?.[field], value?.[field], field))
|
|
545
|
+
.map(publicChangedField);
|
|
546
|
+
if (!fields.length) continue;
|
|
547
|
+
changedCount += 1;
|
|
548
|
+
pushBounded(changed, {
|
|
549
|
+
before: summarize(previous),
|
|
550
|
+
after: summarize(value),
|
|
551
|
+
changed_fields: fields,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
for (const [key, value] of beforeMap) {
|
|
555
|
+
if (afterMap.has(key)) continue;
|
|
556
|
+
removedCount += 1;
|
|
557
|
+
pushBounded(removed, summarize(value));
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
before_count: beforeMap.size,
|
|
561
|
+
after_count: afterMap.size,
|
|
562
|
+
added_count: addedCount,
|
|
563
|
+
removed_count: removedCount,
|
|
564
|
+
changed_count: changedCount,
|
|
565
|
+
added,
|
|
566
|
+
removed,
|
|
567
|
+
changed,
|
|
568
|
+
truncated: addedCount > added.length || removedCount > removed.length || changedCount > changed.length,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function browserElementSummary(element) {
|
|
573
|
+
return {
|
|
574
|
+
ref: typeof element?.ref === "string" ? element.ref : "",
|
|
575
|
+
frame_id: Number.isInteger(element?.frame_id) ? element.frame_id : null,
|
|
576
|
+
role: boundedText(element?.role, 200),
|
|
577
|
+
name: boundedText(element?.name || element?.label || element?.placeholder, 500),
|
|
578
|
+
visible: element?.visible === true,
|
|
579
|
+
enabled: element?.enabled === true,
|
|
580
|
+
editable: element?.editable === true,
|
|
581
|
+
checked: element?.checked === true,
|
|
582
|
+
focused: element?.focused === true,
|
|
583
|
+
sensitive: element?.sensitive === true,
|
|
584
|
+
bounding_box: boundedBox(element?.bounding_box),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function applicationElementSummary(element) {
|
|
589
|
+
return {
|
|
590
|
+
ref: typeof element?.ref === "string" ? element.ref : "",
|
|
591
|
+
role: boundedText(element?.role, 200),
|
|
592
|
+
subrole: boundedText(element?.subrole, 200),
|
|
593
|
+
name: boundedText(element?.name || element?.title || element?.description, 500),
|
|
594
|
+
identifier: boundedText(element?.identifier, 500),
|
|
595
|
+
enabled: element?.enabled === true,
|
|
596
|
+
focused: element?.focused === true,
|
|
597
|
+
checked: typeof element?.checked === "boolean" ? element.checked : null,
|
|
598
|
+
selected: typeof element?.selected === "boolean" ? element.selected : null,
|
|
599
|
+
expanded: typeof element?.expanded === "boolean" ? element.expanded : null,
|
|
600
|
+
visible: element?.visible === true,
|
|
601
|
+
sensitive: element?.sensitive === true,
|
|
602
|
+
bounding_box: boundedBox(element?.bounding_box),
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function equivalentField(left, right, field) {
|
|
607
|
+
if (field !== "bounding_box" && field !== "_machine_owner_window_bounds") return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
|
|
608
|
+
if (!left && !right) return true;
|
|
609
|
+
if (!left || !right) return false;
|
|
610
|
+
const tolerance = field === "_machine_owner_window_bounds" ? 1 : 2;
|
|
611
|
+
return ["x", "y", "width", "height"].every((key) => nearNumber(left[key], right[key], tolerance));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function publicChangedField(field) {
|
|
615
|
+
return field === "_machine_owner_window_bounds" ? "owner_window" : field;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function compactObservationBase(observation) {
|
|
619
|
+
return {
|
|
620
|
+
snapshot_id: observation.snapshot_id,
|
|
621
|
+
surface: observation.surface,
|
|
622
|
+
captured_at: observation.captured_at,
|
|
623
|
+
target: observation.target,
|
|
624
|
+
capture: observation.capture,
|
|
625
|
+
capabilities: observation.capabilities,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function compactBrowserObservation(observation, base) {
|
|
630
|
+
const accessibility = observation.semantic?.accessibility;
|
|
631
|
+
return {
|
|
632
|
+
...base,
|
|
633
|
+
document_epoch: observation.document_epoch || "",
|
|
634
|
+
semantic: {
|
|
635
|
+
kind: observation.semantic?.kind || "",
|
|
636
|
+
total_elements: nonNegativeSafeInteger(observation.semantic?.total_elements),
|
|
637
|
+
max_elements: nonNegativeSafeInteger(observation.semantic?.max_elements),
|
|
638
|
+
frames_truncated: observation.semantic?.frames_truncated === true,
|
|
639
|
+
selection: observation.semantic?.selection || null,
|
|
640
|
+
viewport: observation.semantic?.viewport || null,
|
|
641
|
+
accessibility: accessibility ? {
|
|
642
|
+
available: accessibility.available !== false,
|
|
643
|
+
returned_nodes: nonNegativeSafeInteger(accessibility.returned_nodes),
|
|
644
|
+
observed_nodes: nonNegativeSafeInteger(accessibility.observed_nodes),
|
|
645
|
+
ignored_nodes: nonNegativeSafeInteger(accessibility.ignored_nodes),
|
|
646
|
+
truncated: accessibility.truncated === true,
|
|
647
|
+
query_matched: typeof accessibility.query_matched === "boolean" ? accessibility.query_matched : null,
|
|
648
|
+
query_match_count: optionalFiniteNumber(accessibility.query_match_count),
|
|
649
|
+
query_search_exhaustive: typeof accessibility.query_search_exhaustive === "boolean" ? accessibility.query_search_exhaustive : null,
|
|
650
|
+
top_query_score: optionalFiniteNumber(accessibility.top_query_score),
|
|
651
|
+
failed_frame_count: nonNegativeSafeInteger(accessibility.failed_frame_count),
|
|
652
|
+
} : null,
|
|
653
|
+
},
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function compactApplicationObservation(observation, base) {
|
|
658
|
+
const elements = Array.isArray(observation.semantic?.elements) ? observation.semantic.elements : [];
|
|
659
|
+
const returned = elements.slice(0, MAX_APPLICATION_POST_ELEMENTS).map(applicationElementSummary);
|
|
660
|
+
return {
|
|
661
|
+
...base,
|
|
662
|
+
semantic: {
|
|
663
|
+
kind: observation.semantic?.kind || "",
|
|
664
|
+
element_count: elements.length,
|
|
665
|
+
truncated: observation.semantic?.truncated === true,
|
|
666
|
+
menus_included: observation.semantic?.menus_included === true,
|
|
667
|
+
selection: observation.semantic?.selection || null,
|
|
668
|
+
elements: returned,
|
|
669
|
+
elements_truncated: elements.length > returned.length,
|
|
670
|
+
},
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function semanticRefs(values) {
|
|
675
|
+
const refs = [];
|
|
676
|
+
for (const value of values || []) {
|
|
677
|
+
const ref = typeof value?.ref === "string" ? value.ref : "";
|
|
678
|
+
if (ref && !refs.includes(ref)) refs.push(ref);
|
|
679
|
+
}
|
|
680
|
+
return refs;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function emptyDelta() {
|
|
684
|
+
return { added: [], removed: [], changed: [], truncated: false };
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function screenshotChange(before, after) {
|
|
688
|
+
const left = validSha256(before.capture?.screenshot_sha256);
|
|
689
|
+
const right = validSha256(after.capture?.screenshot_sha256);
|
|
690
|
+
return left && right ? left !== right : null;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function validScreenshotQuality(value) {
|
|
694
|
+
if (value === undefined || value === null) return null;
|
|
695
|
+
return Number.isInteger(value) && value >= 1 && value <= 100 ? value : null;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function validSha256(value) {
|
|
699
|
+
if (typeof value !== "string") return "";
|
|
700
|
+
const digest = value.toLowerCase();
|
|
701
|
+
return /^[a-f0-9]{64}$/.test(digest) ? digest : "";
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function semanticFingerprint(observation) {
|
|
705
|
+
return createHash("sha256").update(JSON.stringify({ target: observation.target, semantic: observation.semantic })).digest("hex");
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function isPositiveViewport(value) {
|
|
709
|
+
return Boolean(value
|
|
710
|
+
&& typeof value.width === "number" && Number.isFinite(value.width) && value.width > 0
|
|
711
|
+
&& typeof value.height === "number" && Number.isFinite(value.height) && value.height > 0
|
|
712
|
+
&& typeof value.scale === "number" && Number.isFinite(value.scale) && value.scale > 0);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function record(value) {
|
|
716
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function publicRecord(value, excluded = null) {
|
|
720
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
721
|
+
const output = {};
|
|
722
|
+
for (const [key, item] of Object.entries(value)) {
|
|
723
|
+
if (key.startsWith("_machine_") || excluded?.has(key)) continue;
|
|
724
|
+
output[key] = item;
|
|
725
|
+
}
|
|
726
|
+
return output;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function publicRecordOrNull(value) {
|
|
730
|
+
return value && typeof value === "object" && !Array.isArray(value) ? publicRecord(value) : null;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function nonNegativeSafeInteger(value) {
|
|
734
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function optionalFiniteNumber(value) {
|
|
738
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
function recordOrNull(value) {
|
|
743
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function pushBounded(list, value) {
|
|
747
|
+
if (list.length < MAX_DELTA_ENTRIES) list.push(value);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function boundedBox(value) {
|
|
751
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
752
|
+
const out = {};
|
|
753
|
+
for (const key of ["x", "y", "width", "height"]) {
|
|
754
|
+
const number = value[key];
|
|
755
|
+
if (typeof number !== "number" || !Number.isFinite(number)) return null;
|
|
756
|
+
out[key] = Math.round(number * 100) / 100;
|
|
757
|
+
}
|
|
758
|
+
return out;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function boundedText(value, max) {
|
|
762
|
+
return typeof value === "string" ? value.slice(0, max) : "";
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function nearNumber(left, right, tolerance) {
|
|
766
|
+
return typeof left === "number" && Number.isFinite(left)
|
|
767
|
+
&& typeof right === "number" && Number.isFinite(right)
|
|
768
|
+
&& Math.abs(left - right) <= tolerance;
|
|
769
|
+
}
|