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,5 +1,6 @@
1
1
  (() => {
2
- if (globalThis.__machineBridgePageAutomation) return;
2
+ const PAGE_AUTOMATION_VERSION = 4;
3
+ if (globalThis.__machineBridgePageAutomation?.version === PAGE_AUTOMATION_VERSION) return;
3
4
 
4
5
  const INTERACTIVE_SELECTOR = "a,button,input,select,textarea,[role],[contenteditable]:not([contenteditable='false']),summary";
5
6
  const MAX_SHADOW_ROOTS = 200;
@@ -9,6 +10,16 @@
9
10
  const MAX_PAGE_URL_CHARS = 8192;
10
11
  const MAX_WAIT_TEXT_CHARS = 2 * 1024 * 1024;
11
12
  const MAX_ELEMENT_REFS = 10000;
13
+ const PAGE_ACTIONS = new Set([
14
+ "click", "double_click", "hover", "fill", "type_text", "select", "check", "uncheck", "focus", "press", "submit", "scroll_into_view",
15
+ ]);
16
+ const FORM_ACTIONS = new Set(["fill", "select", "check", "uncheck", "click"]);
17
+ const SELECTOR_FIELDS = new Set(["ref", "css", "id", "name", "label", "text", "role", "placeholder", "index"]);
18
+ const SNAPSHOT_IDENTITY_BOOLEAN_FIELDS = new Set(["sensitive", "in_shadow_dom"]);
19
+ const SNAPSHOT_IDENTITY_FIELDS = new Set([
20
+ "tag", "type", "role", "name", "id", "field_name", "label", "placeholder", "href", "sensitive", "in_shadow_dom",
21
+ ]);
22
+ const DOCUMENT_EPOCH = `doc_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 14)}`;
12
23
  const elementRefs = new WeakMap();
13
24
  const refElements = new Map();
14
25
  let nextRef = 1;
@@ -82,6 +93,54 @@
82
93
  return results;
83
94
  }
84
95
 
96
+ function normalizedFocusQuery(value) {
97
+ if (value === undefined || value === "") return "";
98
+ if (typeof value !== "string" || value.length > 1000 || value.includes("\0")) throw new Error("browser inspection focusQuery is invalid");
99
+ return value.trim().replace(/\s+/g, " ").toLowerCase();
100
+ }
101
+
102
+ function inspectBoolean(value, label, fallback) {
103
+ if (value === undefined) return fallback;
104
+ if (typeof value !== "boolean") throw new Error(`browser inspection ${label} is invalid`);
105
+ return value;
106
+ }
107
+
108
+ function focusMatchScore(element, focusQuery) {
109
+ if (!focusQuery) return 0;
110
+ const identity = [
111
+ element.getAttribute?.("aria-label"), element.getAttribute?.("title"), element.getAttribute?.("placeholder"),
112
+ element.getAttribute?.("name"), element.id, boundedNodeText(element, 500), element.getAttribute?.("role"),
113
+ ].filter(Boolean).join(" ").toLowerCase().replace(/\s+/g, " ");
114
+ if (identity === focusQuery) return 240;
115
+ if (identity.includes(focusQuery)) return 180;
116
+ const tokens = focusQuery.split(" ").filter((token) => token.length > 1);
117
+ return Math.min(120, tokens.filter((token) => identity.includes(token)).length * 30);
118
+ }
119
+
120
+ function elementSalience(element, focusQuery) {
121
+ let score = 0;
122
+ const tag = String(element.tagName || "").toLowerCase();
123
+ if (["button", "input", "select", "textarea", "a", "summary"].includes(tag)) score += 20;
124
+ if (element === document.activeElement || element.focused === true) score += 80;
125
+ if (element.disabled === true || String(element.getAttribute?.("aria-disabled") || "").toLowerCase() === "true") score -= 40;
126
+ const rect = safeBoundingRect(element);
127
+ if (rect && rect.width > 0 && rect.height > 0) {
128
+ score += 20;
129
+ if (rect.x + rect.width > 0 && rect.y + rect.height > 0 && rect.x < Number(globalThis.innerWidth) && rect.y < Number(globalThis.innerHeight)) score += 50;
130
+ }
131
+ if (Number(element.tabIndex) >= 0) score += 10;
132
+ score += focusMatchScore(element, focusQuery);
133
+ return score;
134
+ }
135
+
136
+ function safeBoundingRect(element) {
137
+ try {
138
+ const rect = element?.getBoundingClientRect?.();
139
+ if (!rect || ![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) return null;
140
+ return rect;
141
+ } catch { return null; }
142
+ }
143
+
85
144
  function isInteractiveElement(element) {
86
145
  const tag = String(element.tagName || "").toLowerCase();
87
146
  return ["a", "button", "input", "select", "textarea", "summary"].includes(tag)
@@ -124,24 +183,39 @@
124
183
 
125
184
  function inspect(params) {
126
185
  pruneDetachedRefs();
127
- const maxElements = Math.max(0, Number(params.maxElements) || 0);
128
- const includeValues = params.includeValues === true;
186
+ if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("browser inspection payload is invalid");
187
+ const maxElements = params.maxElements === undefined ? 0 : params.maxElements;
188
+ if (!Number.isSafeInteger(maxElements) || maxElements < 0) throw new Error("browser inspection maxElements is invalid");
189
+ const includeValues = inspectBoolean(params.includeValues, "includeValues", false);
190
+ const includePrivateHistory = inspectBoolean(params.includePrivateHistory, "includePrivateHistory", false);
191
+ const focusQuery = normalizedFocusQuery(params.focusQuery);
129
192
  const scan = scanPageElements();
130
193
  const candidates = [];
131
194
  let interactiveCount = 0;
132
195
  let formCount = 0;
196
+ let sourceIndex = 0;
133
197
  for (const entry of scan.entries) {
134
198
  for (const element of entry.elements) {
135
199
  if (String(element.tagName || "").toLowerCase() === "form") formCount += 1;
136
- if (!isInteractiveElement(element)) continue;
200
+ if (!isInteractiveElement(element)) { sourceIndex += 1; continue; }
137
201
  interactiveCount += 1;
138
- if (candidates.length < maxElements) candidates.push(element);
202
+ candidates.push({ element, score: elementSalience(element, focusQuery), focusScore: focusMatchScore(element, focusQuery), sourceIndex });
203
+ sourceIndex += 1;
139
204
  }
140
205
  }
141
- const describedElements = candidates.map((element, index) => describeElement(element, index, includeValues));
206
+ candidates.sort((left, right) => right.score - left.score || left.sourceIndex - right.sourceIndex);
207
+ const selected = candidates.slice(0, maxElements);
208
+ const queryMatches = focusQuery ? candidates.filter((candidate) => candidate.focusScore > 0) : [];
209
+ const describedElements = selected.map(({ element, score, focusScore }, index) => ({
210
+ ...describeElement(element, index, includeValues),
211
+ salience_score: Math.round(score),
212
+ ...(focusQuery ? { focus_match_score: Math.round(focusScore) } : {}),
213
+ }));
142
214
  return {
143
- snapshot_version: 2,
215
+ snapshot_version: 3,
144
216
  document: {
217
+ epoch: DOCUMENT_EPOCH,
218
+ ...(includePrivateHistory ? { _machine_history_entry_key: historyEntryKey() } : {}),
145
219
  title: boundedPageText(document.title, 1000),
146
220
  url: safePageUrl(location.href),
147
221
  language: boundedPageText(document.documentElement.lang, 100),
@@ -154,111 +228,424 @@
154
228
  tracked_refs: refElements.size,
155
229
  ref_limit: MAX_ELEMENT_REFS,
156
230
  refs_evicted: evictedRefs,
231
+ selection_strategy: focusQuery ? "salience+focus_query" : "salience",
232
+ focus_query_applied: Boolean(focusQuery),
233
+ focus_query_matched: focusQuery ? queryMatches.length > 0 : null,
234
+ focus_query_match_count: focusQuery ? queryMatches.length : null,
235
+ focus_query_search_exhaustive: focusQuery ? scan.truncated !== true : null,
157
236
  },
158
237
  elements: describedElements,
159
238
  truncated: scan.truncated || interactiveCount > describedElements.length,
160
239
  };
161
240
  }
162
241
 
242
+ function historyEntryKey() {
243
+ try {
244
+ const value = globalThis.navigation?.currentEntry?.key;
245
+ return typeof value === "string" && value && !value.includes("\0") ? value.slice(0, 512) : "";
246
+ } catch { return ""; }
247
+ }
248
+
249
+ function documentState() {
250
+ const viewport = globalThis.visualViewport;
251
+ return {
252
+ epoch: DOCUMENT_EPOCH,
253
+ _machine_history_entry_key: historyEntryKey(),
254
+ title: boundedPageText(document.title, 1000),
255
+ url: safePageUrl(location.href),
256
+ ready_state: document.readyState,
257
+ viewport: {
258
+ width: finiteNumber(viewport?.width, finiteNumber(globalThis.innerWidth, 0)),
259
+ height: finiteNumber(viewport?.height, finiteNumber(globalThis.innerHeight, 0)),
260
+ offset_left: finiteNumber(viewport?.offsetLeft, 0),
261
+ offset_top: finiteNumber(viewport?.offsetTop, 0),
262
+ scale: finiteNumber(viewport?.scale, 1),
263
+ scroll_x: finiteNumber(globalThis.scrollX, 0),
264
+ scroll_y: finiteNumber(globalThis.scrollY, 0),
265
+ device_pixel_ratio: finiteNumber(globalThis.devicePixelRatio, 1),
266
+ },
267
+ };
268
+ }
269
+
270
+ function exactOptionalAuthorityString(value, maxLength) {
271
+ if (value === undefined || value === "") return "";
272
+ if (typeof value !== "string" || value.length > maxLength || value.includes("\0")) {
273
+ throw new Error("snapshot authority string is invalid before dispatch");
274
+ }
275
+ return value;
276
+ }
277
+
278
+ function historyAction(params) {
279
+ const action = exactOptionalAuthorityString(params?.action, 32);
280
+ if (!["reload", "back", "forward"].includes(action)) throw new Error("history action is invalid before dispatch");
281
+ const expectedUrl = exactOptionalAuthorityString(params?.expectedTabUrl, 32768);
282
+ const expectedDocument = exactOptionalAuthorityString(params?.expectedDocumentEpoch, 9000);
283
+ const expectedHistoryEntry = exactOptionalAuthorityString(params?.expectedHistoryEntryKey, 512);
284
+ const currentUrl = safePageUrl(location.href);
285
+ if (expectedUrl && currentUrl !== expectedUrl) {
286
+ throw new Error("snapshot browser tab changed before navigation dispatch; observe again");
287
+ }
288
+ if (expectedDocument && DOCUMENT_EPOCH !== expectedDocument) {
289
+ throw new Error("snapshot history document changed before dispatch; observe again");
290
+ }
291
+ if (expectedHistoryEntry) {
292
+ const currentHistoryEntry = historyEntryKey();
293
+ if (!currentHistoryEntry) throw new Error("snapshot history entry could not be verified before dispatch; observe again");
294
+ if (currentHistoryEntry !== expectedHistoryEntry) throw new Error("snapshot history entry changed before dispatch; observe again");
295
+ }
296
+ const navigation = globalThis.navigation;
297
+ if (action === "back" && navigation?.canGoBack !== true) {
298
+ throw new Error("snapshot browser history has no back entry before dispatch; observe again");
299
+ }
300
+ if (action === "forward" && navigation?.canGoForward !== true) {
301
+ throw new Error("snapshot browser history has no forward entry before dispatch; observe again");
302
+ }
303
+ const mutate = action === "reload" ? navigation?.reload : action === "back" ? navigation?.back : navigation?.forward;
304
+ if (typeof mutate !== "function") throw new Error("snapshot history mutation API is unavailable before dispatch; observe again");
305
+ try { mutate.call(navigation); }
306
+ catch {
307
+ throw new Error("browser action may have been dispatched; the action outcome is unknown because the renderer history mutation API failed. Inspect the page before retrying.");
308
+ }
309
+ return { dispatched: true };
310
+ }
311
+
312
+ function refIdentity(params) {
313
+ const ref = params?.ref;
314
+ if (typeof ref !== "string" || !ref || ref.length > 100 || ref.includes("\0")) throw new Error("element reference is invalid");
315
+ const elements = findElements({ ref }, { allowStaleRef: true });
316
+ const element = elements.length === 1 ? elements[0] : null;
317
+ return {
318
+ attached: Boolean(element),
319
+ matched: Boolean(element && snapshotIdentityMatches(element, params?.expectedIdentity)),
320
+ };
321
+ }
322
+
323
+ function pointProbe(params) {
324
+ const x = exactFiniteNumber(params?.x);
325
+ const y = exactFiniteNumber(params?.y);
326
+ const width = exactPositiveFiniteNumber(globalThis.innerWidth);
327
+ const height = exactPositiveFiniteNumber(globalThis.innerHeight);
328
+ if (x === null || y === null || width === null || height === null || x < 0 || y < 0 || x >= width || y >= height) {
329
+ throw new Error("visual point is outside the current viewport");
330
+ }
331
+ const raw = document.elementFromPoint(x, y);
332
+ if (!raw) return { point: { x, y }, hit: null };
333
+ const target = isInteractiveElement(raw) ? raw : raw.closest?.(INTERACTIVE_SELECTOR) || raw;
334
+ return { point: { x, y }, hit: describeElement(target, 0, false), raw_tag: String(raw.tagName || "").toLowerCase() };
335
+ }
336
+
337
+ function exactFiniteNumber(value) {
338
+ return typeof value === "number" && Number.isFinite(value) ? (Object.is(value, -0) ? 0 : value) : null;
339
+ }
340
+
341
+ function exactPositiveFiniteNumber(value) {
342
+ const number = exactFiniteNumber(value);
343
+ return number !== null && number > 0 ? number : null;
344
+ }
345
+
346
+ function exactElementTimeoutMs(value) {
347
+ if (value === undefined) return 10000;
348
+ if (!Number.isSafeInteger(value) || value < 1 || value > 120000) throw new Error("element timeout is invalid before dispatch");
349
+ return value;
350
+ }
351
+
352
+ function requiredMutationText(value, label, maxLength = 131072) {
353
+ if (typeof value !== "string" || value.includes("\0") || value.length > maxLength) {
354
+ throw new Error(`${label} must be a string of at most ${maxLength} characters without NUL bytes before dispatch`);
355
+ }
356
+ return value;
357
+ }
358
+
359
+ function validatePageSelector(value) {
360
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("selector must be an object before dispatch");
361
+ const output = {};
362
+ for (const key of Object.keys(value)) if (!SELECTOR_FIELDS.has(key)) throw new Error(`unknown selector field before dispatch: ${key}`);
363
+ for (const key of ["ref", "css", "id", "name", "label", "text", "role", "placeholder"]) {
364
+ if (!Object.hasOwn(value, key)) continue;
365
+ const maxLength = key === "ref" ? 100 : 2000;
366
+ output[key] = requiredMutationText(value[key], `selector.${key}`, maxLength);
367
+ }
368
+ if (Object.hasOwn(value, "index")) {
369
+ if (!Number.isSafeInteger(value.index) || value.index < 0 || value.index > 10000) throw new Error("selector.index must be an integer from 0 to 10000 before dispatch");
370
+ output.index = value.index;
371
+ }
372
+ if (!Object.keys(output).length) throw new Error("selector requires at least one field before dispatch");
373
+ if (output.ref !== undefined && Object.keys(output).length !== 1) throw new Error("selector.ref cannot be combined with other fields before dispatch");
374
+ return output;
375
+ }
376
+
377
+ function validateExpectedIdentity(value) {
378
+ if (value === undefined || value === null) return null;
379
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("snapshot identity must be an object before dispatch");
380
+ const output = {};
381
+ for (const [field, expected] of Object.entries(value)) {
382
+ if (!SNAPSHOT_IDENTITY_FIELDS.has(field)) throw new Error(`unknown snapshot identity field before dispatch: ${field}`);
383
+ if (SNAPSHOT_IDENTITY_BOOLEAN_FIELDS.has(field)) {
384
+ if (typeof expected !== "boolean") throw new Error(`snapshot identity ${field} must be boolean before dispatch`);
385
+ output[field] = expected;
386
+ } else {
387
+ output[field] = requiredMutationText(expected, `snapshot identity ${field}`, field === "href" ? 8192 : 2000);
388
+ }
389
+ }
390
+ return output;
391
+ }
392
+
393
+ function requiredPageAction(value) {
394
+ if (typeof value !== "string" || !PAGE_ACTIONS.has(value)) throw new Error("page action is invalid before dispatch");
395
+ return value;
396
+ }
397
+
398
+ function validateActionPayload(params) {
399
+ if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("page action payload is invalid before dispatch");
400
+ const allowed = new Set(["action", "selector", "value", "key", "elementTimeoutMs", "expectedIdentity"]);
401
+ for (const key of Object.keys(params)) if (!allowed.has(key)) throw new Error(`unknown page action property before dispatch: ${key}`);
402
+ const action = requiredPageAction(params.action);
403
+ const selector = validatePageSelector(params.selector);
404
+ const timeoutMs = exactElementTimeoutMs(params.elementTimeoutMs);
405
+ const expectedIdentity = validateExpectedIdentity(params.expectedIdentity);
406
+ let value = params.value;
407
+ let key = params.key;
408
+ if (["fill", "select", "type_text"].includes(action)) {
409
+ value = requiredMutationText(value, "value");
410
+ if (key !== undefined && key !== "") throw new Error("key is not valid for this page action before dispatch");
411
+ } else if (action === "press") {
412
+ if (value !== undefined && value !== null) value = requiredMutationText(value, "value", 100);
413
+ if (key !== undefined && key !== "") key = requiredMutationText(key, "key", 100);
414
+ keyboardEventInit(key || value || "Enter");
415
+ } else {
416
+ if (value !== undefined && value !== null) throw new Error("value is not valid for this page action before dispatch");
417
+ if (key !== undefined && key !== "") throw new Error("key is not valid for this page action before dispatch");
418
+ }
419
+ return { action, selector, timeoutMs, expectedIdentity, value, key };
420
+ }
421
+
422
+ function rendererUtf8ByteLength(value) {
423
+ let bytes = 0;
424
+ for (let index = 0; index < value.length; index += 1) {
425
+ const code = value.charCodeAt(index);
426
+ if (code < 0x80) bytes += 1;
427
+ else if (code < 0x800) bytes += 2;
428
+ else if (code >= 0xd800 && code <= 0xdbff && index + 1 < value.length) {
429
+ const next = value.charCodeAt(index + 1);
430
+ if (next >= 0xdc00 && next <= 0xdfff) { bytes += 4; index += 1; } else bytes += 3;
431
+ } else bytes += 3;
432
+ }
433
+ return bytes;
434
+ }
435
+
436
+ function rendererDecodedBase64Bytes(value) {
437
+ if (typeof value !== "string" || value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) return null;
438
+ const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
439
+ return (value.length / 4) * 3 - padding;
440
+ }
441
+
442
+ function validateFormPayload(params) {
443
+ if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("form payload is invalid before dispatch");
444
+ if (!Array.isArray(params.fields) || params.fields.length < 1 || params.fields.length > 200) throw new Error("form fields are invalid before dispatch");
445
+ if (typeof params.submit !== "boolean") throw new Error("form submit must be boolean before dispatch");
446
+ const timeoutMs = exactElementTimeoutMs(params.elementTimeoutMs);
447
+ const submitSelector = params.submitSelector === undefined || params.submitSelector === null ? null : validatePageSelector(params.submitSelector);
448
+ let totalValueBytes = 0;
449
+ const fields = params.fields.map((field, index) => {
450
+ if (!field || typeof field !== "object" || Array.isArray(field)) throw new Error(`form field ${index} is invalid before dispatch`);
451
+ for (const key of Object.keys(field)) if (!["selector", "action", "value", "sensitive"].includes(key)) throw new Error(`unknown form field ${index} property before dispatch: ${key}`);
452
+ if (typeof field.action !== "string" || !FORM_ACTIONS.has(field.action)) throw new Error(`form field ${index} action is invalid before dispatch`);
453
+ if (typeof field.sensitive !== "boolean") throw new Error(`form field ${index} sensitive flag must be boolean before dispatch`);
454
+ const selector = validatePageSelector(field.selector);
455
+ let value = field.value;
456
+ if (["fill", "select"].includes(field.action)) {
457
+ value = requiredMutationText(value, `form field ${index} value`);
458
+ totalValueBytes += rendererUtf8ByteLength(value);
459
+ if (totalValueBytes > 4 * 1024 * 1024) throw new Error("form values exceed the 4 MiB aggregate budget before dispatch");
460
+ }
461
+ else if (value !== undefined && value !== null) throw new Error(`form field ${index} value is not valid before dispatch`);
462
+ return { selector, action: field.action, value, sensitive: field.sensitive };
463
+ });
464
+ return { fields, submit: params.submit, submitSelector, timeoutMs };
465
+ }
466
+
467
+ function validateUploadPayload(params) {
468
+ if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("upload payload is invalid before dispatch");
469
+ const selector = validatePageSelector(params.selector);
470
+ const timeoutMs = exactElementTimeoutMs(params.elementTimeoutMs);
471
+ if (!Array.isArray(params.files) || params.files.length < 1 || params.files.length > 8) throw new Error("upload files are invalid before dispatch");
472
+ let totalBytes = 0;
473
+ const files = params.files.map((item, index) => {
474
+ if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error(`upload file ${index} is invalid before dispatch`);
475
+ const filename = requiredMutationText(item.filename, `upload file ${index} filename`, 255);
476
+ if (!filename || filename === "." || filename === ".." || /[\/\\\u0000-\u001f\u007f]/.test(filename)) throw new Error(`upload file ${index} filename is invalid before dispatch`);
477
+ const mime = requiredMutationText(item.mime, `upload file ${index} mime`, 200);
478
+ if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(mime)) throw new Error(`upload file ${index} mime is invalid before dispatch`);
479
+ const data = requiredMutationText(item.data, `upload file ${index} data`, 8 * 1024 * 1024);
480
+ const bytes = rendererDecodedBase64Bytes(data);
481
+ if (bytes === null) throw new Error(`upload file ${index} data is invalid before dispatch`);
482
+ totalBytes += bytes;
483
+ if (totalBytes > 5 * 1024 * 1024) throw new Error("upload files exceed the 5 MiB aggregate budget before dispatch");
484
+ return { filename, mime, data };
485
+ });
486
+ return { selector, timeoutMs, files };
487
+ }
488
+
163
489
  async function prepareAction(params) {
164
490
  const element = await prepareElementForAction(params);
165
- const result = actionTarget(element);
166
- return { ok: true, element: describeElement(element, 0, false), point: result.point };
491
+ try {
492
+ const result = actionTarget(element);
493
+ return { ok: true, element: describeElement(element, 0, false), point: result.point };
494
+ } catch (error) {
495
+ throw domMutationUnknown(params.action, error);
496
+ }
167
497
  }
168
498
 
169
499
  async function action(params) {
170
- const element = await prepareElementForAction(params);
171
- await applyOne(element, params.action, params.value, params.key);
172
- return { ok: true, element: describeElement(element, 0, false), input_mode: "dom" };
500
+ const prepared = validateActionPayload(params);
501
+ const element = await waitForActionable(prepared.selector, prepared.action, prepared.timeoutMs);
502
+ assertSnapshotIdentity(element, prepared.expectedIdentity);
503
+ const mutationStarted = await applyOne(element, prepared.action, prepared.value, prepared.key, prepared.timeoutMs);
504
+ try {
505
+ return { ok: true, element: describeElement(element, 0, false), input_mode: "dom" };
506
+ } catch (error) {
507
+ if (mutationStarted) throw domMutationUnknown(prepared.action, error);
508
+ throw error;
509
+ }
173
510
  }
174
511
 
175
512
  async function prepareElementForAction(params) {
176
- const timeoutMs = Number(params.elementTimeoutMs) || 10000;
177
- const element = await waitForActionable(params.selector, params.action, timeoutMs);
178
- if (["press", "type_text", "focus"].includes(params.action)) element.focus();
179
- if (["click", "double_click", "hover"].includes(params.action)) {
180
- scrollElementIntoView(element);
181
- await waitForStableBox(element, timeoutMs);
182
- await waitForPointerTarget(element, timeoutMs);
183
- } else if (params.action === "scroll_into_view") {
184
- scrollElementIntoView(element);
513
+ const prepared = validateActionPayload(params);
514
+ const element = await waitForActionable(prepared.selector, prepared.action, prepared.timeoutMs);
515
+ assertSnapshotIdentity(element, prepared.expectedIdentity);
516
+ let mutationStarted = false;
517
+ try {
518
+ if (["press", "type_text", "focus"].includes(prepared.action)) {
519
+ mutationStarted = true;
520
+ element.focus();
521
+ }
522
+ if (["click", "double_click", "hover"].includes(prepared.action)) {
523
+ mutationStarted = true;
524
+ scrollElementIntoView(element);
525
+ await waitForStableBox(element, prepared.timeoutMs);
526
+ await waitForPointerTarget(element, prepared.timeoutMs);
527
+ } else if (prepared.action === "scroll_into_view") {
528
+ mutationStarted = true;
529
+ scrollElementIntoView(element);
530
+ }
531
+ } catch (error) {
532
+ if (mutationStarted) throw domMutationUnknown(prepared.action, error);
533
+ throw error;
185
534
  }
186
535
  return element;
187
536
  }
188
537
 
189
538
  async function fillForm(params) {
539
+ const prepared = validateFormPayload(params);
190
540
  const results = [];
191
- const timeoutMs = Number(params.elementTimeoutMs) || 10000;
192
- for (let index = 0; index < params.fields.length; index += 1) {
193
- const field = params.fields[index];
541
+ const timeoutMs = prepared.timeoutMs;
542
+ for (let index = 0; index < prepared.fields.length; index += 1) {
543
+ const field = prepared.fields[index];
194
544
  try {
195
545
  const element = await waitForActionable(field.selector, field.action, timeoutMs);
196
- await applyOne(element, field.action, field.value, "");
197
- results.push({ index, action: field.action, sensitive: field.sensitive === true, element: describeElement(element, index, false) });
546
+ const mutationStarted = await applyOne(element, field.action, field.value, "", timeoutMs);
547
+ let description;
548
+ try { description = describeElement(element, index, false); }
549
+ catch (error) { if (mutationStarted) throw domMutationUnknown("fill_form", error); throw error; }
550
+ results.push({ index, action: field.action, sensitive: field.sensitive === true, element: description });
198
551
  } catch (error) {
199
- const prefix = index > 0
200
- ? `form field ${index} (${field.action}) failed after ${index} earlier field(s) may have changed`
201
- : `form field 0 (${field.action}) failed before any earlier field changed`;
552
+ if (index > 0 || String(error?.message || error).includes("outcome is unknown")) {
553
+ throw domMutationUnknown("fill_form", error);
554
+ }
555
+ const prefix = `form field 0 (${field.action}) failed before any earlier field changed`;
202
556
  throw new Error(`${prefix}: ${boundedPageText(error?.message || error, 500)}`);
203
557
  }
204
558
  }
205
- if (params.submit) {
559
+ if (prepared.submit) {
560
+ let submissionStarted = false;
206
561
  try {
207
- const submitter = params.submitSelector ? await waitForActionable(params.submitSelector, "click", timeoutMs) : deepQuerySelectorAll("button[type='submit'],input[type='submit']", 1)[0];
208
- if (submitter) submitter.click();
562
+ const submitter = prepared.submitSelector ? await waitForActionable(prepared.submitSelector, "click", timeoutMs) : deepQuerySelectorAll("button[type='submit'],input[type='submit']", 1)[0];
563
+ if (submitter) {
564
+ submissionStarted = true;
565
+ submitter.click();
566
+ }
209
567
  else {
210
- const field = params.fields.length ? findOne(params.fields[0].selector) : null;
568
+ const field = prepared.fields.length ? findOne(prepared.fields[0].selector) : null;
211
569
  const form = field?.form || field?.closest?.("form") || deepQuerySelectorAll("form", 1)[0];
212
570
  if (!form) throw new Error("no form or submit control found");
571
+ submissionStarted = true;
213
572
  if (typeof form.requestSubmit === "function") form.requestSubmit();
214
573
  else form.submit();
215
574
  }
216
575
  } catch (error) {
576
+ if (submissionStarted || results.length > 0) throw domMutationUnknown("form_submission", error);
217
577
  throw new Error(`form submission failed after ${results.length} field(s) changed: ${boundedPageText(error?.message || error, 500)}`);
218
578
  }
219
579
  }
220
- return { ok: true, fields: results, submitted: params.submit === true, values_exposed: false };
580
+ return { ok: true, fields: results, submitted: prepared.submit, values_exposed: false };
221
581
  }
222
582
 
223
583
  async function uploadFiles(params) {
224
- const input = await waitForActionable(params.selector, "upload", Number(params.elementTimeoutMs) || 10000);
584
+ const prepared = validateUploadPayload(params);
585
+ const input = await waitForActionable(prepared.selector, "upload", prepared.timeoutMs);
225
586
  if (!(input instanceof HTMLInputElement) || input.type !== "file") throw new Error("matched element is not a file input");
226
587
  const transfer = new DataTransfer();
227
- for (const item of params.files) {
588
+ for (const item of prepared.files) {
228
589
  const binary = atob(item.data);
229
590
  const bytes = new Uint8Array(binary.length);
230
591
  for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
231
592
  transfer.items.add(new File([bytes], item.filename, { type: item.mime || "application/octet-stream" }));
232
593
  }
233
- input.files = transfer.files;
234
- dispatchValueEvents(input);
235
- return { ok: true, file_count: transfer.files.length, element: describeElement(input, 0, false), values_exposed: false };
594
+ try {
595
+ input.files = transfer.files;
596
+ dispatchValueEvents(input);
597
+ return { ok: true, file_count: transfer.files.length, element: describeElement(input, 0, false), values_exposed: false };
598
+ } catch (error) {
599
+ throw domMutationUnknown("upload_files", error);
600
+ }
601
+ }
602
+
603
+ function validateWaitPayload(params) {
604
+ if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error("browser wait payload is invalid");
605
+ const allowed = new Set(["selector", "state", "text", "loadState"]);
606
+ for (const key of Object.keys(params)) if (!allowed.has(key)) throw new Error(`unknown browser wait property: ${key}`);
607
+ const selector = params.selector === undefined || params.selector === null ? null : validatePageSelector(params.selector);
608
+ const state = exactWaitString(params.state, "state", 32);
609
+ if (state && !["attached", "detached", "visible", "hidden", "enabled", "editable", "checked", "unchecked"].includes(state)) throw new Error("browser wait state is invalid");
610
+ if (state && !selector) throw new Error("browser wait state requires selector");
611
+ const text = exactWaitString(params.text, "text", 4000);
612
+ const loadState = exactWaitString(params.loadState, "loadState", 32);
613
+ if (loadState && !["domcontentloaded", "complete"].includes(loadState)) throw new Error("browser wait loadState is invalid");
614
+ if (!selector && !text && !loadState) throw new Error("browser wait payload requires a page condition");
615
+ return { selector, state, text, loadState };
616
+ }
617
+
618
+ function exactWaitString(value, label, maxLength) {
619
+ if (value === undefined || value === "") return "";
620
+ if (typeof value !== "string" || value.includes("\0") || value.length > maxLength) throw new Error(`browser wait ${label} is invalid`);
621
+ return value;
236
622
  }
237
623
 
238
624
  function checkWait(params) {
239
- const elements = params.selector ? findElements(params.selector, { allowStaleRef: true }) : [];
625
+ const prepared = validateWaitPayload(params);
626
+ const elements = prepared.selector ? findElements(prepared.selector, { allowStaleRef: true }) : [];
240
627
  const visible = elements.filter(isVisible);
241
628
  const enabled = elements.filter(isEnabled);
242
629
  const editable = elements.filter(isEditable);
243
- const stateMatched = !params.state
244
- || (params.state === "attached" && elements.length > 0)
245
- || (params.state === "detached" && elements.length === 0)
246
- || (params.state === "visible" && visible.length > 0)
247
- || (params.state === "hidden" && visible.length === 0)
248
- || (params.state === "enabled" && enabled.length > 0)
249
- || (params.state === "editable" && editable.length > 0)
250
- || (params.state === "checked" && elements.some((element) => "checked" in element && element.checked === true))
251
- || (params.state === "unchecked" && elements.some((element) => "checked" in element && element.checked === false));
252
- const textSearch = params.text ? pageContainsText(params.text) : { found: true, truncated: false, scanned_chars: 0 };
630
+ const stateMatched = !prepared.state
631
+ || (prepared.state === "attached" && elements.length > 0)
632
+ || (prepared.state === "detached" && elements.length === 0)
633
+ || (prepared.state === "visible" && visible.length > 0)
634
+ || (prepared.state === "hidden" && visible.length === 0)
635
+ || (prepared.state === "enabled" && enabled.length > 0)
636
+ || (prepared.state === "editable" && editable.length > 0)
637
+ || (prepared.state === "checked" && elements.some((element) => "checked" in element && element.checked === true))
638
+ || (prepared.state === "unchecked" && elements.some((element) => "checked" in element && element.checked === false));
639
+ const textSearch = prepared.text ? pageContainsText(prepared.text) : { found: true, truncated: false, scanned_chars: 0 };
253
640
  const textMatched = textSearch.found;
254
- const loadMatched = !params.loadState
255
- || (params.loadState === "domcontentloaded" && ["interactive", "complete"].includes(document.readyState))
256
- || (params.loadState === "complete" && document.readyState === "complete");
641
+ const loadMatched = !prepared.loadState
642
+ || (prepared.loadState === "domcontentloaded" && ["interactive", "complete"].includes(document.readyState))
643
+ || (prepared.loadState === "complete" && document.readyState === "complete");
257
644
  return {
258
645
  matched: stateMatched && textMatched && loadMatched,
259
646
  selector_count: elements.length,
260
647
  visible_count: visible.length,
261
- state: params.state || "",
648
+ state: prepared.state || "",
262
649
  text_found: textMatched,
263
650
  text_scan_truncated: textSearch.truncated,
264
651
  text_scanned_chars: textSearch.scanned_chars,
@@ -297,6 +684,27 @@
297
684
  return item;
298
685
  }
299
686
 
687
+ function snapshotIdentityMatches(element, expected) {
688
+ if (expected === undefined || expected === null) return true;
689
+ if (!expected || typeof expected !== "object" || Array.isArray(expected)) return false;
690
+ const actual = describeElement(element, 0, false);
691
+ for (const [field, expectedValue] of Object.entries(expected)) {
692
+ if (!SNAPSHOT_IDENTITY_FIELDS.has(field)) return false;
693
+ if (SNAPSHOT_IDENTITY_BOOLEAN_FIELDS.has(field)) {
694
+ if (typeof expectedValue !== "boolean" || actual[field] !== expectedValue) return false;
695
+ continue;
696
+ }
697
+ if (typeof expectedValue !== "string") return false;
698
+ const actualValue = typeof actual[field] === "string" ? actual[field] : "";
699
+ if (actualValue !== expectedValue) return false;
700
+ }
701
+ return true;
702
+ }
703
+
704
+ function assertSnapshotIdentity(element, expected) {
705
+ if (!snapshotIdentityMatches(element, expected)) throw new Error("snapshot_ref_identity_changed_before_dispatch");
706
+ }
707
+
300
708
  function isSensitiveElement(element, tag, type) {
301
709
  if (tag !== "input" && tag !== "textarea" && !element.isContentEditable) return false;
302
710
  if (["password", "hidden"].includes(type)) return true;
@@ -390,79 +798,117 @@
390
798
  function actionTarget(element) {
391
799
  const box = boundingBox(element);
392
800
  if (!box) return { point: null };
393
- const viewportWidth = Number(globalThis.innerWidth);
394
- const viewportHeight = Number(globalThis.innerHeight);
801
+ const viewportWidth = exactPositiveFiniteNumber(globalThis.innerWidth);
802
+ const viewportHeight = exactPositiveFiniteNumber(globalThis.innerHeight);
803
+ if (viewportWidth === null || viewportHeight === null) throw new Error("browser viewport dimensions are unavailable before dispatch");
395
804
  const left = Math.max(0, box.x);
396
805
  const top = Math.max(0, box.y);
397
- const right = Math.min(Number.isFinite(viewportWidth) ? viewportWidth : box.x + box.width, box.x + box.width);
398
- const bottom = Math.min(Number.isFinite(viewportHeight) ? viewportHeight : box.y + box.height, box.y + box.height);
806
+ const right = Math.min(viewportWidth, box.x + box.width);
807
+ const bottom = Math.min(viewportHeight, box.y + box.height);
399
808
  if (right <= left || bottom <= top) return { point: null };
400
809
  return { point: { x: left + (right - left) / 2, y: top + (bottom - top) / 2 } };
401
810
  }
402
811
 
403
- async function applyOne(element, operation, value, key) {
404
- if (!["click", "double_click", "hover", "scroll_into_view"].includes(operation)) scrollElementIntoView(element);
405
- if (operation === "click") { element.click(); return; }
406
- if (operation === "double_click") {
407
- element.click();
408
- element.click();
409
- element.dispatchEvent(new MouseEvent("dblclick", { bubbles: true, composed: true, detail: 2 }));
410
- return;
411
- }
412
- if (operation === "hover") { dispatchHoverEvents(element); return; }
413
- if (operation === "focus") { element.focus(); return; }
414
- if (operation === "scroll_into_view") return;
415
- if (operation === "submit") {
416
- const form = element.form || element.closest("form");
417
- if (!form) throw new Error("matched element is not associated with a form");
418
- if (typeof form.requestSubmit === "function") form.requestSubmit(); else form.submit();
419
- return;
420
- }
421
- if (operation === "check" || operation === "uncheck") {
422
- const wanted = operation === "check";
423
- if (Boolean(element.checked) !== wanted) element.click();
424
- if (Boolean(element.checked) !== wanted) throw new Error(`checkable control did not reach the requested ${wanted ? "checked" : "unchecked"} state`);
425
- return;
426
- }
812
+ async function applyOne(element, operation, value, key, timeoutMs = 10000) {
813
+ const wanted = operation === "check" ? true : operation === "uncheck" ? false : null;
814
+ if (wanted !== null && Boolean(element.checked) === wanted) return false;
815
+ let selectedOption = null;
427
816
  if (operation === "select") {
428
- const text = String(value ?? "");
429
- const option = [...element.options].find((item) => item.value === text || item.text.trim() === text);
430
- if (!option) throw new Error("select option was not found");
431
- setNativeValue(element, option.value);
432
- dispatchValueEvents(element);
433
- return;
434
- }
435
- if (operation === "fill") {
436
- element.focus();
437
- if (element.isContentEditable) element.textContent = String(value ?? "");
438
- else setNativeValue(element, String(value ?? ""));
439
- dispatchValueEvents(element);
440
- return;
441
- }
442
- if (operation === "type_text") {
443
- element.focus();
444
- const text = String(value ?? "");
445
- if (element.isContentEditable) element.textContent = `${element.textContent || ""}${text}`;
446
- else if (typeof element.setRangeText === "function" && Number.isInteger(element.selectionStart) && Number.isInteger(element.selectionEnd)) {
447
- element.setRangeText(text, element.selectionStart, element.selectionEnd, "end");
448
- } else setNativeValue(element, `${String(element.value || "")}${text}`);
449
- dispatchValueEvents(element);
450
- return;
451
- }
452
- if (operation === "press") {
453
- const event = keyboardEventInit(key || value || "Enter");
454
- element.focus();
455
- element.dispatchEvent(new KeyboardEvent("keydown", { ...event, bubbles: true, composed: true }));
456
- element.dispatchEvent(new KeyboardEvent("keyup", { ...event, bubbles: true, composed: true }));
457
- return;
458
- }
459
- throw new Error(`unsupported element action: ${operation}`);
817
+ const text = requiredMutationText(value, "select value");
818
+ selectedOption = [...element.options].find((item) => item.value === text || item.text.trim() === text) || null;
819
+ if (!selectedOption) throw new Error("select option was not found");
820
+ }
821
+ const keyEvent = operation === "press" ? keyboardEventInit(key || value || "Enter") : null;
822
+ let mutationStarted = false;
823
+ try {
824
+ if (["click", "double_click", "hover"].includes(operation)) {
825
+ mutationStarted = true;
826
+ scrollElementIntoView(element);
827
+ await waitForStableBox(element, timeoutMs);
828
+ await waitForPointerTarget(element, timeoutMs);
829
+ if (operation === "click") element.click();
830
+ else if (operation === "double_click") {
831
+ element.click();
832
+ element.click();
833
+ element.dispatchEvent(new MouseEvent("dblclick", { bubbles: true, composed: true, detail: 2 }));
834
+ } else dispatchHoverEvents(element);
835
+ return true;
836
+ }
837
+ if (operation === "focus") { mutationStarted = true; element.focus(); return true; }
838
+ if (operation === "scroll_into_view") { mutationStarted = true; scrollElementIntoView(element); return true; }
839
+ if (operation === "submit") {
840
+ const form = element.form || element.closest("form");
841
+ if (!form) throw new Error("matched element is not associated with a form");
842
+ mutationStarted = true;
843
+ if (typeof form.requestSubmit === "function") form.requestSubmit(); else form.submit();
844
+ return true;
845
+ }
846
+ if (wanted !== null) {
847
+ mutationStarted = true;
848
+ element.click();
849
+ if (Boolean(element.checked) !== wanted) throw new Error(`checkable control did not reach the requested ${wanted ? "checked" : "unchecked"} state`);
850
+ return true;
851
+ }
852
+ if (operation === "select") {
853
+ mutationStarted = true;
854
+ setNativeValue(element, selectedOption.value);
855
+ dispatchValueEvents(element);
856
+ return true;
857
+ }
858
+ if (operation === "fill") {
859
+ const text = requiredMutationText(value, "fill value");
860
+ mutationStarted = true;
861
+ element.focus();
862
+ if (element.isContentEditable) element.textContent = text;
863
+ else setNativeValue(element, text);
864
+ dispatchValueEvents(element);
865
+ return true;
866
+ }
867
+ if (operation === "type_text") {
868
+ const text = requiredMutationText(value, "type_text value");
869
+ mutationStarted = true;
870
+ element.focus();
871
+ if (element.isContentEditable) element.textContent = `${element.textContent || ""}${text}`;
872
+ else if (typeof element.setRangeText === "function" && Number.isInteger(element.selectionStart) && Number.isInteger(element.selectionEnd)) {
873
+ element.setRangeText(text, element.selectionStart, element.selectionEnd, "end");
874
+ } else setNativeValue(element, `${String(element.value || "")}${text}`);
875
+ dispatchValueEvents(element);
876
+ return true;
877
+ }
878
+ if (operation === "press") {
879
+ mutationStarted = true;
880
+ element.focus();
881
+ element.dispatchEvent(new KeyboardEvent("keydown", { ...keyEvent, bubbles: true, composed: true }));
882
+ element.dispatchEvent(new KeyboardEvent("keyup", { ...keyEvent, bubbles: true, composed: true }));
883
+ return true;
884
+ }
885
+ throw new Error(`unsupported element action: ${operation}`);
886
+ } catch (error) {
887
+ if (mutationStarted) throw domMutationUnknown(operation, error);
888
+ throw error;
889
+ }
890
+ }
891
+
892
+ function domMutationUnknown(operation, error) {
893
+ const detail = boundedPageText(error?.message || error, 500).replace(/\s+/g, " ");
894
+ return new Error(`browser action may have been dispatched; the action outcome is unknown because DOM ${operation} failed after a side-effecting step. Inspect the page before retrying.${detail ? ` (${detail})` : ""}`);
460
895
  }
461
896
 
462
897
  function keyboardEventInit(value) {
463
- const parts = String(value || "Enter").split("+").map((part) => part.trim()).filter(Boolean);
898
+ const text = value === undefined || value === "" ? "Enter" : requiredMutationText(value, "key", 100);
899
+ const parts = text.split("+").map((part) => part.trim()).filter(Boolean);
464
900
  const rawKey = parts.pop() || "Enter";
465
- const modifiers = new Set(parts.map((part) => part === "Ctrl" ? "Control" : part === "Cmd" || part === "Command" ? "Meta" : part));
901
+ const modifiers = new Set();
902
+ for (const part of parts) {
903
+ const normalized = part === "Ctrl" ? "Control" : part === "Cmd" || part === "Command" ? "Meta" : part;
904
+ if (!["Alt", "Control", "Meta", "Shift"].includes(normalized)) throw new Error(`unsupported key modifier: ${part}`);
905
+ modifiers.add(normalized);
906
+ }
907
+ const namedKeys = new Set([
908
+ "Enter", "Tab", "Escape", "Backspace", "Delete", "ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown",
909
+ "Home", "End", "PageUp", "PageDown", "Space",
910
+ ]);
911
+ if (!namedKeys.has(rawKey) && [...rawKey].length !== 1) throw new Error(`unsupported key: ${rawKey}`);
466
912
  return {
467
913
  key: rawKey === "Space" ? " " : rawKey,
468
914
  altKey: modifiers.has("Alt"),
@@ -509,7 +955,8 @@
509
955
 
510
956
  function findElements(selector, { allowStaleRef = false } = {}) {
511
957
  if (selector.ref) {
512
- const element = refElements.get(String(selector.ref));
958
+ if (typeof selector.ref !== "string") return [];
959
+ const element = refElements.get(selector.ref);
513
960
  if (!element) return [];
514
961
  if (!element.isConnected) {
515
962
  if (allowStaleRef) return [];
@@ -702,12 +1149,25 @@
702
1149
  return "";
703
1150
  }
704
1151
 
1152
+ function finiteNumber(value, fallback) {
1153
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
1154
+ }
1155
+
705
1156
  function delay(ms) {
1157
+ const runtime = globalThis.chrome?.runtime;
1158
+ if (runtime && typeof runtime.sendMessage === "function") {
1159
+ const delayMs = Number.isFinite(ms) ? Math.ceil(ms) : 0;
1160
+ if (delayMs < 1 || delayMs > 250) return Promise.reject(new Error("browser action delay is invalid"));
1161
+ return Promise.resolve(runtime.sendMessage({ type: "machine_bridge_internal_delay", delay_ms: delayMs }))
1162
+ .then((response) => {
1163
+ if (response?.ok !== true) throw new Error("browser action timing service unavailable");
1164
+ });
1165
+ }
706
1166
  return new Promise((resolve) => { setTimeout(resolve, ms); });
707
1167
  }
708
1168
 
709
1169
  Object.defineProperty(globalThis, "__machineBridgePageAutomation", {
710
- value: Object.freeze({ inspect, prepareAction, action, fillForm, uploadFiles, checkWait }),
1170
+ value: Object.freeze({ version: PAGE_AUTOMATION_VERSION, inspect, documentState, historyAction, refIdentity, pointProbe, prepareAction, action, fillForm, uploadFiles, checkWait }),
711
1171
  configurable: true,
712
1172
  });
713
1173
  })();