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.
Files changed (473) hide show
  1. package/CHANGELOG.md +1142 -0
  2. package/CONTRIBUTING.md +23 -25
  3. package/GOVERNANCE.md +2 -2
  4. package/README.md +58 -20
  5. package/SECURITY.md +161 -120
  6. package/SUPPORT.md +1 -1
  7. package/browser-extension/broker-auth.js +83 -0
  8. package/browser-extension/broker-liveness.js +35 -0
  9. package/browser-extension/browser-error-boundary.js +96 -0
  10. package/browser-extension/browser-operations.js +1456 -101
  11. package/browser-extension/devtools-input.js +195 -34
  12. package/browser-extension/devtools-observation.js +429 -0
  13. package/browser-extension/devtools-session.js +48 -0
  14. package/browser-extension/manifest.json +4 -3
  15. package/browser-extension/page-automation.js +578 -118
  16. package/browser-extension/pairing-bootstrap.js +42 -0
  17. package/browser-extension/pairing.js +28 -12
  18. package/browser-extension/service-worker.js +126 -106
  19. package/docs/AGENT_CONTEXT.md +13 -10
  20. package/docs/ARCHITECTURE.md +159 -67
  21. package/docs/AUDIT.md +1302 -4
  22. package/docs/CLIENTS.md +19 -6
  23. package/docs/COMPUTER_USE.md +297 -0
  24. package/docs/ENGINEERING.md +72 -13
  25. package/docs/GETTING_STARTED.md +18 -20
  26. package/docs/LOCAL_AUTHORIZATION.md +115 -72
  27. package/docs/LOCAL_AUTOMATION.md +27 -21
  28. package/docs/LOGGING.md +40 -19
  29. package/docs/MANAGED_JOBS.md +37 -34
  30. package/docs/MULTI_ACCOUNT.md +98 -37
  31. package/docs/OPERATIONS.md +145 -51
  32. package/docs/OVERVIEW.md +14 -11
  33. package/docs/POLICY_REFERENCE.md +71 -3
  34. package/docs/PRIVACY.md +41 -2
  35. package/docs/PROJECT_STANDARDS.md +29 -6
  36. package/docs/RELEASING.md +238 -64
  37. package/docs/TESTING.md +150 -39
  38. package/docs/THREAT_MODEL.md +164 -67
  39. package/docs/TOOL_REFERENCE.md +653 -60
  40. package/docs/UPGRADING.md +78 -18
  41. package/native/macos/MachineBridgeBackgroundInput.swift +1051 -0
  42. package/native/macos/MachineBridgeBackgroundInputSmokeFixture.swift +295 -0
  43. package/native/macos/MachineBridgeTrustBroker.swift +233 -0
  44. package/package.json +94 -16
  45. package/scripts/accepted-candidate-tarball.mjs +77 -0
  46. package/scripts/candidate-runtime-store.mjs +93 -0
  47. package/scripts/check-plan.mjs +73 -3
  48. package/scripts/check-runner.mjs +129 -17
  49. package/scripts/commit-message-check.mjs +5 -2
  50. package/scripts/consumer-package-security.mjs +293 -0
  51. package/scripts/coverage-check.mjs +251 -15
  52. package/scripts/coverage-generation.mjs +41 -0
  53. package/scripts/coverage-range-merge.mjs +41 -0
  54. package/scripts/foreground-daemon-recovery.mjs +88 -0
  55. package/scripts/generate-policy-reference.mjs +28 -5
  56. package/scripts/generate-tool-reference.mjs +1 -1
  57. package/scripts/generate-worker-types.mjs +21 -11
  58. package/scripts/github-backlog.mjs +11 -10
  59. package/scripts/github-push.mjs +62 -16
  60. package/scripts/github-release-asset.mjs +58 -0
  61. package/scripts/github-release.mjs +241 -132
  62. package/scripts/global-package-installation.mjs +68 -0
  63. package/scripts/hardened-npm-session.mjs +42 -0
  64. package/scripts/install-published-prerelease.mjs +198 -0
  65. package/scripts/local-release-acceptance.mjs +103 -45
  66. package/scripts/macos-background-input-smoke.mjs +515 -0
  67. package/scripts/network-retry.mjs +8 -1
  68. package/scripts/npm-global-prefix.mjs +27 -0
  69. package/scripts/npm-publication-policy.mjs +17 -0
  70. package/scripts/official-mcp-conformance.mjs +267 -0
  71. package/scripts/persistent-activation-process.mjs +101 -0
  72. package/scripts/prepare-pinned-npm.mjs +12 -69
  73. package/scripts/prerelease-activation.mjs +160 -0
  74. package/scripts/privacy-check.mjs +14 -6
  75. package/scripts/promotion-digest.mjs +128 -0
  76. package/scripts/publish-npm.mjs +260 -0
  77. package/scripts/published-release.mjs +132 -0
  78. package/scripts/release-acceptance.mjs +62 -54
  79. package/scripts/release-browser-extension-store.mjs +172 -0
  80. package/scripts/release-candidate-manifest.mjs +52 -0
  81. package/scripts/release-channel.mjs +106 -0
  82. package/scripts/release-diagnostic.mjs +41 -0
  83. package/scripts/release-impact-check.mjs +12 -22
  84. package/scripts/release-oauth-canary-core.mjs +325 -0
  85. package/scripts/release-oauth-canary-evidence.mjs +100 -0
  86. package/scripts/release-oauth-canary.mjs +127 -0
  87. package/scripts/release-publication-guard.mjs +67 -0
  88. package/scripts/release-soak.mjs +284 -0
  89. package/scripts/release-state.mjs +1 -1
  90. package/scripts/run-checks.mjs +44 -7
  91. package/scripts/run-worker-dry-run.mjs +21 -0
  92. package/scripts/sarif-security-gate.mjs +2 -1
  93. package/scripts/sbom-check.mjs +106 -0
  94. package/scripts/start-release-candidate.mjs +267 -44
  95. package/scripts/syntax-check.mjs +36 -14
  96. package/scripts/verification-generation-guard.mjs +15 -0
  97. package/scripts/verification-idle-sleep-guard.mjs +37 -0
  98. package/scripts/verification-state.mjs +108 -0
  99. package/scripts/wrangler-command-lifecycle.mjs +115 -0
  100. package/src/local/account-access.mjs +13 -1
  101. package/src/local/account-admin-response.mjs +92 -0
  102. package/src/local/account-admin.mjs +52 -19
  103. package/src/local/agent-context-projection.mjs +26 -7
  104. package/src/local/agent-context.mjs +88 -49
  105. package/src/local/agent-skill-discovery.mjs +16 -5
  106. package/src/local/app-automation-macos-jxa.mjs +350 -0
  107. package/src/local/app-automation.mjs +873 -163
  108. package/src/local/application-capability-projection.mjs +24 -0
  109. package/src/local/authority-context.mjs +106 -0
  110. package/src/local/autostart-log-maintenance.mjs +36 -0
  111. package/src/local/browser-bridge-http.mjs +5 -3
  112. package/src/local/browser-bridge.mjs +109 -31
  113. package/src/local/browser-broker-auth-http.mjs +67 -0
  114. package/src/local/browser-broker-auth.mjs +98 -0
  115. package/src/local/browser-broker-routes.mjs +63 -20
  116. package/src/local/browser-broker-server.mjs +17 -7
  117. package/src/local/browser-command.mjs +79 -30
  118. package/src/local/browser-computer-observation-service.mjs +266 -0
  119. package/src/local/browser-extension-identity.mjs +50 -0
  120. package/src/local/browser-extension-path.mjs +22 -0
  121. package/src/local/browser-extension-protocol.mjs +50 -11
  122. package/src/local/browser-operation-service.mjs +147 -117
  123. package/src/local/browser-pairing-grant.mjs +109 -0
  124. package/src/local/browser-pairing-http.mjs +36 -0
  125. package/src/local/browser-pairing-launch.mjs +58 -0
  126. package/src/local/browser-pairing-store.mjs +85 -48
  127. package/src/local/browser-request-registry.mjs +40 -14
  128. package/src/local/browser-request-settlement.mjs +78 -0
  129. package/src/local/browser-resource-input.mjs +74 -0
  130. package/src/local/browser-trusted-input-health.mjs +63 -0
  131. package/src/local/call-authority.mjs +21 -0
  132. package/src/local/call-capacity.mjs +45 -0
  133. package/src/local/call-registry-drain.mjs +54 -0
  134. package/src/local/call-registry.mjs +30 -15
  135. package/src/local/capability-observer.mjs +5 -0
  136. package/src/local/capability-ranking.mjs +3 -2
  137. package/src/local/child-process-settlement.mjs +132 -0
  138. package/src/local/cli-account-admin.mjs +40 -6
  139. package/src/local/cli-activate.mjs +162 -0
  140. package/src/local/cli-local-admin.mjs +20 -45
  141. package/src/local/cli-options.mjs +12 -13
  142. package/src/local/cli-ready-output.mjs +52 -0
  143. package/src/local/cli-service.mjs +91 -11
  144. package/src/local/cli.mjs +232 -150
  145. package/src/local/computer-use-application-observation.mjs +224 -0
  146. package/src/local/computer-use-arguments.mjs +270 -0
  147. package/src/local/computer-use-deadline.mjs +50 -0
  148. package/src/local/computer-use-dispatch-settlement.mjs +40 -0
  149. package/src/local/computer-use-expectation.mjs +171 -0
  150. package/src/local/computer-use-observation.mjs +769 -0
  151. package/src/local/computer-use-recovery.mjs +82 -0
  152. package/src/local/computer-use-result-budget.mjs +145 -0
  153. package/src/local/computer-use-snapshot-store.mjs +67 -0
  154. package/src/local/computer-use.mjs +2255 -0
  155. package/src/local/daemon-http-relay-auth.mjs +26 -0
  156. package/src/local/daemon-http-relay-connection.mjs +298 -0
  157. package/src/local/daemon-http-relay-request.mjs +64 -0
  158. package/src/local/daemon-http-relay-sequence.mjs +49 -0
  159. package/src/local/daemon-process.mjs +40 -7
  160. package/src/local/delegated-process-sandbox.mjs +148 -0
  161. package/src/local/device-identity.mjs +156 -49
  162. package/src/local/device-root-provider.mjs +81 -0
  163. package/src/local/directory-metadata.mjs +47 -0
  164. package/src/local/doctor-reporting.mjs +23 -0
  165. package/src/local/durable-process-spec.mjs +80 -0
  166. package/src/local/errors.mjs +25 -15
  167. package/src/local/exclusive-file.mjs +74 -26
  168. package/src/local/execution-limits.mjs +9 -0
  169. package/src/local/execution-routing.mjs +276 -0
  170. package/src/local/execution-surface.mjs +23 -0
  171. package/src/local/file-mutation-coordinator.mjs +47 -0
  172. package/src/local/file-snapshot-preservation.mjs +24 -0
  173. package/src/local/filesystem-identity.mjs +54 -0
  174. package/src/local/fixed-process-environment.mjs +21 -0
  175. package/src/local/full-access-test.mjs +31 -13
  176. package/src/local/git-commit.mjs +69 -0
  177. package/src/local/git-config-safety.mjs +55 -0
  178. package/src/local/git-log-parser.mjs +41 -0
  179. package/src/local/git-metadata-boundary.mjs +77 -0
  180. package/src/local/git-metadata-tree-safety.mjs +59 -0
  181. package/src/local/git-operation-state.mjs +19 -0
  182. package/src/local/git-service.mjs +165 -30
  183. package/src/local/hardened-npm-download-timeout.mjs +46 -0
  184. package/src/local/hardened-npm-download.mjs +109 -0
  185. package/src/local/hardened-npm-extract.mjs +20 -0
  186. package/src/local/hardened-npm-verification.mjs +104 -0
  187. package/src/local/hardened-npm.mjs +199 -0
  188. package/src/local/job-runner.mjs +244 -89
  189. package/src/local/lifecycle.mjs +6 -0
  190. package/src/local/log.mjs +19 -45
  191. package/src/local/macos-background-input.mjs +398 -0
  192. package/src/local/macos-idle-sleep-assertion.mjs +65 -0
  193. package/src/local/macos-trust-broker.mjs +404 -0
  194. package/src/local/managed-job-cancellation.mjs +42 -0
  195. package/src/local/managed-job-capacity.mjs +16 -0
  196. package/src/local/managed-job-directory-generation.mjs +60 -0
  197. package/src/local/managed-job-directory.mjs +75 -0
  198. package/src/local/managed-job-durable-process.mjs +49 -0
  199. package/src/local/managed-job-lock.mjs +46 -14
  200. package/src/local/managed-job-plan.mjs +38 -14
  201. package/src/local/managed-job-projection.mjs +23 -15
  202. package/src/local/managed-job-retention.mjs +128 -0
  203. package/src/local/managed-job-runner-claim.mjs +70 -0
  204. package/src/local/managed-job-runner.mjs +35 -10
  205. package/src/local/managed-job-storage.mjs +22 -6
  206. package/src/local/managed-job-terminal-maintenance.mjs +40 -0
  207. package/src/local/managed-job-terminal.mjs +150 -0
  208. package/src/local/managed-jobs.mjs +424 -287
  209. package/src/local/npm-cli.mjs +42 -0
  210. package/src/local/npm-environment.mjs +37 -0
  211. package/src/local/operation-authorization.mjs +68 -324
  212. package/src/local/operation-risk.mjs +51 -4
  213. package/src/local/owner-state-lock.mjs +118 -0
  214. package/src/local/package-identity.mjs +9 -0
  215. package/src/local/patch.mjs +20 -18
  216. package/src/local/path-inspection.mjs +23 -0
  217. package/src/local/policy.mjs +19 -0
  218. package/src/local/private-toolchain-integrity.mjs +54 -0
  219. package/src/local/process-execution.mjs +87 -75
  220. package/src/local/process-foreground-timeout.mjs +50 -0
  221. package/src/local/process-identity.mjs +41 -7
  222. package/src/local/process-nonreplayable-settlement.mjs +50 -0
  223. package/src/local/process-result-projection.mjs +22 -0
  224. package/src/local/process-session-events.mjs +47 -0
  225. package/src/local/process-session-remote-activity.mjs +10 -0
  226. package/src/local/process-session-termination.mjs +73 -0
  227. package/src/local/process-sessions.mjs +144 -112
  228. package/src/local/process-tracker.mjs +140 -13
  229. package/src/local/process-tree-ownership-types.d.ts +37 -0
  230. package/src/local/process-tree-ownership.mjs +57 -0
  231. package/src/local/process-tree-signal.mjs +34 -0
  232. package/src/local/process-tree-snapshot.mjs +77 -0
  233. package/src/local/process-tree-supervisor.mjs +69 -0
  234. package/src/local/process-tree.mjs +11 -53
  235. package/src/local/project-metadata.mjs +19 -6
  236. package/src/local/relay-call-recovery.mjs +58 -38
  237. package/src/local/relay-connection-classification.mjs +131 -0
  238. package/src/local/relay-connection-support.mjs +91 -0
  239. package/src/local/relay-connection.mjs +179 -272
  240. package/src/local/relay-diagnostics.mjs +91 -0
  241. package/src/local/relay-heartbeat.mjs +119 -0
  242. package/src/local/relay-liveness.mjs +109 -0
  243. package/src/local/relay-peer-diagnostics.mjs +29 -0
  244. package/src/local/release-runtime-lock.mjs +18 -0
  245. package/src/local/remote-activity-idle-sleep-guard.mjs +77 -0
  246. package/src/local/remote-configuration.mjs +48 -0
  247. package/src/local/resilient-relay-connection.mjs +178 -0
  248. package/src/local/resource-admission-diagnostics.mjs +52 -0
  249. package/src/local/resource-admission-policy.mjs +115 -0
  250. package/src/local/resource-admission.mjs +339 -0
  251. package/src/local/resource-build-root.mjs +83 -0
  252. package/src/local/resource-cargo-concurrency.mjs +38 -0
  253. package/src/local/resource-cmake-concurrency.mjs +42 -0
  254. package/src/local/resource-command-concurrency.mjs +37 -0
  255. package/src/local/resource-command-profile.mjs +176 -0
  256. package/src/local/resource-coordinator-accounting.mjs +21 -0
  257. package/src/local/resource-cpu-window.mjs +20 -0
  258. package/src/local/resource-disk-headroom.mjs +13 -0
  259. package/src/local/resource-elastic-memory.mjs +19 -0
  260. package/src/local/resource-elastic-request.mjs +36 -0
  261. package/src/local/resource-foreground-wait.mjs +29 -0
  262. package/src/local/resource-go-concurrency.mjs +43 -0
  263. package/src/local/resource-gradle-concurrency.mjs +57 -0
  264. package/src/local/resource-host-cache.mjs +39 -0
  265. package/src/local/resource-host-darwin.mjs +54 -0
  266. package/src/local/resource-host-linux.mjs +35 -0
  267. package/src/local/resource-host-sample-file.mjs +23 -0
  268. package/src/local/resource-host-snapshot.mjs +99 -0
  269. package/src/local/resource-lease-accounting.mjs +119 -0
  270. package/src/local/resource-light-command.mjs +6 -0
  271. package/src/local/resource-make-concurrency.mjs +42 -0
  272. package/src/local/resource-maven-concurrency.mjs +38 -0
  273. package/src/local/resource-ninja-command-concurrency.mjs +20 -0
  274. package/src/local/resource-ninja-concurrency.mjs +28 -0
  275. package/src/local/resource-operations.mjs +26 -10
  276. package/src/local/resource-pressure.mjs +67 -0
  277. package/src/local/resource-probe-command.mjs +35 -0
  278. package/src/local/resource-process-admission.mjs +59 -0
  279. package/src/local/resource-process-ancestry-cache.mjs +43 -0
  280. package/src/local/resource-process-ancestry.mjs +32 -0
  281. package/src/local/resource-process-priority.mjs +15 -0
  282. package/src/local/resource-project-key.mjs +53 -0
  283. package/src/local/resource-pytest-concurrency.mjs +33 -0
  284. package/src/local/resource-release-control-classification.mjs +14 -0
  285. package/src/local/resource-release-control-executable.mjs +70 -0
  286. package/src/local/resource-release-control-workspace.mjs +41 -0
  287. package/src/local/resource-request-contract.mjs +30 -0
  288. package/src/local/resource-script-classification.mjs +44 -0
  289. package/src/local/resource-shell-analysis.mjs +58 -0
  290. package/src/local/resource-staging-recovery.mjs +97 -0
  291. package/src/local/resource-swift-concurrency.mjs +31 -0
  292. package/src/local/resource-transaction-lock.mjs +189 -0
  293. package/src/local/resource-wait.mjs +48 -0
  294. package/src/local/resource-waiters.mjs +139 -0
  295. package/src/local/resource-xcode-command.mjs +7 -0
  296. package/src/local/resource-xcode-concurrency.mjs +34 -0
  297. package/src/local/resource-xcode-non-build.mjs +19 -0
  298. package/src/local/runtime-activation.mjs +700 -0
  299. package/src/local/runtime-activity-projection.mjs +48 -0
  300. package/src/local/runtime-capabilities.mjs +48 -15
  301. package/src/local/runtime-diagnostic-state.mjs +14 -0
  302. package/src/local/runtime-diagnostics.mjs +40 -13
  303. package/src/local/runtime-info-projection.mjs +60 -0
  304. package/src/local/runtime-info-relay-projection.mjs +21 -0
  305. package/src/local/runtime-paths.mjs +2 -1
  306. package/src/local/runtime-process-routing.mjs +33 -0
  307. package/src/local/runtime-relay-connection-options.mjs +44 -0
  308. package/src/local/runtime-relay-control.mjs +98 -0
  309. package/src/local/runtime-relay.mjs +26 -50
  310. package/src/local/runtime-reporting.mjs +37 -21
  311. package/src/local/runtime-resource-service.mjs +77 -0
  312. package/src/local/runtime-tool-handlers.mjs +13 -10
  313. package/src/local/runtime.mjs +244 -208
  314. package/src/local/secure-file.mjs +73 -20
  315. package/src/local/security-audit-dispatch.mjs +35 -0
  316. package/src/local/security-audit-log.mjs +215 -0
  317. package/src/local/security-audit-state.mjs +139 -0
  318. package/src/local/security-audit-storage.mjs +135 -0
  319. package/src/local/security-audit-warning.mjs +31 -0
  320. package/src/local/security-audit-worker.mjs +87 -0
  321. package/src/local/service-convergence.mjs +42 -1
  322. package/src/local/service-definition.mjs +24 -0
  323. package/src/local/service-environment.mjs +18 -10
  324. package/src/local/service-owner.mjs +146 -0
  325. package/src/local/service-ownership.mjs +17 -0
  326. package/src/local/service-restart-handoff.mjs +64 -0
  327. package/src/local/service-restart-scheduler.mjs +49 -0
  328. package/src/local/service-runtime-convergence.mjs +52 -0
  329. package/src/local/service-runtime.mjs +105 -0
  330. package/src/local/service-status.mjs +60 -0
  331. package/src/local/service.mjs +385 -92
  332. package/src/local/shell.mjs +39 -19
  333. package/src/local/ssh-key.mjs +158 -42
  334. package/src/local/state-inventory.mjs +50 -24
  335. package/src/local/state-owner-lock-inventory.mjs +23 -0
  336. package/src/local/state-root-owned-namespaces.mjs +83 -0
  337. package/src/local/state-root-retirement.mjs +89 -0
  338. package/src/local/state.mjs +386 -96
  339. package/src/local/stdio.mjs +96 -66
  340. package/src/local/support-state-projection.mjs +30 -0
  341. package/src/local/system-network-route.mjs +76 -0
  342. package/src/local/systemd-removal.mjs +20 -0
  343. package/src/local/tool-executor.mjs +85 -20
  344. package/src/local/tool-result-boundary.mjs +51 -0
  345. package/src/local/toolchain-operation-lock.mjs +41 -0
  346. package/src/local/tools.mjs +7 -5
  347. package/src/local/trusted-executable.mjs +53 -0
  348. package/src/local/trusted-git-executable.mjs +34 -0
  349. package/src/local/trusted-github-cli.mjs +24 -0
  350. package/src/local/windows-service-convergence.mjs +49 -0
  351. package/src/local/windows-service.mjs +52 -45
  352. package/src/local/worker-deployment-fingerprint.mjs +102 -0
  353. package/src/local/worker-deployment.mjs +48 -60
  354. package/src/local/worker-health.mjs +12 -4
  355. package/src/local/worker-secret-file.mjs +37 -17
  356. package/src/local/workspace-file-service.mjs +156 -239
  357. package/src/local/workspace-file-transaction.mjs +189 -0
  358. package/src/local/workspace-search.mjs +55 -0
  359. package/src/local/wrangler-toolchain/package-lock.json +1505 -0
  360. package/src/local/wrangler-toolchain/package.json +23 -0
  361. package/src/local/wrangler-toolchain-verification.mjs +135 -0
  362. package/src/local/wrangler-toolchain.mjs +188 -0
  363. package/src/shared/access-contract.json +7 -1
  364. package/src/shared/activation-recovery.mjs +49 -0
  365. package/src/shared/admin-auth.d.mts +2 -1
  366. package/src/shared/admin-auth.mjs +3 -3
  367. package/src/shared/authority-revocation.d.mts +9 -0
  368. package/src/shared/authority-revocation.mjs +28 -0
  369. package/src/shared/daemon-auth.d.mts +11 -0
  370. package/src/shared/daemon-auth.mjs +17 -0
  371. package/src/shared/device-session-auth.d.mts +20 -0
  372. package/src/shared/device-session-auth.mjs +51 -0
  373. package/src/shared/foreground-timeout.d.mts +15 -0
  374. package/src/shared/foreground-timeout.mjs +70 -0
  375. package/src/shared/log-redaction.d.mts +9 -0
  376. package/src/shared/log-redaction.mjs +51 -0
  377. package/src/shared/mcp-protocol.d.mts +27 -0
  378. package/src/shared/mcp-protocol.mjs +275 -0
  379. package/src/shared/project-overview-projection.d.mts +4 -0
  380. package/src/shared/project-overview-projection.mjs +96 -0
  381. package/src/shared/relay-contract.json +28 -0
  382. package/src/shared/result-projection.d.mts +2 -1
  383. package/src/shared/result-projection.mjs +13 -2
  384. package/src/shared/server-metadata.json +15 -9
  385. package/src/shared/tool-argument-validation.d.mts +17 -0
  386. package/src/shared/tool-argument-validation.mjs +325 -0
  387. package/src/shared/tool-call-capacity.d.mts +41 -0
  388. package/src/shared/tool-call-capacity.mjs +59 -0
  389. package/src/shared/tool-catalog.json +626 -57
  390. package/src/worker/access.ts +12 -0
  391. package/src/worker/account-admin.ts +55 -33
  392. package/src/worker/authority-revocations.ts +174 -0
  393. package/src/worker/authority.ts +52 -6
  394. package/src/worker/daemon-auth.ts +49 -68
  395. package/src/worker/daemon-channel.ts +36 -0
  396. package/src/worker/daemon-http-auth.ts +63 -0
  397. package/src/worker/daemon-http-channel.ts +120 -0
  398. package/src/worker/daemon-http-controller.ts +163 -0
  399. package/src/worker/daemon-http-protocol.ts +62 -0
  400. package/src/worker/daemon-http-registry.ts +82 -0
  401. package/src/worker/daemon-last-observation.ts +30 -0
  402. package/src/worker/daemon-liveness.ts +8 -6
  403. package/src/worker/daemon-ready-dispatch.ts +17 -0
  404. package/src/worker/daemon-ready-messages.ts +73 -0
  405. package/src/worker/daemon-ready-waiters.ts +79 -0
  406. package/src/worker/daemon-recovery-budget.ts +13 -0
  407. package/src/worker/daemon-registry.ts +58 -0
  408. package/src/worker/daemon-relay-diagnostics.ts +127 -0
  409. package/src/worker/daemon-socket-attachment.ts +55 -0
  410. package/src/worker/daemon-sockets.ts +72 -62
  411. package/src/worker/daemon-status.ts +38 -0
  412. package/src/worker/device-session-verifier.ts +129 -0
  413. package/src/worker/dpop.ts +157 -0
  414. package/src/worker/durable-process-timeout.ts +29 -0
  415. package/src/worker/errors.ts +57 -0
  416. package/src/worker/http.ts +112 -26
  417. package/src/worker/index.ts +446 -440
  418. package/src/worker/mcp-access.ts +57 -0
  419. package/src/worker/mcp-controller.ts +188 -0
  420. package/src/worker/mcp-http-accept.ts +11 -0
  421. package/src/worker/mcp-http-contract.ts +322 -0
  422. package/src/worker/mcp-initialization-compat.ts +141 -0
  423. package/src/worker/mcp-jsonrpc.ts +11 -29
  424. package/src/worker/mcp-removed-protocol.ts +17 -0
  425. package/src/worker/mcp-response-proxy.ts +135 -0
  426. package/src/worker/mcp-response-stream.ts +65 -0
  427. package/src/worker/mcp-stale-schema-compat.ts +57 -0
  428. package/src/worker/mcp-stream-proxy-contract.ts +48 -0
  429. package/src/worker/mcp-tool-call-input.ts +23 -0
  430. package/src/worker/nonce-store.ts +33 -35
  431. package/src/worker/oauth-authorization-page.ts +21 -1
  432. package/src/worker/oauth-client-admin.ts +52 -0
  433. package/src/worker/oauth-client-contract.ts +38 -0
  434. package/src/worker/oauth-controller.ts +107 -47
  435. package/src/worker/oauth-field-contract.ts +31 -0
  436. package/src/worker/oauth-record-contract.ts +7 -0
  437. package/src/worker/oauth-refresh-authority.ts +27 -0
  438. package/src/worker/oauth-refresh-exchange.ts +162 -0
  439. package/src/worker/oauth-refresh-families.ts +130 -27
  440. package/src/worker/oauth-refresh-persistence.ts +116 -0
  441. package/src/worker/oauth-state.ts +33 -18
  442. package/src/worker/oauth-store-validation.ts +131 -0
  443. package/src/worker/oauth-token-derivation.ts +33 -0
  444. package/src/worker/oauth-token-issuance.ts +119 -0
  445. package/src/worker/oauth-tokens.ts +56 -190
  446. package/src/worker/observability.ts +38 -5
  447. package/src/worker/pending-admission.ts +15 -0
  448. package/src/worker/pending-call-capacity.ts +108 -0
  449. package/src/worker/pending-call-contract.ts +42 -5
  450. package/src/worker/pending-call-deadlines.ts +75 -0
  451. package/src/worker/pending-calls.ts +131 -125
  452. package/src/worker/runtime-alarm-storage.ts +37 -0
  453. package/src/worker/runtime-alarm.ts +136 -0
  454. package/src/worker/server-info-activity.ts +36 -0
  455. package/src/worker/server-info-tool-delivery.ts +15 -0
  456. package/src/worker/server-info.ts +168 -0
  457. package/src/worker/tool-catalog.ts +47 -1
  458. package/src/worker/tool-timeout.ts +75 -14
  459. package/src/worker/websocket-protocol.ts +26 -2
  460. package/src/worker/worker-edge-guard.ts +90 -0
  461. package/src/worker/worker-edge-log.ts +65 -0
  462. package/src/worker/worker-entry.ts +52 -0
  463. package/src/worker/worker-mcp-config.ts +23 -0
  464. package/src/worker/worker-metadata.ts +48 -0
  465. package/src/worker/worker-rate-limit-key.ts +30 -0
  466. package/src/worker/worker-runtime-config.ts +20 -0
  467. package/src/worker/worker-static-routes.ts +54 -0
  468. package/src/worker/worker-task-lifetime.ts +18 -0
  469. package/tsconfig.local.json +17 -1
  470. package/wrangler.jsonc +13 -0
  471. package/src/local/cli-approval.mjs +0 -117
  472. package/src/local/operation-state-lock.mjs +0 -92
  473. package/src/worker/mcp-session.ts +0 -72
@@ -1,8 +1,12 @@
1
- import { opendir, realpath } from "node:fs/promises";
2
- import { homedir } from "node:os";
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 = String(args.query || "").trim().toLowerCase();
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
- capabilities: this.capabilities(),
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 (args.background === true) argv.push("-g");
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 escaped = resolvedApplication.replace(/'/g, "''");
106
- const targetFragment = target ? ` -ArgumentList '${target.replace(/'/g, "''")}'` : "";
107
- command = { cmd: "powershell.exe", argv: ["-NoProfile", "-NonInteractive", "-Command", `Start-Process -FilePath '${escaped}'${targetFragment}`] };
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
- const result = await this.runProcess(command.cmd, command.argv, clampInt(args.timeout_seconds, 30, 1, 120) * 1000, false, 256 * 1024, context, undefined, null);
116
- return { application, resolved_application: resolvedApplication, target, platform: this.platform, ...result };
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: args.include_values === true,
130
- includeMenus: args.include_menus === true,
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
- ...result,
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
- let value = args.value === undefined ? null : String(args.value);
150
- if (args.value_resource !== undefined) {
151
- if (value !== null) throw new Error("value and value_resource are mutually exclusive");
152
- value = await this.readResourceText(requiredResourceName(args.value_resource));
153
- }
154
- if (value !== null && (value.includes("\0") || value.length > MAX_TEXT_CHARS)) {
155
- throw new Error(`application action value exceeds ${MAX_TEXT_CHARS} characters or contains a NUL byte`);
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
- action,
540
+ expectedProcessId,
541
+ expectedProcessGeneration,
542
+ action: keyboardAction ? "focus" : action,
161
543
  selector,
162
- value,
163
- maxDepth: clampInt(args.max_depth, 8, 1, MAX_UI_DEPTH),
544
+ value: keyboardAction ? null : value,
545
+ key: null,
546
+ maxDepth,
164
547
  maxElements: MAX_UI_ELEMENTS,
165
- includeMenus: args.include_menus === true,
548
+ includeMenus,
549
+ includeProcessGeneration: true,
550
+ expectedWindowBounds,
551
+ expectedElementBounds,
166
552
  };
167
- const result = await this.runJxa(payload, clampInt(args.timeout_seconds, 30, 1, 120), context);
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
- ...result,
568
+ ...(valueVerificationHandle ? { _machine_value_verification_handle: valueVerificationHandle } : {}),
569
+ ...publicResult,
176
570
  };
177
571
  }
178
572
 
179
- async runJxa(payload, timeoutSeconds, context) {
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
- const result = await this.runProcess(
182
- "osascript",
183
- ["-l", "JavaScript", "-e", MACOS_UI_JXA],
184
- timeoutSeconds * 1000,
185
- false,
186
- 1024 * 1024,
187
- context,
188
- undefined,
189
- input,
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) throw new Error("macOS accessibility helper returned no JSON 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.error) throw new Error(String(parsed.error));
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
- const root = await realpath(inputRoot).catch(() => "");
244
- if (!root || seenRoots.has(root)) continue;
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
- const handle = await opendir(current.directory).catch(() => null);
250
- if (!handle) continue;
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) return { applications: results, truncated: true, visitedEntries: visited };
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) return { applications: results, truncated: true, visitedEntries: visited };
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
- const application = String(value || "").trim();
270
- if (!APP_NAME_PATTERN.test(application)) throw new Error("application must be a non-empty name or path without control characters");
271
- return application;
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
- const action = String(value || "").trim();
282
- if (!["activate", "click", "set_value", "focus", "press", "keystroke"].includes(action)) {
283
- throw new Error("action must be one of activate, click, set_value, focus, press, or keystroke");
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 action;
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
- const name = String(value || "").trim();
303
- if (!/^[a-z][a-z0-9._-]{0,63}$/.test(name)) throw new Error("value_resource is invalid");
304
- return name;
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 || value === null || value === "") return "";
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 || value === null || value === "") return fallback;
319
- const parsed = Number(value);
320
- if (!Number.isInteger(parsed)) throw new Error(`expected an integer from ${min} to ${max}`);
321
- return Math.min(Math.max(parsed, min), max);
322
- }
323
-
324
- const MACOS_UI_JXA = String.raw`
325
- ObjC.import('Foundation');
326
- function readPayload() {
327
- const data = $.NSFileHandle.fileHandleWithStandardInput.readDataToEndOfFile;
328
- const text = ObjC.unwrap($.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding));
329
- return JSON.parse(text);
330
- }
331
- function safe(fn, fallback) { try { const value = fn(); return value === undefined || value === null ? fallback : value; } catch (_) { return fallback; } }
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
+ }