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,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { mkdtemp, open, opendir, realpath, rm } from "node:fs/promises";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
3
4
|
import { basename, extname, join } from "node:path";
|
|
4
5
|
import { performance } from "node:perf_hooks";
|
|
5
6
|
import { createToolAuthorizer } from "./policy.mjs";
|
|
7
|
+
import { classifyOperationalError } from "./log.mjs";
|
|
8
|
+
import { BridgeError } from "./errors.mjs";
|
|
9
|
+
import { MACOS_UI_JXA } from "./app-automation-macos-jxa.mjs";
|
|
6
10
|
|
|
7
11
|
const MAX_APPLICATIONS = 1000;
|
|
8
12
|
const MAX_APPLICATION_SCAN_ENTRIES = 20_000;
|
|
@@ -10,14 +14,19 @@ const MAX_APPLICATION_SCAN_DEPTH = 3;
|
|
|
10
14
|
const MAX_UI_ELEMENTS = 500;
|
|
11
15
|
const MAX_UI_DEPTH = 12;
|
|
12
16
|
const MAX_TEXT_CHARS = 4000;
|
|
17
|
+
const MAX_APPLICATION_SCREENSHOT_BYTES = 32 * 1024 * 1024;
|
|
13
18
|
const APP_NAME_PATTERN = /^[^\0\r\n]{1,300}$/;
|
|
14
19
|
const DEFAULT_APPLICATION_CACHE_MS = 30_000;
|
|
20
|
+
const VALUE_VERIFICATION_TTL_MS = 60_000;
|
|
21
|
+
const MAX_VALUE_VERIFICATION_HANDLES = 32;
|
|
22
|
+
const APPLICATION_KEY_PRESS_KEYS = new Set(["Enter", "Tab", "Escape", "Backspace", "Delete", "ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown", "Home", "End", "PageUp", "PageDown", "Space"]);
|
|
15
23
|
|
|
16
24
|
export class AppAutomationManager {
|
|
17
|
-
constructor({ policy, authorizeTool = null, displayPath, runProcess, readResourceText, throwIfCancelled = () => {}, platform = process.platform, home = homedir(), applicationRoots = null, applicationCacheMs = DEFAULT_APPLICATION_CACHE_MS, now = () => performance.now() }) {
|
|
25
|
+
constructor({ policy, authorizeTool = null, displayPath, projectCapabilities = (capabilities) => capabilities, runProcess, readResourceText, throwIfCancelled = () => {}, platform = process.platform, home = homedir(), applicationRoots = null, applicationCacheMs = DEFAULT_APPLICATION_CACHE_MS, now = () => performance.now(), backgroundVisualBackend = process.env.MBM_MACOS_BACKGROUND_VISUAL_BACKEND || "disabled", backgroundInputService = null }) {
|
|
18
26
|
this.policy = policy || {};
|
|
19
27
|
this.authorizeTool = createToolAuthorizer(this.policy, authorizeTool);
|
|
20
28
|
this.displayPath = displayPath;
|
|
29
|
+
this.projectCapabilities = projectCapabilities;
|
|
21
30
|
this.runProcess = runProcess;
|
|
22
31
|
this.readResourceText = readResourceText;
|
|
23
32
|
this.throwIfCancelled = throwIfCancelled;
|
|
@@ -27,6 +36,34 @@ export class AppAutomationManager {
|
|
|
27
36
|
this.applicationCacheMs = Math.max(0, Number(applicationCacheMs) || 0);
|
|
28
37
|
this.now = now;
|
|
29
38
|
this.applicationCache = null;
|
|
39
|
+
this.backgroundVisualBackend = normalizeBackgroundVisualBackend(backgroundVisualBackend);
|
|
40
|
+
this.backgroundInputService = backgroundInputService;
|
|
41
|
+
this.valueVerificationStore = new Map();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
visualPointCapability() {
|
|
45
|
+
const configured = this.platform === "darwin" && this.backgroundVisualBackend === "skylight-experimental";
|
|
46
|
+
const serviceStatus = configured && typeof this.backgroundInputService?.status === "function"
|
|
47
|
+
? this.backgroundInputService.status()
|
|
48
|
+
: null;
|
|
49
|
+
const injectedServiceWithoutStatus = configured && this.backgroundInputService !== null && serviceStatus === null;
|
|
50
|
+
return {
|
|
51
|
+
available: configured && (serviceStatus?.available === true || injectedServiceWithoutStatus),
|
|
52
|
+
configured,
|
|
53
|
+
probed: serviceStatus?.probed === true || injectedServiceWithoutStatus,
|
|
54
|
+
backend: configured ? "skylight-experimental" : "disabled",
|
|
55
|
+
experimental: configured,
|
|
56
|
+
non_disruptive_intent: configured,
|
|
57
|
+
error_class: configured && serviceStatus?.error ? serviceStatus.error : "",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async probeVisualPointCapability(context = {}) {
|
|
62
|
+
if (this.backgroundVisualBackend !== "skylight-experimental" || !this.backgroundInputService) return this.visualPointCapability();
|
|
63
|
+
if (typeof this.backgroundInputService.probe === "function") {
|
|
64
|
+
await this.backgroundInputService.probe(context).catch(() => { /* Optional capability probing must not fail application inventory. */ });
|
|
65
|
+
}
|
|
66
|
+
return this.visualPointCapability();
|
|
30
67
|
}
|
|
31
68
|
|
|
32
69
|
capabilities() {
|
|
@@ -36,6 +73,8 @@ export class AppAutomationManager {
|
|
|
36
73
|
open: true,
|
|
37
74
|
accessibility_inspection: this.platform === "darwin",
|
|
38
75
|
structured_accessibility_actions: this.platform === "darwin",
|
|
76
|
+
window_screenshot: this.platform === "darwin",
|
|
77
|
+
background_visual_point: this.visualPointCapability(),
|
|
39
78
|
arbitrary_script_execution: false,
|
|
40
79
|
permission_note: this.platform === "darwin"
|
|
41
80
|
? "UI inspection and actions require macOS Accessibility permission for the Machine Bridge runtime."
|
|
@@ -46,7 +85,7 @@ export class AppAutomationManager {
|
|
|
46
85
|
async listApplications(args = {}, context = {}) {
|
|
47
86
|
this.authorizeTool("list_local_applications");
|
|
48
87
|
this.throwIfCancelled(context);
|
|
49
|
-
const query =
|
|
88
|
+
const query = optionalString(args.query, "query", 1000).trim().toLowerCase();
|
|
50
89
|
const limit = clampInt(args.max_results, 200, 1, MAX_APPLICATIONS);
|
|
51
90
|
const discovery = await this.discoverApplications(context);
|
|
52
91
|
const matched = discovery.applications
|
|
@@ -54,13 +93,16 @@ export class AppAutomationManager {
|
|
|
54
93
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
55
94
|
const filtered = matched
|
|
56
95
|
.slice(0, limit)
|
|
57
|
-
.map((item) => ({ ...item, path: item.path ? this.displayPath(item.path) : "" }));
|
|
96
|
+
.map((item) => ({ ...item, path: item.path ? this.displayPath(item.path, context) : "" }));
|
|
58
97
|
return {
|
|
59
98
|
platform: this.platform,
|
|
60
99
|
applications: filtered,
|
|
61
100
|
truncated: discovery.truncated || matched.length > limit,
|
|
62
101
|
scanned_entries: discovery.visitedEntries,
|
|
63
|
-
|
|
102
|
+
warnings: discovery.warnings.map((warning) => ({
|
|
103
|
+
path: this.displayPath(warning.path, context), error_class: warning.error_class,
|
|
104
|
+
})),
|
|
105
|
+
capabilities: this.projectCapabilities(this.capabilities(), context),
|
|
64
106
|
};
|
|
65
107
|
}
|
|
66
108
|
|
|
@@ -70,6 +112,7 @@ export class AppAutomationManager {
|
|
|
70
112
|
return this.applicationCache.result;
|
|
71
113
|
}
|
|
72
114
|
const result = await this.scanApplications(context);
|
|
115
|
+
this.throwIfCancelled(context);
|
|
73
116
|
this.applicationCache = { createdAt: this.now(), result };
|
|
74
117
|
return result;
|
|
75
118
|
}
|
|
@@ -93,18 +136,19 @@ export class AppAutomationManager {
|
|
|
93
136
|
this.authorizeTool("open_local_application");
|
|
94
137
|
const application = requiredApplication(args.application);
|
|
95
138
|
const target = optionalString(args.target, "target", 32768);
|
|
139
|
+
const background = optionalBoolean(args.background, "background", false);
|
|
96
140
|
this.throwIfCancelled(context);
|
|
97
141
|
const resolvedApplication = this.platform === "darwin" ? application : await this.resolveApplicationReference(application, context);
|
|
98
142
|
let command;
|
|
99
143
|
if (this.platform === "darwin") {
|
|
100
144
|
const argv = ["-a", application];
|
|
101
|
-
if (
|
|
145
|
+
if (background) argv.push("-g");
|
|
102
146
|
if (target) argv.push(target);
|
|
103
147
|
command = { cmd: "open", argv };
|
|
104
148
|
} else if (this.platform === "win32") {
|
|
105
|
-
const
|
|
106
|
-
const targetFragment = target ? ` -ArgumentList
|
|
107
|
-
command = { cmd: "powershell.exe", argv: ["-NoProfile", "-NonInteractive", "-Command", `Start-Process -FilePath
|
|
149
|
+
const fileLiteral = powershellSingleQuotedLiteral(resolvedApplication);
|
|
150
|
+
const targetFragment = target ? ` -ArgumentList ${powershellSingleQuotedLiteral(target)}` : "";
|
|
151
|
+
command = { cmd: "powershell.exe", argv: ["-NoProfile", "-NonInteractive", "-Command", `Start-Process -FilePath ${fileLiteral}${targetFragment}`] };
|
|
108
152
|
} else if (resolvedApplication.toLowerCase().endsWith(".desktop")) {
|
|
109
153
|
command = { cmd: "gio", argv: ["launch", resolvedApplication, ...(target ? [target] : [])] };
|
|
110
154
|
} else {
|
|
@@ -112,8 +156,21 @@ export class AppAutomationManager {
|
|
|
112
156
|
? { cmd: resolvedApplication, argv: [target] }
|
|
113
157
|
: { cmd: resolvedApplication, argv: [] };
|
|
114
158
|
}
|
|
115
|
-
|
|
116
|
-
|
|
159
|
+
this.throwIfCancelled(context);
|
|
160
|
+
let result;
|
|
161
|
+
try {
|
|
162
|
+
result = await this.runProcess(command.cmd, command.argv, clampInt(args.timeout_seconds, 30, 1, 120) * 1000, false, 256 * 1024, context, undefined, null, { nonReplayableMutation: true });
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error instanceof BridgeError && error.details?.reason === "process_outcome_unknown_after_spawn") throw applicationLaunchOutcomeUnknown(error);
|
|
165
|
+
if (error instanceof BridgeError && error.details?.reason === "process_failed_before_spawn") {
|
|
166
|
+
throw new BridgeError("unavailable", "application launch unavailable before dispatch", {
|
|
167
|
+
retryable: true, details: { reason: "application_launch_unavailable_before_dispatch" },
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
const publicResolvedApplication = /[\\/]/.test(resolvedApplication) ? this.displayPath(resolvedApplication, context) : resolvedApplication;
|
|
173
|
+
return { application, resolved_application: publicResolvedApplication, target, platform: this.platform, ...result };
|
|
117
174
|
}
|
|
118
175
|
|
|
119
176
|
async inspectApplication(args = {}, context = {}) {
|
|
@@ -121,24 +178,347 @@ export class AppAutomationManager {
|
|
|
121
178
|
assertMacAccessibility(this.platform);
|
|
122
179
|
const application = requiredApplication(args.application);
|
|
123
180
|
const processName = applicationProcessName(application);
|
|
181
|
+
const expectedProcessId = optionalExpectedProcessId(args.expected_process_id);
|
|
182
|
+
const expectedProcessGeneration = optionalExpectedProcessGeneration(args.expected_process_generation);
|
|
183
|
+
const includeProcessId = optionalBoolean(args.include_process_id, "include_process_id", false);
|
|
184
|
+
const includeValues = optionalBoolean(args.include_values, "include_values", false);
|
|
185
|
+
const includeMenus = optionalBoolean(args.include_menus, "include_menus", false);
|
|
186
|
+
const includeGeometry = optionalBoolean(args.include_geometry, "include_geometry", false);
|
|
187
|
+
const includeWindowState = optionalBoolean(args.include_window_state, "include_window_state", false);
|
|
188
|
+
const includeProcessGeneration = includeProcessId || expectedProcessGeneration !== null;
|
|
124
189
|
const payload = {
|
|
125
190
|
operation: "inspect",
|
|
126
191
|
application: processName,
|
|
192
|
+
expectedProcessId,
|
|
193
|
+
expectedProcessGeneration,
|
|
194
|
+
includeProcessGeneration,
|
|
127
195
|
maxDepth: clampInt(args.max_depth, 6, 1, MAX_UI_DEPTH),
|
|
128
196
|
maxElements: clampInt(args.max_elements, 200, 1, MAX_UI_ELEMENTS),
|
|
129
|
-
includeValues
|
|
130
|
-
includeMenus
|
|
197
|
+
includeValues,
|
|
198
|
+
includeMenus,
|
|
199
|
+
includeGeometry,
|
|
200
|
+
includeWindowState,
|
|
131
201
|
};
|
|
132
202
|
const result = await this.runJxa(payload, clampInt(args.timeout_seconds, 30, 1, 120), context);
|
|
203
|
+
const processId = requiredPositiveInteger(result?.process_id, "application process pid");
|
|
204
|
+
const processGeneration = optionalProcessGeneration(result?.process_generation, "application process generation");
|
|
205
|
+
const machineWindow = includeWindowState ? safeSelectedMacWindow(result?.window_state) : null;
|
|
206
|
+
const { process_id: _processId, process_generation: _processGeneration, window_state: _windowState, ...publicResult } = result || {};
|
|
207
|
+
if (expectedProcessId && processId !== expectedProcessId) throw new Error("application process changed before operation");
|
|
208
|
+
if (expectedProcessGeneration && processGeneration !== expectedProcessGeneration) throw new Error("application process generation changed before operation");
|
|
209
|
+
if (includeProcessId && !processGeneration) throw new Error("application process generation is unavailable during Accessibility inspection");
|
|
133
210
|
return {
|
|
134
211
|
application,
|
|
135
212
|
process_name: processName,
|
|
136
213
|
platform: this.platform,
|
|
137
214
|
accessibility_permission_required: true,
|
|
138
|
-
...
|
|
215
|
+
...publicResult,
|
|
216
|
+
...(includeProcessId ? { _machine_process_id: processId, _machine_process_generation: processGeneration } : {}),
|
|
217
|
+
...(includeWindowState ? {
|
|
218
|
+
_machine_window_state_checked: true,
|
|
219
|
+
...(machineWindow ? { _machine_window: machineWindow } : {}),
|
|
220
|
+
} : {}),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async captureApplication(args = {}, context = {}) {
|
|
225
|
+
this.authorizeTool("computer_observe");
|
|
226
|
+
assertMacAccessibility(this.platform);
|
|
227
|
+
const application = requiredApplication(args.application);
|
|
228
|
+
const processName = applicationProcessName(application);
|
|
229
|
+
const timeoutSeconds = clampInt(args.timeout_seconds, 30, 1, 120);
|
|
230
|
+
const expectedProcessId = optionalExpectedProcessId(args.expected_process_id);
|
|
231
|
+
const expectedProcessGeneration = optionalExpectedProcessGeneration(args.expected_process_generation);
|
|
232
|
+
const captured = await this.captureMacWindow(processName, timeoutSeconds, context, expectedProcessId, expectedProcessGeneration);
|
|
233
|
+
const background_visual_point = await this.probeVisualPointCapability(context);
|
|
234
|
+
return {
|
|
235
|
+
application, process_name: processName, platform: this.platform, background_visual_point,
|
|
236
|
+
_machine_process_id: captured.window.process_id,
|
|
237
|
+
_machine_process_generation: captured.window.process_generation,
|
|
238
|
+
screenshot: { mime_type: "image/png", data: captured.bytes.toString("base64"), source: "macos_window", byte_length: captured.bytes.length, bounds: captured.window.bounds || null },
|
|
239
|
+
window: { id: captured.window.window_id, bounds: captured.window.bounds || null },
|
|
240
|
+
screen_recording_permission_required: true,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async inspectApplicationWindow(args = {}, context = {}) {
|
|
245
|
+
this.authorizeTool("computer_observe");
|
|
246
|
+
assertMacAccessibility(this.platform);
|
|
247
|
+
const application = requiredApplication(args.application);
|
|
248
|
+
const processName = applicationProcessName(application);
|
|
249
|
+
const timeoutSeconds = clampInt(args.timeout_seconds, 30, 1, 120);
|
|
250
|
+
const expectedProcessId = optionalExpectedProcessId(args.expected_process_id);
|
|
251
|
+
const expectedProcessGeneration = optionalExpectedProcessGeneration(args.expected_process_generation);
|
|
252
|
+
const includeProcessId = optionalBoolean(args.include_process_id, "include_process_id", false);
|
|
253
|
+
const window = await this.resolveMacWindow(processName, timeoutSeconds, context, expectedProcessId, expectedProcessGeneration);
|
|
254
|
+
return {
|
|
255
|
+
application,
|
|
256
|
+
process_name: processName,
|
|
257
|
+
platform: this.platform,
|
|
258
|
+
window: { id: window.window_id, bounds: window.bounds || null },
|
|
259
|
+
...(includeProcessId ? {
|
|
260
|
+
_machine_process_id: window.process_id,
|
|
261
|
+
_machine_process_generation: window.process_generation,
|
|
262
|
+
} : {}),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async pointApplication(args = {}, context = {}) {
|
|
267
|
+
this.authorizeTool("computer_act");
|
|
268
|
+
assertMacAccessibility(this.platform);
|
|
269
|
+
const visualCapability = this.visualPointCapability();
|
|
270
|
+
if (!visualCapability.available) throw new Error("macOS background visual point input is disabled or unavailable");
|
|
271
|
+
const application = requiredApplication(args.application);
|
|
272
|
+
const processName = applicationProcessName(application);
|
|
273
|
+
const normalizedX = normalizedCoordinate(args.normalized_x, "normalized_x");
|
|
274
|
+
const normalizedY = normalizedCoordinate(args.normalized_y, "normalized_y");
|
|
275
|
+
const clickCount = args.click_count === undefined ? 1 : positiveInteger(args.click_count, "click_count");
|
|
276
|
+
if (clickCount > 2) throw new Error("application visual click_count must be 1 or 2");
|
|
277
|
+
const expectedWindowId = positiveInteger(args.window_id, "window_id");
|
|
278
|
+
const expectedBounds = requiredWindowBounds(args.bounds);
|
|
279
|
+
const expectedScreenshotSha256 = requiredSha256(args.screenshot_sha256, "screenshot_sha256");
|
|
280
|
+
const expectedProcessId = optionalExpectedProcessId(args.expected_process_id);
|
|
281
|
+
const expectedProcessGeneration = optionalExpectedProcessGeneration(args.expected_process_generation);
|
|
282
|
+
const timeoutSeconds = clampInt(args.timeout_seconds, 30, 1, 120);
|
|
283
|
+
this.throwIfCancelled(context);
|
|
284
|
+
let captured;
|
|
285
|
+
try { captured = await this.captureMacWindow(processName, timeoutSeconds, context, expectedProcessId, expectedProcessGeneration); }
|
|
286
|
+
catch (error) {
|
|
287
|
+
const message = String(error?.message || error);
|
|
288
|
+
if (message.includes("application process changed before operation") || message.includes("application process generation changed before operation")) throw error;
|
|
289
|
+
throw new Error("application visual snapshot unavailable before dispatch");
|
|
290
|
+
}
|
|
291
|
+
const actualSha256 = createHash("sha256").update(captured.bytes).digest("hex");
|
|
292
|
+
if (captured.window.window_id !== expectedWindowId
|
|
293
|
+
|| !sameWindowBoundsNode(captured.window.bounds, expectedBounds)
|
|
294
|
+
|| actualSha256 !== expectedScreenshotSha256) {
|
|
295
|
+
throw new Error("application visual snapshot changed before dispatch");
|
|
296
|
+
}
|
|
297
|
+
this.throwIfCancelled(context);
|
|
298
|
+
const screenX = expectedBounds.x + normalizedX * expectedBounds.width;
|
|
299
|
+
const screenY = expectedBounds.y + normalizedY * expectedBounds.height;
|
|
300
|
+
const localX = normalizedX * expectedBounds.width;
|
|
301
|
+
const localY = normalizedY * expectedBounds.height;
|
|
302
|
+
let result;
|
|
303
|
+
try {
|
|
304
|
+
result = await this.backgroundInputService.click({
|
|
305
|
+
pid: captured.window.process_id,
|
|
306
|
+
process_generation: captured.window.process_generation,
|
|
307
|
+
window_id: expectedWindowId, screen_x: screenX, screen_y: screenY,
|
|
308
|
+
local_x: localX, local_y: localY,
|
|
309
|
+
...(clickCount === 1 ? {} : { click_count: clickCount }),
|
|
310
|
+
window_x: expectedBounds.x, window_y: expectedBounds.y,
|
|
311
|
+
window_width: expectedBounds.width, window_height: expectedBounds.height,
|
|
312
|
+
timeout_seconds: timeoutSeconds,
|
|
313
|
+
}, context);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (error instanceof BridgeError && error.code === "cancelled") throw error;
|
|
316
|
+
const message = String(error?.message || error);
|
|
317
|
+
if (message.includes("outcome is unknown") || message.includes("partially dispatched")) {
|
|
318
|
+
throw applicationVisualInputOutcomeUnknown(error);
|
|
319
|
+
}
|
|
320
|
+
if (message.includes("process_generation_changed_before_dispatch")) throw new Error("application process generation changed before operation");
|
|
321
|
+
if (message.includes("before dispatch") || message.includes("unavailable")) throw new Error("application visual input unavailable before dispatch");
|
|
322
|
+
throw applicationVisualInputOutcomeUnknown(error);
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
application, process_name: processName, ok: result?.ok === true,
|
|
326
|
+
coordinate_source: "macos_skylight_experimental", window_bound: true, screenshot_revalidated: true,
|
|
327
|
+
input_transport: typeof result?.input_transport === "string" ? result.input_transport : "",
|
|
328
|
+
experimental_backend: true,
|
|
329
|
+
focus_without_raise: result?.focus_without_raise === true,
|
|
330
|
+
front_window_validated: result?.front_window_validated === true,
|
|
331
|
+
cursor_preserved: result?.cursor_preserved === true,
|
|
332
|
+
frontmost_restored: result?.frontmost_restored === true,
|
|
333
|
+
normalized_point: { x: normalizedX, y: normalizedY },
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async dragApplication(args = {}, context = {}) {
|
|
338
|
+
this.authorizeTool("computer_act");
|
|
339
|
+
assertMacAccessibility(this.platform);
|
|
340
|
+
const visualCapability = this.visualPointCapability();
|
|
341
|
+
if (!visualCapability.available || typeof this.backgroundInputService?.drag !== "function") {
|
|
342
|
+
throw new Error("macOS background visual drag input is disabled or unavailable");
|
|
343
|
+
}
|
|
344
|
+
const application = requiredApplication(args.application);
|
|
345
|
+
const processName = applicationProcessName(application);
|
|
346
|
+
const normalizedX = normalizedCoordinate(args.normalized_x, "normalized_x");
|
|
347
|
+
const normalizedY = normalizedCoordinate(args.normalized_y, "normalized_y");
|
|
348
|
+
const destinationNormalizedX = normalizedCoordinate(args.destination_normalized_x, "destination_normalized_x");
|
|
349
|
+
const destinationNormalizedY = normalizedCoordinate(args.destination_normalized_y, "destination_normalized_y");
|
|
350
|
+
const expectedWindowId = positiveInteger(args.window_id, "window_id");
|
|
351
|
+
const expectedBounds = requiredWindowBounds(args.bounds);
|
|
352
|
+
const expectedScreenshotSha256 = requiredSha256(args.screenshot_sha256, "screenshot_sha256");
|
|
353
|
+
const expectedProcessId = optionalExpectedProcessId(args.expected_process_id);
|
|
354
|
+
const expectedProcessGeneration = optionalExpectedProcessGeneration(args.expected_process_generation);
|
|
355
|
+
const timeoutSeconds = clampInt(args.timeout_seconds, 30, 1, 120);
|
|
356
|
+
this.throwIfCancelled(context);
|
|
357
|
+
let captured;
|
|
358
|
+
try { captured = await this.captureMacWindow(processName, timeoutSeconds, context, expectedProcessId, expectedProcessGeneration); }
|
|
359
|
+
catch (error) {
|
|
360
|
+
const message = String(error?.message || error);
|
|
361
|
+
if (message.includes("application process changed before operation") || message.includes("application process generation changed before operation")) throw error;
|
|
362
|
+
throw new Error("application visual snapshot unavailable before dispatch");
|
|
363
|
+
}
|
|
364
|
+
const actualSha256 = createHash("sha256").update(captured.bytes).digest("hex");
|
|
365
|
+
if (captured.window.window_id !== expectedWindowId
|
|
366
|
+
|| !sameWindowBoundsNode(captured.window.bounds, expectedBounds)
|
|
367
|
+
|| actualSha256 !== expectedScreenshotSha256) {
|
|
368
|
+
throw new Error("application visual snapshot changed before dispatch");
|
|
369
|
+
}
|
|
370
|
+
this.throwIfCancelled(context);
|
|
371
|
+
const sourcePoint = applicationWindowPoint(expectedBounds, normalizedX, normalizedY);
|
|
372
|
+
const destinationPoint = applicationWindowPoint(expectedBounds, destinationNormalizedX, destinationNormalizedY);
|
|
373
|
+
let result;
|
|
374
|
+
try {
|
|
375
|
+
result = await this.backgroundInputService.drag({
|
|
376
|
+
pid: captured.window.process_id,
|
|
377
|
+
process_generation: captured.window.process_generation,
|
|
378
|
+
window_id: expectedWindowId,
|
|
379
|
+
screen_x: sourcePoint.screen_x, screen_y: sourcePoint.screen_y,
|
|
380
|
+
local_x: sourcePoint.local_x, local_y: sourcePoint.local_y,
|
|
381
|
+
destination_screen_x: destinationPoint.screen_x, destination_screen_y: destinationPoint.screen_y,
|
|
382
|
+
destination_local_x: destinationPoint.local_x, destination_local_y: destinationPoint.local_y,
|
|
383
|
+
window_x: expectedBounds.x, window_y: expectedBounds.y,
|
|
384
|
+
window_width: expectedBounds.width, window_height: expectedBounds.height,
|
|
385
|
+
timeout_seconds: timeoutSeconds,
|
|
386
|
+
}, context);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
if (error instanceof BridgeError && error.code === "cancelled") throw error;
|
|
389
|
+
const message = String(error?.message || error);
|
|
390
|
+
if (message.includes("outcome is unknown") || message.includes("partially dispatched")) {
|
|
391
|
+
throw applicationVisualInputOutcomeUnknown(error);
|
|
392
|
+
}
|
|
393
|
+
if (message.includes("process_generation_changed_before_dispatch")) throw new Error("application process generation changed before operation");
|
|
394
|
+
if (message.includes("before dispatch") || message.includes("unavailable")) throw new Error("application visual input unavailable before dispatch");
|
|
395
|
+
throw applicationVisualInputOutcomeUnknown(error);
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
application, process_name: processName, ok: result?.ok === true,
|
|
399
|
+
coordinate_source: "macos_skylight_experimental", window_bound: true, screenshot_revalidated: true,
|
|
400
|
+
input_transport: typeof result?.input_transport === "string" ? result.input_transport : "",
|
|
401
|
+
experimental_backend: true,
|
|
402
|
+
focus_without_raise: result?.focus_without_raise === true,
|
|
403
|
+
front_window_validated: result?.front_window_validated === true,
|
|
404
|
+
cursor_preserved: result?.cursor_preserved === true,
|
|
405
|
+
frontmost_restored: result?.frontmost_restored === true,
|
|
406
|
+
normalized_point: { x: normalizedX, y: normalizedY },
|
|
407
|
+
destination_normalized_point: { x: destinationNormalizedX, y: destinationNormalizedY },
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async scrollApplication(args = {}, context = {}) {
|
|
412
|
+
this.authorizeTool("computer_act");
|
|
413
|
+
assertMacAccessibility(this.platform);
|
|
414
|
+
const visualCapability = this.visualPointCapability();
|
|
415
|
+
if (!visualCapability.available || typeof this.backgroundInputService?.scroll !== "function") {
|
|
416
|
+
throw new Error("macOS background visual scroll input is disabled or unavailable");
|
|
417
|
+
}
|
|
418
|
+
const application = requiredApplication(args.application);
|
|
419
|
+
const processName = applicationProcessName(application);
|
|
420
|
+
const normalizedX = normalizedCoordinate(args.normalized_x, "normalized_x");
|
|
421
|
+
const normalizedY = normalizedCoordinate(args.normalized_y, "normalized_y");
|
|
422
|
+
const deltaX = applicationScrollDelta(args.delta_x, "delta_x");
|
|
423
|
+
const deltaY = applicationScrollDelta(args.delta_y, "delta_y");
|
|
424
|
+
if (deltaX === 0 && deltaY === 0) throw new Error("application visual scroll requires a non-zero delta_x or delta_y");
|
|
425
|
+
const expectedWindowId = positiveInteger(args.window_id, "window_id");
|
|
426
|
+
const expectedBounds = requiredWindowBounds(args.bounds);
|
|
427
|
+
const expectedScreenshotSha256 = requiredSha256(args.screenshot_sha256, "screenshot_sha256");
|
|
428
|
+
const expectedProcessId = optionalExpectedProcessId(args.expected_process_id);
|
|
429
|
+
const expectedProcessGeneration = optionalExpectedProcessGeneration(args.expected_process_generation);
|
|
430
|
+
const timeoutSeconds = clampInt(args.timeout_seconds, 30, 1, 120);
|
|
431
|
+
this.throwIfCancelled(context);
|
|
432
|
+
let captured;
|
|
433
|
+
try { captured = await this.captureMacWindow(processName, timeoutSeconds, context, expectedProcessId, expectedProcessGeneration); }
|
|
434
|
+
catch (error) {
|
|
435
|
+
const message = String(error?.message || error);
|
|
436
|
+
if (message.includes("application process changed before operation") || message.includes("application process generation changed before operation")) throw error;
|
|
437
|
+
throw new Error("application visual snapshot unavailable before dispatch");
|
|
438
|
+
}
|
|
439
|
+
const actualSha256 = createHash("sha256").update(captured.bytes).digest("hex");
|
|
440
|
+
if (captured.window.window_id !== expectedWindowId
|
|
441
|
+
|| !sameWindowBoundsNode(captured.window.bounds, expectedBounds)
|
|
442
|
+
|| actualSha256 !== expectedScreenshotSha256) {
|
|
443
|
+
throw new Error("application visual snapshot changed before dispatch");
|
|
444
|
+
}
|
|
445
|
+
this.throwIfCancelled(context);
|
|
446
|
+
const point = applicationWindowPoint(expectedBounds, normalizedX, normalizedY);
|
|
447
|
+
let result;
|
|
448
|
+
try {
|
|
449
|
+
result = await this.backgroundInputService.scroll({
|
|
450
|
+
pid: captured.window.process_id,
|
|
451
|
+
process_generation: captured.window.process_generation,
|
|
452
|
+
window_id: expectedWindowId,
|
|
453
|
+
screen_x: point.screen_x, screen_y: point.screen_y,
|
|
454
|
+
local_x: point.local_x, local_y: point.local_y,
|
|
455
|
+
delta_x: deltaX, delta_y: deltaY,
|
|
456
|
+
window_x: expectedBounds.x, window_y: expectedBounds.y,
|
|
457
|
+
window_width: expectedBounds.width, window_height: expectedBounds.height,
|
|
458
|
+
timeout_seconds: timeoutSeconds,
|
|
459
|
+
}, context);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (error instanceof BridgeError && error.code === "cancelled") throw error;
|
|
462
|
+
const message = String(error?.message || error);
|
|
463
|
+
if (message.includes("outcome is unknown") || message.includes("partially dispatched")) {
|
|
464
|
+
throw applicationVisualInputOutcomeUnknown(error);
|
|
465
|
+
}
|
|
466
|
+
if (message.includes("process_generation_changed_before_dispatch")) throw new Error("application process generation changed before operation");
|
|
467
|
+
if (message.includes("before dispatch") || message.includes("unavailable")) throw new Error("application visual input unavailable before dispatch");
|
|
468
|
+
throw applicationVisualInputOutcomeUnknown(error);
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
application, process_name: processName, ok: result?.ok === true,
|
|
472
|
+
coordinate_source: "macos_skylight_experimental", window_bound: true, screenshot_revalidated: true,
|
|
473
|
+
input_transport: typeof result?.input_transport === "string" ? result.input_transport : "",
|
|
474
|
+
experimental_backend: true,
|
|
475
|
+
focus_without_raise: result?.focus_without_raise === true,
|
|
476
|
+
front_window_validated: result?.front_window_validated === true,
|
|
477
|
+
cursor_preserved: result?.cursor_preserved === true,
|
|
478
|
+
frontmost_restored: result?.frontmost_restored === true,
|
|
479
|
+
normalized_point: { x: normalizedX, y: normalizedY },
|
|
480
|
+
scroll_delta: { delta_x: deltaX, delta_y: deltaY },
|
|
139
481
|
};
|
|
140
482
|
}
|
|
141
483
|
|
|
484
|
+
async captureMacWindow(processName, timeoutSeconds, context, expectedProcessId = null, expectedProcessGeneration = null) {
|
|
485
|
+
const window = await this.resolveMacWindow(processName, timeoutSeconds, context, expectedProcessId, expectedProcessGeneration);
|
|
486
|
+
const directory = await mkdtemp(join(tmpdir(), "mbm-app-shot-"));
|
|
487
|
+
const outputPath = join(directory, "window.png");
|
|
488
|
+
try {
|
|
489
|
+
await this.runProcess("/usr/sbin/screencapture", ["-x", "-o", "-l", String(window.window_id), outputPath], timeoutSeconds * 1000, false, 64 * 1024, context, undefined, null);
|
|
490
|
+
const handle = await open(outputPath, "r");
|
|
491
|
+
try {
|
|
492
|
+
const info = await handle.stat();
|
|
493
|
+
if (!info.isFile() || info.size < 8 || info.size > MAX_APPLICATION_SCREENSHOT_BYTES) throw new Error("application screenshot size is invalid");
|
|
494
|
+
const bytes = await handle.readFile();
|
|
495
|
+
if (!isPng(bytes)) throw new Error("application screenshot is not a PNG image");
|
|
496
|
+
return { window, bytes };
|
|
497
|
+
} finally {
|
|
498
|
+
await handle.close();
|
|
499
|
+
}
|
|
500
|
+
} finally {
|
|
501
|
+
await rm(directory, { recursive: true, force: true }).catch(() => { /* Screenshot staging cleanup is best-effort after settlement. */ });
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async resolveMacWindow(processName, timeoutSeconds, context, expectedProcessId = null, expectedProcessGeneration = null) {
|
|
506
|
+
const windowState = await this.runJxa({
|
|
507
|
+
operation: "window_candidates",
|
|
508
|
+
application: processName,
|
|
509
|
+
expectedProcessId,
|
|
510
|
+
expectedProcessGeneration,
|
|
511
|
+
includeProcessGeneration: true,
|
|
512
|
+
}, timeoutSeconds, context);
|
|
513
|
+
const window = selectMacWindow(windowState);
|
|
514
|
+
if (expectedProcessId && window.process_id !== expectedProcessId) throw new Error("application process changed before operation");
|
|
515
|
+
if (expectedProcessGeneration && window.process_generation !== expectedProcessGeneration) {
|
|
516
|
+
throw new Error("application process generation changed before operation");
|
|
517
|
+
}
|
|
518
|
+
if (!window.process_generation) throw new Error("application process generation is unavailable during window selection");
|
|
519
|
+
return window;
|
|
520
|
+
}
|
|
521
|
+
|
|
142
522
|
async operateApplication(args = {}, context = {}) {
|
|
143
523
|
this.authorizeTool("operate_local_application");
|
|
144
524
|
assertMacAccessibility(this.platform);
|
|
@@ -146,25 +526,38 @@ export class AppAutomationManager {
|
|
|
146
526
|
const processName = applicationProcessName(application);
|
|
147
527
|
const action = normalizeAppAction(args.action);
|
|
148
528
|
const selector = action === "activate" ? null : normalizeUiSelector(args.selector || {});
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
529
|
+
const expectedWindowBounds = args.expected_window_bounds === undefined ? null : requiredWindowBounds(args.expected_window_bounds);
|
|
530
|
+
const expectedElementBounds = args.expected_element_bounds === undefined ? null : requiredWindowBounds(args.expected_element_bounds);
|
|
531
|
+
const expectedProcessId = optionalExpectedProcessId(args.expected_process_id);
|
|
532
|
+
const expectedProcessGeneration = optionalExpectedProcessGeneration(args.expected_process_generation);
|
|
533
|
+
const timeoutSeconds = clampInt(args.timeout_seconds, 30, 1, 120);
|
|
534
|
+
const maxDepth = clampInt(args.max_depth, 8, 1, MAX_UI_DEPTH);
|
|
535
|
+
const includeMenus = optionalBoolean(args.include_menus, "include_menus", false);
|
|
536
|
+
const { key, value, keyboardAction } = await this.resolveApplicationActionInput(args, action);
|
|
157
537
|
const payload = {
|
|
158
538
|
operation: "act",
|
|
159
539
|
application: processName,
|
|
160
|
-
|
|
540
|
+
expectedProcessId,
|
|
541
|
+
expectedProcessGeneration,
|
|
542
|
+
action: keyboardAction ? "focus" : action,
|
|
161
543
|
selector,
|
|
162
|
-
value,
|
|
163
|
-
|
|
544
|
+
value: keyboardAction ? null : value,
|
|
545
|
+
key: null,
|
|
546
|
+
maxDepth,
|
|
164
547
|
maxElements: MAX_UI_ELEMENTS,
|
|
165
|
-
includeMenus
|
|
548
|
+
includeMenus,
|
|
549
|
+
includeProcessGeneration: true,
|
|
550
|
+
expectedWindowBounds,
|
|
551
|
+
expectedElementBounds,
|
|
166
552
|
};
|
|
167
|
-
const result = await this.runJxa(payload,
|
|
553
|
+
const result = await this.runJxa(payload, timeoutSeconds, context, { mutating: true });
|
|
554
|
+
if (keyboardAction) {
|
|
555
|
+
Object.assign(result, await this.dispatchPreparedApplicationKeyboard({
|
|
556
|
+
action, key, value, expectedProcessId, expectedProcessGeneration, result, timeoutSeconds,
|
|
557
|
+
}, context));
|
|
558
|
+
}
|
|
559
|
+
const { process_id: _processId, process_generation: _processGeneration, ...publicResult } = result || {};
|
|
560
|
+
const valueVerificationHandle = this.retainApplicationVerificationValue(args, action, value, result);
|
|
168
561
|
return {
|
|
169
562
|
application,
|
|
170
563
|
process_name: processName,
|
|
@@ -172,36 +565,256 @@ export class AppAutomationManager {
|
|
|
172
565
|
selector,
|
|
173
566
|
value_source: args.value_resource !== undefined ? "local-resource" : value === null ? "none" : "mcp-argument",
|
|
174
567
|
value_exposed: false,
|
|
175
|
-
...
|
|
568
|
+
...(valueVerificationHandle ? { _machine_value_verification_handle: valueVerificationHandle } : {}),
|
|
569
|
+
...publicResult,
|
|
176
570
|
};
|
|
177
571
|
}
|
|
178
572
|
|
|
179
|
-
async
|
|
573
|
+
async resolveApplicationActionInput(args, action) {
|
|
574
|
+
const textAction = action === "set_value" || action === "keystroke";
|
|
575
|
+
if (action === "key_press" && args.key === undefined) throw new Error("application key_press requires key");
|
|
576
|
+
if (action !== "key_press" && args.key !== undefined) throw new Error(`application ${action} does not accept key`);
|
|
577
|
+
const key = action === "key_press" ? normalizeApplicationKeyPress(args.key) : null;
|
|
578
|
+
if (args.value !== undefined && args.value_resource !== undefined) throw new Error("value and value_resource are mutually exclusive");
|
|
579
|
+
if (!textAction && (args.value !== undefined || args.value_resource !== undefined)) {
|
|
580
|
+
throw new Error(`application ${action} does not accept value or value_resource`);
|
|
581
|
+
}
|
|
582
|
+
if (textAction && args.value === undefined && args.value_resource === undefined) {
|
|
583
|
+
throw new Error(`application ${action} requires value or value_resource`);
|
|
584
|
+
}
|
|
585
|
+
let value = args.value === undefined ? null : applicationText(args.value, "application action value");
|
|
586
|
+
if (textAction && args.value_resource !== undefined) {
|
|
587
|
+
value = applicationText(await this.readResourceText(requiredResourceName(args.value_resource)), "application resource value");
|
|
588
|
+
}
|
|
589
|
+
if (action === "keystroke" && value === "") throw new Error("application keystroke requires non-empty text");
|
|
590
|
+
const keyboardAction = action === "keystroke" || action === "key_press";
|
|
591
|
+
if (keyboardAction && (!this.backgroundInputService
|
|
592
|
+
|| typeof this.backgroundInputService.keystroke !== "function"
|
|
593
|
+
|| typeof this.backgroundInputService.keyPress !== "function")) {
|
|
594
|
+
throw new Error("macOS PID keyboard input unavailable before dispatch");
|
|
595
|
+
}
|
|
596
|
+
return { key, value, keyboardAction };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async dispatchPreparedApplicationKeyboard(input, context) {
|
|
600
|
+
const resultProcessId = requiredPositiveInteger(input.result?.process_id, "application process pid");
|
|
601
|
+
const resultProcessGeneration = optionalProcessGeneration(input.result?.process_generation, "application process generation");
|
|
602
|
+
if (input.result?.element?.focused !== true) throw new Error("application keyboard target could not become focused before dispatch");
|
|
603
|
+
if (!input.expectedProcessGeneration && !resultProcessGeneration) {
|
|
604
|
+
throw new Error("application process generation is unavailable before keyboard dispatch");
|
|
605
|
+
}
|
|
606
|
+
this.throwIfCancelled(context);
|
|
607
|
+
try {
|
|
608
|
+
const keyboardResult = input.action === "keystroke"
|
|
609
|
+
? await this.backgroundInputService.keystroke({
|
|
610
|
+
pid: input.expectedProcessId || resultProcessId,
|
|
611
|
+
process_generation: input.expectedProcessGeneration || resultProcessGeneration,
|
|
612
|
+
text: input.value,
|
|
613
|
+
timeout_seconds: input.timeoutSeconds,
|
|
614
|
+
}, context)
|
|
615
|
+
: await this.backgroundInputService.keyPress({
|
|
616
|
+
pid: input.expectedProcessId || resultProcessId,
|
|
617
|
+
process_generation: input.expectedProcessGeneration || resultProcessGeneration,
|
|
618
|
+
key: input.key,
|
|
619
|
+
timeout_seconds: input.timeoutSeconds,
|
|
620
|
+
}, context);
|
|
621
|
+
return { input_transport: keyboardResult.input_transport, focus_prepared: true };
|
|
622
|
+
} catch (error) {
|
|
623
|
+
if (error instanceof BridgeError && error.code === "cancelled") throw error;
|
|
624
|
+
const message = String(error?.message || error);
|
|
625
|
+
if (message.includes("process generation changed before dispatch")) {
|
|
626
|
+
throw new Error("application process generation changed before operation");
|
|
627
|
+
}
|
|
628
|
+
if (message.includes("outcome is unknown") || message.includes("partially dispatched")) {
|
|
629
|
+
throw applicationKeyboardOutcomeUnknown(input.action, error);
|
|
630
|
+
}
|
|
631
|
+
throw error;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
retainApplicationVerificationValue(args, action, value, result) {
|
|
636
|
+
if (action !== "set_value" || args.value_resource === undefined || args.retain_value_verification !== true
|
|
637
|
+
|| value === null || result?.ok !== true || result?.element?.sensitive !== false) return "";
|
|
638
|
+
return this.retainApplicationValue(value);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
async verifyApplicationValue(args = {}, context = {}) {
|
|
642
|
+
this.authorizeTool("computer_act");
|
|
643
|
+
assertMacAccessibility(this.platform);
|
|
644
|
+
const application = requiredApplication(args.application);
|
|
645
|
+
const processName = applicationProcessName(application);
|
|
646
|
+
const selector = normalizeUiSelector(args.selector || {});
|
|
647
|
+
const expectedWindowBounds = args.expected_window_bounds === undefined ? null : requiredWindowBounds(args.expected_window_bounds);
|
|
648
|
+
const expectedElementBounds = args.expected_element_bounds === undefined ? null : requiredWindowBounds(args.expected_element_bounds);
|
|
649
|
+
const expectedProcessId = optionalExpectedProcessId(args.expected_process_id);
|
|
650
|
+
const expectedProcessGeneration = optionalExpectedProcessGeneration(args.expected_process_generation);
|
|
651
|
+
let value = args.value === undefined ? null : applicationText(args.value, "application verification value");
|
|
652
|
+
let valueSource = value === null ? "none" : "mcp-argument";
|
|
653
|
+
if (args.value_verification_handle !== undefined) {
|
|
654
|
+
if (value !== null || args.value_resource !== undefined) throw new Error("value_verification_handle is mutually exclusive with value and value_resource");
|
|
655
|
+
value = this.takeApplicationValue(args.value_verification_handle);
|
|
656
|
+
valueSource = "retained-action-value";
|
|
657
|
+
} else if (args.value_resource !== undefined) {
|
|
658
|
+
if (value !== null) throw new Error("value and value_resource are mutually exclusive");
|
|
659
|
+
value = applicationText(await this.readResourceText(requiredResourceName(args.value_resource)), "application resource value");
|
|
660
|
+
valueSource = "local-resource";
|
|
661
|
+
}
|
|
662
|
+
if (value === null) throw new Error("application value verification requires value or value_resource");
|
|
663
|
+
const includeMenus = optionalBoolean(args.include_menus, "include_menus", false);
|
|
664
|
+
const result = await this.runJxa({
|
|
665
|
+
operation: "verify_value",
|
|
666
|
+
application: processName,
|
|
667
|
+
expectedProcessId,
|
|
668
|
+
expectedProcessGeneration,
|
|
669
|
+
selector,
|
|
670
|
+
value,
|
|
671
|
+
maxDepth: clampInt(args.max_depth, 8, 1, MAX_UI_DEPTH),
|
|
672
|
+
maxElements: MAX_UI_ELEMENTS,
|
|
673
|
+
includeMenus,
|
|
674
|
+
expectedWindowBounds,
|
|
675
|
+
expectedElementBounds,
|
|
676
|
+
}, clampInt(args.timeout_seconds, 30, 1, 120), context);
|
|
677
|
+
return {
|
|
678
|
+
application,
|
|
679
|
+
process_name: processName,
|
|
680
|
+
selector,
|
|
681
|
+
value_source: valueSource,
|
|
682
|
+
value_exposed: false,
|
|
683
|
+
supported: result?.supported === true,
|
|
684
|
+
matched: result?.matched === true,
|
|
685
|
+
matched_count: Number.isSafeInteger(result?.matched_count) && result.matched_count >= 0 ? result.matched_count : 0,
|
|
686
|
+
selected_index: Number.isInteger(result?.selected_index) ? result.selected_index : null,
|
|
687
|
+
reason: typeof result?.reason === "string" ? result.reason : "",
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
retainApplicationValue(value) {
|
|
692
|
+
this.pruneApplicationValues();
|
|
693
|
+
while (this.valueVerificationStore.size >= MAX_VALUE_VERIFICATION_HANDLES) {
|
|
694
|
+
this.valueVerificationStore.delete(this.valueVerificationStore.keys().next().value);
|
|
695
|
+
}
|
|
696
|
+
let handle = `av_${randomBytes(18).toString("base64url")}`;
|
|
697
|
+
while (this.valueVerificationStore.has(handle)) handle = `av_${randomBytes(18).toString("base64url")}`;
|
|
698
|
+
const retainedValue = applicationText(value, "application retained verification value");
|
|
699
|
+
this.valueVerificationStore.set(handle, { value: retainedValue, expires_at: this.now() + VALUE_VERIFICATION_TTL_MS });
|
|
700
|
+
return handle;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
takeApplicationValue(rawHandle) {
|
|
704
|
+
this.pruneApplicationValues();
|
|
705
|
+
const handle = requiredApplicationValueVerificationHandle(rawHandle);
|
|
706
|
+
const record = this.valueVerificationStore.get(handle) || null;
|
|
707
|
+
this.valueVerificationStore.delete(handle);
|
|
708
|
+
if (!record) throw new Error("application value verification handle is missing or expired");
|
|
709
|
+
return record.value;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
discardApplicationValueVerification(rawHandle) {
|
|
713
|
+
if (typeof rawHandle !== "string" || !/^av_[A-Za-z0-9_-]{24,80}$/.test(rawHandle)) return false;
|
|
714
|
+
return this.valueVerificationStore.delete(rawHandle);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
pruneApplicationValues() {
|
|
718
|
+
const now = this.now();
|
|
719
|
+
for (const [handle, record] of this.valueVerificationStore) {
|
|
720
|
+
if (typeof record?.expires_at !== "number" || !Number.isFinite(record.expires_at) || record.expires_at <= now) {
|
|
721
|
+
this.valueVerificationStore.delete(handle);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
async runJxa(payload, timeoutSeconds, context, { mutating = false } = {}) {
|
|
180
727
|
const input = `${JSON.stringify(payload)}\n`;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
728
|
+
this.throwIfCancelled(context);
|
|
729
|
+
let result;
|
|
730
|
+
try {
|
|
731
|
+
result = await this.runProcess(
|
|
732
|
+
"osascript",
|
|
733
|
+
["-l", "JavaScript", "-e", MACOS_UI_JXA],
|
|
734
|
+
timeoutSeconds * 1000,
|
|
735
|
+
mutating,
|
|
736
|
+
1024 * 1024,
|
|
737
|
+
context,
|
|
738
|
+
undefined,
|
|
739
|
+
input,
|
|
740
|
+
mutating ? { nonReplayableMutation: true } : {},
|
|
741
|
+
);
|
|
742
|
+
} catch (error) {
|
|
743
|
+
if (mutating && error instanceof BridgeError && error.details?.reason === "process_outcome_unknown_after_spawn") {
|
|
744
|
+
throw applicationAccessibilityOutcomeUnknown(error);
|
|
745
|
+
}
|
|
746
|
+
if (error instanceof BridgeError && error.details?.reason === "process_failed_before_spawn") {
|
|
747
|
+
throw new BridgeError("unavailable", "application Accessibility helper unavailable before dispatch", {
|
|
748
|
+
retryable: true, details: { reason: "application_accessibility_helper_unavailable_before_dispatch" },
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
throw error;
|
|
752
|
+
}
|
|
753
|
+
if (mutating && result.code !== 0) throw applicationAccessibilityOutcomeUnknown();
|
|
191
754
|
const output = result.stdout.trim();
|
|
192
|
-
if (!output)
|
|
755
|
+
if (!output) {
|
|
756
|
+
if (mutating) throw applicationAccessibilityOutcomeUnknown(new Error("missing helper settlement"));
|
|
757
|
+
throw new Error("macOS accessibility helper returned no JSON output");
|
|
758
|
+
}
|
|
193
759
|
let parsed;
|
|
194
760
|
try {
|
|
195
761
|
parsed = JSON.parse(output);
|
|
196
|
-
} catch {
|
|
762
|
+
} catch (error) {
|
|
763
|
+
if (mutating) throw applicationAccessibilityOutcomeUnknown(error);
|
|
197
764
|
throw new Error("macOS accessibility helper returned invalid JSON");
|
|
198
765
|
}
|
|
199
|
-
if (parsed
|
|
766
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
767
|
+
if (mutating) throw applicationAccessibilityOutcomeUnknown();
|
|
768
|
+
throw new Error("macOS accessibility helper returned an invalid settlement object");
|
|
769
|
+
}
|
|
770
|
+
if (Object.hasOwn(parsed, "error")) {
|
|
771
|
+
if (typeof parsed.error !== "string" || !parsed.error || parsed.error.includes("\0") || parsed.error.length > 2000) {
|
|
772
|
+
if (mutating) throw applicationAccessibilityOutcomeUnknown();
|
|
773
|
+
throw new Error("macOS accessibility helper returned an invalid error settlement");
|
|
774
|
+
}
|
|
775
|
+
const message = parsed.error;
|
|
776
|
+
if (mutating && message === "application Accessibility mutation may have been partially dispatched; the action outcome is unknown. Inspect the application before retrying.") {
|
|
777
|
+
throw applicationAccessibilityOutcomeUnknown();
|
|
778
|
+
}
|
|
779
|
+
if (message === "macOS Automation permission to control System Events is required") {
|
|
780
|
+
throw new BridgeError("permission_denied", message, {
|
|
781
|
+
retryable: false, details: { reason: "macos_system_events_automation_permission_required" },
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
throw new Error(message);
|
|
785
|
+
}
|
|
786
|
+
if (mutating && parsed.ok !== true) throw applicationAccessibilityOutcomeUnknown();
|
|
200
787
|
return parsed;
|
|
201
788
|
}
|
|
202
789
|
|
|
203
790
|
}
|
|
204
791
|
|
|
792
|
+
function applicationLaunchOutcomeUnknown(cause = null) {
|
|
793
|
+
return exposedApplicationSettlement("application launch may have been partially dispatched; the outcome is unknown. Inspect application state before retrying.", cause);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function applicationAccessibilityOutcomeUnknown(cause = null) {
|
|
797
|
+
return exposedApplicationSettlement("application Accessibility mutation may have been partially dispatched; the action outcome is unknown. Inspect the application before retrying.", cause);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function applicationKeyboardOutcomeUnknown(action, cause = null) {
|
|
801
|
+
const label = action === "key_press" ? "key_press" : "keystroke";
|
|
802
|
+
return exposedApplicationSettlement(`application ${label} may have been partially dispatched; the action outcome is unknown. Inspect the application before retrying.`, cause);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function applicationVisualInputOutcomeUnknown(cause = null) {
|
|
806
|
+
return exposedApplicationSettlement("application visual input may have been partially dispatched; the action outcome is unknown. Inspect the application before retrying.", cause);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function exposedApplicationSettlement(message, cause = null) {
|
|
810
|
+
const terminationRequested = cause?.details?.termination_requested === true;
|
|
811
|
+
const sideEffectsStarted = cause?.details?.side_effects_started ?? "unknown";
|
|
812
|
+
return new BridgeError("execution_failed", message, {
|
|
813
|
+
expose: true, retryable: false, cause: cause instanceof Error ? cause : undefined,
|
|
814
|
+
details: { side_effects_started: sideEffectsStarted, termination_requested: terminationRequested, effect_settlement: terminationRequested ? "pending" : "unknown" },
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
205
818
|
async function listMacApplications(roots, context, throwIfCancelled) {
|
|
206
819
|
return scanApplicationRoots(roots, {
|
|
207
820
|
context,
|
|
@@ -237,38 +850,55 @@ async function listExecutableRoots(roots, extensions, context, throwIfCancelled)
|
|
|
237
850
|
|
|
238
851
|
async function scanApplicationRoots(roots, { context, throwIfCancelled, match, descend, makeItem }) {
|
|
239
852
|
const results = [];
|
|
853
|
+
const warnings = [];
|
|
240
854
|
const seenRoots = new Set();
|
|
241
855
|
let visited = 0;
|
|
242
856
|
for (const inputRoot of roots) {
|
|
243
|
-
|
|
244
|
-
|
|
857
|
+
let root;
|
|
858
|
+
try { root = await realpath(inputRoot); } catch (error) {
|
|
859
|
+
if (error?.code !== "ENOENT" && warnings.length < 50) {
|
|
860
|
+
warnings.push({ path: inputRoot, error_class: classifyOperationalError(error) });
|
|
861
|
+
}
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (seenRoots.has(root)) continue;
|
|
245
865
|
seenRoots.add(root);
|
|
246
866
|
const stack = [{ directory: root, depth: 0 }];
|
|
247
867
|
while (stack.length) {
|
|
248
868
|
const current = stack.pop();
|
|
249
|
-
|
|
250
|
-
|
|
869
|
+
let handle;
|
|
870
|
+
try { handle = await opendir(current.directory); } catch (error) {
|
|
871
|
+
if (error?.code !== "ENOENT" && warnings.length < 50) {
|
|
872
|
+
warnings.push({ path: current.directory, error_class: classifyOperationalError(error) });
|
|
873
|
+
}
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
251
876
|
for await (const entry of handle) {
|
|
252
877
|
throwIfCancelled(context);
|
|
253
878
|
visited += 1;
|
|
254
|
-
if (visited > MAX_APPLICATION_SCAN_ENTRIES)
|
|
879
|
+
if (visited > MAX_APPLICATION_SCAN_ENTRIES) {
|
|
880
|
+
return { applications: results, warnings, truncated: true, visitedEntries: visited };
|
|
881
|
+
}
|
|
255
882
|
const path = join(current.directory, entry.name);
|
|
256
883
|
if (match(entry)) {
|
|
257
884
|
results.push(makeItem(path, entry));
|
|
258
|
-
if (results.length >= MAX_APPLICATIONS)
|
|
885
|
+
if (results.length >= MAX_APPLICATIONS) {
|
|
886
|
+
return { applications: results, warnings, truncated: true, visitedEntries: visited };
|
|
887
|
+
}
|
|
259
888
|
} else if (current.depth < MAX_APPLICATION_SCAN_DEPTH && descend(entry)) {
|
|
260
889
|
stack.push({ directory: path, depth: current.depth + 1 });
|
|
261
890
|
}
|
|
262
891
|
}
|
|
263
892
|
}
|
|
264
893
|
}
|
|
265
|
-
return { applications: results, truncated: false, visitedEntries: visited };
|
|
894
|
+
return { applications: results, warnings, truncated: false, visitedEntries: visited };
|
|
266
895
|
}
|
|
267
896
|
|
|
268
897
|
function requiredApplication(value) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
898
|
+
if (typeof value !== "string" || !APP_NAME_PATTERN.test(value)) {
|
|
899
|
+
throw new Error("application must be a non-empty name or path without control characters");
|
|
900
|
+
}
|
|
901
|
+
return value;
|
|
272
902
|
}
|
|
273
903
|
|
|
274
904
|
function applicationProcessName(application) {
|
|
@@ -278,11 +908,22 @@ function applicationProcessName(application) {
|
|
|
278
908
|
}
|
|
279
909
|
|
|
280
910
|
function normalizeAppAction(value) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
911
|
+
if (typeof value !== "string" || !["activate", "click", "check", "uncheck", "set_value", "focus", "press", "keystroke", "key_press"].includes(value)) {
|
|
912
|
+
throw new Error("action must be one of activate, click, check, uncheck, set_value, focus, press, keystroke, or key_press");
|
|
913
|
+
}
|
|
914
|
+
return value;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function normalizeApplicationKeyPress(value) {
|
|
918
|
+
if (typeof value !== "string" || !value || value.includes("\0") || value.length > 100) {
|
|
919
|
+
throw new Error("application key_press requires a valid key");
|
|
920
|
+
}
|
|
921
|
+
const shifted = value.startsWith("Shift+");
|
|
922
|
+
const key = shifted ? value.slice("Shift+".length) : value;
|
|
923
|
+
if (!APPLICATION_KEY_PRESS_KEYS.has(key)) {
|
|
924
|
+
throw new Error("application key_press supports Enter, Tab, Escape, Backspace, Delete, ArrowLeft, ArrowUp, ArrowRight, ArrowDown, Home, End, PageUp, PageDown, Space, with optional Shift+");
|
|
284
925
|
}
|
|
285
|
-
return
|
|
926
|
+
return value;
|
|
286
927
|
}
|
|
287
928
|
|
|
288
929
|
function normalizeUiSelector(value) {
|
|
@@ -298,14 +939,165 @@ function normalizeUiSelector(value) {
|
|
|
298
939
|
return selector;
|
|
299
940
|
}
|
|
300
941
|
|
|
942
|
+
function selectMacWindow(value) {
|
|
943
|
+
const processId = requiredPositiveInteger(value?.process_id, "application process pid");
|
|
944
|
+
const processGeneration = requiredProcessGeneration(value?.process_generation, "application process generation");
|
|
945
|
+
const candidates = Array.isArray(value?.candidates) ? value.candidates.slice(0, 32) : [];
|
|
946
|
+
const normalized = candidates.map((candidate) => {
|
|
947
|
+
const windowId = candidate?.window_id;
|
|
948
|
+
if (!Number.isSafeInteger(windowId) || windowId < 1) return null;
|
|
949
|
+
let bounds;
|
|
950
|
+
try { bounds = requiredWindowBounds(candidate?.bounds); } catch { return null; }
|
|
951
|
+
return { window_id: windowId, bounds, title: typeof candidate?.title === "string" ? candidate.title.slice(0, 1000) : "" };
|
|
952
|
+
}).filter(Boolean);
|
|
953
|
+
if (!normalized.length) throw new Error("application has no capturable on-screen window");
|
|
954
|
+
let frontBounds = null;
|
|
955
|
+
try { frontBounds = requiredWindowBounds(value?.front_bounds); }
|
|
956
|
+
catch { /* Invalid optional front-window bounds fall through to the explicit ambiguity checks below. */ }
|
|
957
|
+
if (!frontBounds) {
|
|
958
|
+
if (normalized.length === 1) return { ...normalized[0], process_id: processId, process_generation: processGeneration };
|
|
959
|
+
throw new Error("application front window is ambiguous without Accessibility bounds");
|
|
960
|
+
}
|
|
961
|
+
const scored = normalized.map((candidate) => ({
|
|
962
|
+
candidate,
|
|
963
|
+
distance: windowBoundsDistanceNode(candidate.bounds, frontBounds),
|
|
964
|
+
}));
|
|
965
|
+
const bestDistance = Math.min(...scored.map((item) => item.distance));
|
|
966
|
+
const best = scored.filter((item) => Math.abs(item.distance - bestDistance) <= 0.001).map((item) => item.candidate);
|
|
967
|
+
if (best.length === 1) return { ...best[0], process_id: processId, process_generation: processGeneration };
|
|
968
|
+
const frontTitle = typeof value?.front_title === "string" ? value.front_title.trim().slice(0, 1000) : "";
|
|
969
|
+
if (frontTitle) {
|
|
970
|
+
const titleMatches = best.filter((candidate) => candidate.title.trim() === frontTitle);
|
|
971
|
+
if (titleMatches.length === 1) return { ...titleMatches[0], process_id: processId, process_generation: processGeneration };
|
|
972
|
+
}
|
|
973
|
+
throw new Error("application front window is ambiguous among matching on-screen windows");
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function safeSelectedMacWindow(value) {
|
|
977
|
+
try {
|
|
978
|
+
const window = selectMacWindow(value);
|
|
979
|
+
return {
|
|
980
|
+
id: window.window_id,
|
|
981
|
+
bounds: { ...window.bounds },
|
|
982
|
+
process_id: window.process_id,
|
|
983
|
+
process_generation: window.process_generation,
|
|
984
|
+
};
|
|
985
|
+
} catch {
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function windowBoundsDistanceNode(left, right) {
|
|
991
|
+
return Math.abs(left.x - right.x) + Math.abs(left.y - right.y)
|
|
992
|
+
+ Math.abs(left.width - right.width) + Math.abs(left.height - right.height);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function normalizeBackgroundVisualBackend(value) {
|
|
996
|
+
if (value !== undefined && typeof value !== "string") throw new Error("backgroundVisualBackend must be disabled or skylight-experimental");
|
|
997
|
+
const backend = (value || "disabled").trim().toLowerCase();
|
|
998
|
+
if (!["disabled", "skylight-experimental"].includes(backend)) {
|
|
999
|
+
throw new Error("backgroundVisualBackend must be disabled or skylight-experimental");
|
|
1000
|
+
}
|
|
1001
|
+
return backend;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function normalizedCoordinate(value, label) {
|
|
1005
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value >= 1) throw new Error(`${label} must be from 0 (inclusive) to 1 (exclusive)`);
|
|
1006
|
+
return value;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function applicationWindowPoint(bounds, normalizedX, normalizedY) {
|
|
1010
|
+
return {
|
|
1011
|
+
screen_x: bounds.x + normalizedX * bounds.width,
|
|
1012
|
+
screen_y: bounds.y + normalizedY * bounds.height,
|
|
1013
|
+
local_x: normalizedX * bounds.width,
|
|
1014
|
+
local_y: normalizedY * bounds.height,
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function applicationScrollDelta(value, label) {
|
|
1019
|
+
if (value === undefined) return 0;
|
|
1020
|
+
if (typeof value !== "number" || !Number.isFinite(value) || Math.abs(value) > 10_000) throw new Error(`${label} must be from -10000 to 10000`);
|
|
1021
|
+
return Object.is(value, -0) ? 0 : Math.round(value);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function requiredPositiveInteger(value, label) {
|
|
1025
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new Error(`${label} must be a positive integer`);
|
|
1026
|
+
return value;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function positiveInteger(value, label) {
|
|
1030
|
+
return requiredPositiveInteger(value, label);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function optionalExpectedProcessId(value) {
|
|
1034
|
+
if (value === undefined) return null;
|
|
1035
|
+
return requiredPositiveInteger(value, "expected_process_id");
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function requiredProcessGeneration(value, label) {
|
|
1039
|
+
if (typeof value !== "string" || !value || value.length > 2048 || /[\r\n\0]/.test(value)) throw new Error(`${label} is invalid`);
|
|
1040
|
+
return value;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function optionalProcessGeneration(value, label) {
|
|
1044
|
+
if (value === undefined || value === null) return "";
|
|
1045
|
+
return requiredProcessGeneration(value, label);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function optionalExpectedProcessGeneration(value) {
|
|
1049
|
+
if (value === undefined) return null;
|
|
1050
|
+
return requiredProcessGeneration(value, "expected_process_generation");
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function requiredWindowBounds(value) {
|
|
1054
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("bounds must be an object");
|
|
1055
|
+
const bounds = {};
|
|
1056
|
+
for (const key of ["x", "y", "width", "height"]) {
|
|
1057
|
+
const number = value[key];
|
|
1058
|
+
if (typeof number !== "number" || !Number.isFinite(number)) throw new Error(`bounds.${key} must be finite`);
|
|
1059
|
+
bounds[key] = number;
|
|
1060
|
+
}
|
|
1061
|
+
if (!(bounds.width > 0) || !(bounds.height > 0)) throw new Error("bounds width and height must be positive");
|
|
1062
|
+
return bounds;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function requiredSha256(value, label) {
|
|
1066
|
+
if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) throw new Error(`${label} must be a SHA-256 hex digest`);
|
|
1067
|
+
return value;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function sameWindowBoundsNode(left, right) {
|
|
1071
|
+
if (!left || !right) return false;
|
|
1072
|
+
return ["x", "y", "width", "height"].every((key) => {
|
|
1073
|
+
const a = left[key];
|
|
1074
|
+
const b = right[key];
|
|
1075
|
+
return typeof a === "number" && Number.isFinite(a) && typeof b === "number" && Number.isFinite(b) && Math.abs(a - b) <= 1;
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function applicationText(value, label) {
|
|
1080
|
+
if (typeof value !== "string") throw new Error(`${label} must be a string`);
|
|
1081
|
+
if (value.includes("\0") || value.length > MAX_TEXT_CHARS) {
|
|
1082
|
+
throw new Error(`${label} exceeds ${MAX_TEXT_CHARS} characters or contains a NUL byte`);
|
|
1083
|
+
}
|
|
1084
|
+
return value;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
301
1087
|
function requiredResourceName(value) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
1088
|
+
if (typeof value !== "string" || !/^[a-z][a-z0-9._-]{0,63}$/.test(value)) throw new Error("value_resource is invalid");
|
|
1089
|
+
return value;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function requiredApplicationValueVerificationHandle(value) {
|
|
1093
|
+
if (typeof value !== "string" || !/^av_[A-Za-z0-9_-]{24,80}$/.test(value)) {
|
|
1094
|
+
throw new Error("application value verification handle is invalid");
|
|
1095
|
+
}
|
|
1096
|
+
return value;
|
|
305
1097
|
}
|
|
306
1098
|
|
|
307
1099
|
function optionalString(value, label, maxLength) {
|
|
308
|
-
if (value === undefined
|
|
1100
|
+
if (value === undefined) return "";
|
|
309
1101
|
if (typeof value !== "string" || value.includes("\0") || value.length > maxLength) throw new Error(`${label} must be a string of at most ${maxLength} characters without NUL bytes`);
|
|
310
1102
|
return value;
|
|
311
1103
|
}
|
|
@@ -314,106 +1106,24 @@ function assertMacAccessibility(platform) {
|
|
|
314
1106
|
if (platform !== "darwin") throw new Error("structured application UI automation currently requires macOS");
|
|
315
1107
|
}
|
|
316
1108
|
|
|
1109
|
+
function optionalBoolean(value, label, fallback) {
|
|
1110
|
+
if (value === undefined) return fallback;
|
|
1111
|
+
if (typeof value !== "boolean") throw new Error(`${label} must be boolean`);
|
|
1112
|
+
return value;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
317
1115
|
function clampInt(value, fallback, min, max) {
|
|
318
|
-
if (value === undefined
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function scalar(value) {
|
|
333
|
-
if (value === null || value === undefined) return null;
|
|
334
|
-
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return value;
|
|
335
|
-
return String(value).slice(0, 1000);
|
|
336
|
-
}
|
|
337
|
-
function describe(element, index, includeValues) {
|
|
338
|
-
const item = {
|
|
339
|
-
index,
|
|
340
|
-
role: safe(() => element.role(), ''),
|
|
341
|
-
subrole: safe(() => element.subrole(), ''),
|
|
342
|
-
name: safe(() => element.name(), ''),
|
|
343
|
-
title: safe(() => element.title(), ''),
|
|
344
|
-
description: safe(() => element.description(), ''),
|
|
345
|
-
identifier: safe(() => element.attributes.byName('AXIdentifier').value(), ''),
|
|
346
|
-
enabled: safe(() => element.enabled(), null),
|
|
347
|
-
focused: safe(() => element.focused(), null)
|
|
348
|
-
};
|
|
349
|
-
const identity = [item.role, item.subrole, item.name, item.title, item.description, item.identifier].join(' ').toLowerCase();
|
|
350
|
-
item.sensitive = item.role === 'AXSecureTextField' || /(?:password|passwd|secret|token|api[-_ ]?key|otp|one[-_ ]?time|verification|cvc|cvv|security[-_ ]?code|card[-_ ]?number)/.test(identity);
|
|
351
|
-
if (includeValues && !item.sensitive) item.value = scalar(safe(() => element.value(), null));
|
|
352
|
-
return item;
|
|
353
|
-
}
|
|
354
|
-
function childrenOf(element) { return safe(() => element.uiElements(), []); }
|
|
355
|
-
function flatten(root, maxDepth, maxElements, includeValues, includeMenus) {
|
|
356
|
-
const output = [];
|
|
357
|
-
const elements = [];
|
|
358
|
-
const stack = [{ element: root, depth: 0 }];
|
|
359
|
-
while (stack.length && output.length < maxElements) {
|
|
360
|
-
const current = stack.pop();
|
|
361
|
-
const children = childrenOf(current.element);
|
|
362
|
-
for (let i = children.length - 1; i >= 0; i--) {
|
|
363
|
-
const child = children[i];
|
|
364
|
-
const item = describe(child, output.length, includeValues);
|
|
365
|
-
elements.push(child);
|
|
366
|
-
output.push(item);
|
|
367
|
-
if (output.length >= maxElements) break;
|
|
368
|
-
const isMenuTree = String(item.role || '').startsWith('AXMenu');
|
|
369
|
-
if (current.depth + 1 < maxDepth && (includeMenus || !isMenuTree)) stack.push({ element: child, depth: current.depth + 1 });
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
return { output, elements, truncated: stack.length > 0 || output.length >= maxElements };
|
|
373
|
-
}
|
|
374
|
-
function matches(item, selector) {
|
|
375
|
-
for (const key of ['role','subrole','name','title','description','identifier']) {
|
|
376
|
-
if (selector[key] !== undefined && String(item[key] || '').toLowerCase() !== String(selector[key]).toLowerCase()) return false;
|
|
377
|
-
}
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
function main() {
|
|
381
|
-
const payload = readPayload();
|
|
382
|
-
const se = Application('System Events');
|
|
383
|
-
const process = se.applicationProcesses.byName(payload.application);
|
|
384
|
-
if (!safe(() => process.exists(), false)) throw new Error('application process not found or Accessibility access denied');
|
|
385
|
-
if (payload.operation === 'inspect') {
|
|
386
|
-
const flattened = flatten(process, payload.maxDepth, payload.maxElements, payload.includeValues === true, payload.includeMenus === true);
|
|
387
|
-
return { frontmost: safe(() => process.frontmost(), false), elements: flattened.output, truncated: flattened.truncated, menus_included: payload.includeMenus === true };
|
|
388
|
-
}
|
|
389
|
-
if (payload.action === 'activate') {
|
|
390
|
-
process.frontmost = true;
|
|
391
|
-
return { ok: true, matched: 1 };
|
|
392
|
-
}
|
|
393
|
-
const flattened = flatten(process, payload.maxDepth, payload.maxElements, true, payload.includeMenus === true);
|
|
394
|
-
const matchesList = [];
|
|
395
|
-
for (let i = 0; i < flattened.output.length; i++) if (matches(flattened.output[i], payload.selector)) matchesList.push(i);
|
|
396
|
-
const chosen = payload.selector.index !== undefined ? matchesList[payload.selector.index] : matchesList[0];
|
|
397
|
-
if (chosen === undefined || chosen < 0 || chosen >= flattened.elements.length) throw new Error('no UI element matched selector');
|
|
398
|
-
const element = flattened.elements[chosen];
|
|
399
|
-
if (payload.action === 'click' || payload.action === 'press') {
|
|
400
|
-
const action = safe(() => element.actions.byName('AXPress'), null);
|
|
401
|
-
if (action && safe(() => action.exists(), false)) action.perform();
|
|
402
|
-
else element.click();
|
|
403
|
-
} else if (payload.action === 'set_value') {
|
|
404
|
-
if (payload.value === null) throw new Error('set_value requires value or value_resource');
|
|
405
|
-
element.value = payload.value;
|
|
406
|
-
} else if (payload.action === 'focus') {
|
|
407
|
-
element.focused = true;
|
|
408
|
-
} else if (payload.action === 'keystroke') {
|
|
409
|
-
process.frontmost = true;
|
|
410
|
-
element.focused = true;
|
|
411
|
-
if (payload.value === null) throw new Error('keystroke requires value or value_resource');
|
|
412
|
-
se.keystroke(payload.value);
|
|
413
|
-
} else {
|
|
414
|
-
throw new Error('unsupported action');
|
|
415
|
-
}
|
|
416
|
-
return { ok: true, matched: matchesList.length, selected_index: chosen, element: describe(element, chosen, false) };
|
|
417
|
-
}
|
|
418
|
-
(() => { try { return JSON.stringify(main()); } catch (error) { return JSON.stringify({ error: String(error.message || error) }); } })()
|
|
419
|
-
`;
|
|
1116
|
+
if (value === undefined) return fallback;
|
|
1117
|
+
if (!Number.isSafeInteger(value) || value < min || value > max) throw new Error(`expected an integer from ${min} to ${max}`);
|
|
1118
|
+
return value;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function isPng(value) {
|
|
1122
|
+
return Buffer.isBuffer(value) && value.length >= 8
|
|
1123
|
+
&& value[0] === 0x89 && value[1] === 0x50 && value[2] === 0x4e && value[3] === 0x47
|
|
1124
|
+
&& value[4] === 0x0d && value[5] === 0x0a && value[6] === 0x1a && value[7] === 0x0a;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
export function powershellSingleQuotedLiteral(value) {
|
|
1128
|
+
return `'${String(value).replaceAll("'", "''")}'`;
|
|
1129
|
+
}
|