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,82 +1,119 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import { existsSync } from "node:fs";
3
2
  import { join } from "node:path";
4
3
  import { createExclusiveFileSync, replaceFileAtomicallySync } from "./exclusive-file.mjs";
5
- import { readBoundedRegularFileSync } from "./secure-file.mjs";
6
- import { assertStateMaintenanceAvailable, ensureOwnerOnlyDir, ownerOnlyFile } from "./state.mjs";
7
- import { EXPECTED_EXTENSION_VERSION } from "./browser-extension-protocol.mjs";
4
+ import { inspectPathIfPresentSync, readBoundedRegularFileSync, retryTransientMultipleLinksSync } from "./secure-file.mjs";
5
+ import { acquireMaintenanceLock, assertStateMaintenanceAvailable } from "./state.mjs";
6
+ import { ensureOwnerOnlyDir, ownerOnlyFile } from "./secure-file.mjs";
7
+ import { createMonotonicDeadline } from "./monotonic-deadline.mjs";
8
8
 
9
9
  const DEFAULT_BROWSER_PORT = 39393;
10
10
  const PAIRING_FILE = "browser-bridge.json";
11
+ const PAIRING_SCHEMA_VERSION = 2;
12
+ const PAIRING_AUTH_VERSION = 2;
13
+ const TOKEN_PATTERN = /^[A-Za-z0-9_-]{32,100}$/;
11
14
 
12
- export async function loadOrCreatePairing(stateRoot) {
13
- if (!stateRoot) return { token: randomBytes(32).toString("base64url"), port: DEFAULT_BROWSER_PORT };
14
- assertStateMaintenanceAvailable(stateRoot);
15
+ export async function loadOrCreatePairing(stateRoot, options = {}) {
16
+ if (!stateRoot) return newPairing(DEFAULT_BROWSER_PORT);
15
17
  ensureOwnerOnlyDir(stateRoot);
16
18
  const file = join(stateRoot, PAIRING_FILE);
19
+ const inspect = options.inspectPathIfPresentSync || inspectPathIfPresentSync;
20
+ const existing = inspect(file, "browser pairing state");
21
+ if (existing) {
22
+ if (existing.isSymbolicLink() || !existing.isFile()) throw new Error("browser pairing state must be a regular file and not a symbolic link");
23
+ const current = readPublishedPairing(file);
24
+ if (!current.requiresMigration) { assertStateMaintenanceAvailable(stateRoot); return current.value; }
25
+ return migratePreviousPairing(stateRoot, file);
26
+ }
27
+ assertStateMaintenanceAvailable(stateRoot);
17
28
  for (let attempt = 0; attempt < 2; attempt += 1) {
18
- if (existsSync(file)) {
19
- ownerOnlyFile(file);
20
- let parsed;
21
- try { parsed = JSON.parse(readBoundedRegularFileSync(file, 64 * 1024).toString("utf8")); }
22
- catch { throw new Error("browser pairing state is not valid bounded JSON"); }
23
- if (!/^[A-Za-z0-9_-]{32,100}$/.test(parsed.token) || !Number.isInteger(parsed.port) || parsed.port < 1024 || parsed.port > 65535) {
24
- throw new Error("browser pairing state is invalid");
25
- }
26
- return parsed;
27
- }
28
- const value = { token: randomBytes(32).toString("base64url"), port: DEFAULT_BROWSER_PORT };
29
+ const value = newPairing(DEFAULT_BROWSER_PORT);
29
30
  try {
30
- createExclusiveFileSync(file, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
31
+ createExclusiveFileSync(file, pairingJson(value), { mode: 0o600 });
31
32
  ownerOnlyFile(file);
32
33
  return value;
33
34
  } catch (error) {
34
35
  if (error?.code !== "EEXIST") throw error;
36
+ const created = readPublishedPairing(file);
37
+ if (!created.requiresMigration) return created.value;
35
38
  }
36
39
  }
37
- throw new Error("browser pairing state could not be initialized");
40
+ return migratePreviousPairing(stateRoot, file);
38
41
  }
39
42
 
40
43
  export async function savePairing(stateRoot, value) {
41
44
  assertStateMaintenanceAvailable(stateRoot);
45
+ const normalized = normalizePairing(value);
42
46
  const file = join(stateRoot, PAIRING_FILE);
43
- replaceFileAtomicallySync(file, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
47
+ replaceFileAtomicallySync(file, pairingJson(normalized), { mode: 0o600 });
44
48
  ownerOnlyFile(file);
45
49
  }
46
50
 
47
- export function pairingHtml(port, token) {
48
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="machine-bridge-browser-pair" content="1"><meta name="machine-bridge-browser-port" content="${port}"><meta name="machine-bridge-browser-token" content="${token}"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Machine Bridge browser pairing</title></head><body><h1>Machine Bridge browser pairing</h1><p>Expected extension build: <strong>${EXPECTED_EXTENSION_VERSION}</strong>. Reload the unpacked extension after every Machine Bridge upgrade.</p><p>The installed extension reads pairing material from this loopback-only page and stores it in browser-local extension storage. It is not sent to any website.</p><p id="status">Waiting for the Machine Bridge extension.</p></body></html>`;
51
+ export function readBrowserPairing(stateRoot, options = {}) {
52
+ const current = readBrowserPairingEntry(stateRoot, options); if (!current) return null;
53
+ if (current.requiresMigration) throw new Error("browser pairing state requires migration before pairing"); return current.value;
54
+ }
55
+ export function readBrowserPairingPort(stateRoot, options = {}) { return readBrowserPairingEntry(stateRoot, options)?.value.port ?? null; }
56
+ function readBrowserPairingEntry(stateRoot, options) {
57
+ const file = join(stateRoot, PAIRING_FILE); const inspect = options.inspectPathIfPresentSync || inspectPathIfPresentSync;
58
+ const existing = inspect(file, "browser pairing state");
59
+ if (!existing) return null; if (existing.isSymbolicLink() || !existing.isFile()) throw new Error("browser pairing state must be a regular file and not a symbolic link");
60
+ return readPublishedPairing(file);
49
61
  }
50
62
 
51
- export function isAllowedExtensionOrigin(origin) {
52
- let parsed;
53
- try { parsed = new URL(String(origin || "")); } catch { return false; }
54
- return parsed.protocol === "chrome-extension:"
55
- && /^[a-p]{32}$/.test(parsed.hostname)
56
- && !parsed.username
57
- && !parsed.password
58
- && !parsed.port
59
- && (parsed.pathname === "" || parsed.pathname === "/")
60
- && !parsed.search
61
- && !parsed.hash;
63
+ async function migratePreviousPairing(stateRoot, file) {
64
+ const lock = await acquirePairingMigrationLock(stateRoot);
65
+ try {
66
+ const current = readPublishedPairing(file);
67
+ if (!current.requiresMigration) return current.value;
68
+ const value = { schemaVersion: PAIRING_SCHEMA_VERSION, pairingAuthVersion: PAIRING_AUTH_VERSION, extensionToken: token(), runtimeToken: current.value.runtimeToken || token(), port: current.value.port, migrationPending: true };
69
+ replaceFileAtomicallySync(file, pairingJson(value), { mode: 0o600 });
70
+ ownerOnlyFile(file);
71
+ return value;
72
+ } finally { lock.release(); }
62
73
  }
63
74
 
64
- export function isAllowedLoopbackHost(host, port) {
65
- const normalized = String(host || "").toLowerCase();
66
- return normalized === `127.0.0.1:${port}` || normalized === `localhost:${port}` || normalized === `[::1]:${port}`;
75
+ async function acquirePairingMigrationLock(stateRoot) {
76
+ const deadline = createMonotonicDeadline(5_000);
77
+ do {
78
+ const lock = acquireMaintenanceLock(stateRoot, { operation: "browser-pairing-migration" });
79
+ if (lock.acquired) return lock;
80
+ await new Promise((resolvePromise) => { setTimeout(resolvePromise, Math.min(50, Math.max(1, deadline.remainingMs()))); });
81
+ } while (!deadline.expired());
82
+ throw new Error("browser pairing migration could not acquire the state maintenance lock");
67
83
  }
68
84
 
69
- export function securityHeaders(contentType) {
70
- return {
71
- "content-type": contentType,
72
- "cache-control": "no-store",
73
- "content-security-policy": "default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'",
74
- "x-content-type-options": "nosniff",
75
- "referrer-policy": "no-referrer",
76
- };
85
+ function readPublishedPairing(file) {
86
+ return retryTransientMultipleLinksSync(() => readPairing(file));
77
87
  }
78
88
 
79
- export function sendJson(response, value) {
80
- response.writeHead(200, securityHeaders("application/json; charset=utf-8"));
81
- response.end(`${JSON.stringify(value)}\n`);
89
+ function readPairing(file) {
90
+ ownerOnlyFile(file);
91
+ let parsed;
92
+ try { parsed = JSON.parse(readBoundedRegularFileSync(file, 64 * 1024, "browser pairing state", { verifyPathIdentity: true, rejectMultipleLinks: true }).toString("utf8")); }
93
+ catch { throw new Error("browser pairing state is not valid bounded JSON"); }
94
+ if (parsed?.schemaVersion === PAIRING_SCHEMA_VERSION && parsed?.pairingAuthVersion === PAIRING_AUTH_VERSION) {
95
+ return { requiresMigration: false, value: normalizePairing(parsed) };
96
+ }
97
+ if (parsed?.schemaVersion === PAIRING_SCHEMA_VERSION && parsed?.pairingAuthVersion === undefined
98
+ && TOKEN_PATTERN.test(String(parsed?.extensionToken || ""))
99
+ && TOKEN_PATTERN.test(String(parsed?.runtimeToken || "")) && parsed.extensionToken !== parsed.runtimeToken && validPort(parsed?.port)) {
100
+ return { requiresMigration: true, value: { extensionToken: parsed.extensionToken, runtimeToken: parsed.runtimeToken, port: Number(parsed.port) } };
101
+ }
102
+ if (TOKEN_PATTERN.test(String(parsed?.token || "")) && validPort(parsed?.port)) {
103
+ return { requiresMigration: true, value: { extensionToken: parsed.token, runtimeToken: "", port: Number(parsed.port) } };
104
+ }
105
+ throw new Error("browser pairing state is invalid");
106
+ }
107
+
108
+ function normalizePairing(value) {
109
+ if (value?.schemaVersion !== PAIRING_SCHEMA_VERSION || value?.pairingAuthVersion !== PAIRING_AUTH_VERSION || !TOKEN_PATTERN.test(String(value.extensionToken || ""))
110
+ || !TOKEN_PATTERN.test(String(value.runtimeToken || "")) || value.extensionToken === value.runtimeToken || !validPort(value.port)
111
+ || typeof value.migrationPending !== "boolean") {
112
+ throw new Error("browser pairing state is invalid");
113
+ }
114
+ return { schemaVersion: PAIRING_SCHEMA_VERSION, pairingAuthVersion: PAIRING_AUTH_VERSION, extensionToken: String(value.extensionToken), runtimeToken: String(value.runtimeToken), port: Number(value.port), migrationPending: value.migrationPending };
82
115
  }
116
+ function newPairing(port) { return { schemaVersion: PAIRING_SCHEMA_VERSION, pairingAuthVersion: PAIRING_AUTH_VERSION, extensionToken: token(), runtimeToken: token(), port, migrationPending: false }; }
117
+ function token() { return randomBytes(32).toString("base64url"); }
118
+ function validPort(value) { return Number.isInteger(Number(value)) && Number(value) >= 1024 && Number(value) <= 65535; }
119
+ function pairingJson(value) { return `${JSON.stringify(value, null, 2)}\n`; }
@@ -1,67 +1,93 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { MAX_BROWSER_MESSAGE_BYTES } from "./browser-extension-protocol.mjs";
3
-
3
+ import { browserRequestResponseError, browserRequestTransportError, invalidBrowserResponseSettlement } from "./browser-request-settlement.mjs";
4
4
  export class BrowserRequestRegistry {
5
5
  constructor({ maximum = 32 } = {}) {
6
6
  this.maximum = maximum;
7
7
  this.pending = new Map();
8
8
  }
9
-
10
9
  request({ transport, method, params, timeoutSeconds, callId = "" }) {
11
10
  if (this.pending.size >= this.maximum) throw new Error("too many concurrent browser requests");
11
+ if (typeof method !== "string" || !/^[a-z][a-z0-9_]{0,63}$/.test(method)) throw new Error("browser request method is invalid");
12
+ if (!Number.isSafeInteger(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > 185) throw new Error("browser request timeout is invalid");
12
13
  const id = `browser_${randomBytes(18).toString("base64url")}`;
13
14
  return new Promise((resolvePromise, rejectPromise) => {
14
15
  const timeoutMs = timeoutSeconds * 1000;
15
16
  const timeout = setTimeout(() => {
17
+ const pending = this.pending.get(id);
18
+ if (!pending) return;
16
19
  this.pending.delete(id);
17
- sendCancelQuietly(transport, id);
18
- rejectPromise(new Error(`browser request timed out after ${timeoutSeconds}s`));
20
+ const terminationRequested = sendCancelQuietly(transport, id);
21
+ rejectPromise(browserRequestTransportError({
22
+ pending,
23
+ code: "timeout",
24
+ fallback: `browser request timed out after ${timeoutSeconds}s`,
25
+ terminationRequested,
26
+ }));
19
27
  }, timeoutMs);
20
28
  timeout.unref?.();
21
- this.pending.set(id, { resolve: resolvePromise, reject: rejectPromise, timeout, callId });
29
+ const pending = { resolve: resolvePromise, reject: rejectPromise, timeout, callId, method, dispatchAttempted: false };
30
+ this.pending.set(id, pending);
22
31
  try {
23
32
  const message = JSON.stringify({ type: "request", id, method, params, timeout_ms: timeoutMs });
24
33
  if (Buffer.byteLength(message) > MAX_BROWSER_MESSAGE_BYTES) throw new Error("browser request exceeds maximum message size");
34
+ pending.dispatchAttempted = true;
25
35
  transport.send(message);
26
36
  } catch (error) {
27
37
  clearTimeout(timeout);
28
38
  this.pending.delete(id);
29
- rejectPromise(error);
39
+ rejectPromise(pending.dispatchAttempted
40
+ ? browserRequestTransportError({ pending, code: "unavailable", fallback: error })
41
+ : error);
30
42
  }
31
43
  });
32
44
  }
33
-
34
45
  settle(message) {
35
46
  if (message?.type !== "response" || typeof message.id !== "string") return false;
36
47
  const pending = this.pending.get(message.id);
37
48
  if (!pending) return false;
38
49
  clearTimeout(pending.timeout);
39
50
  this.pending.delete(message.id);
40
- if (message.ok === false) pending.reject(new Error(String(message.error || "browser operation failed").slice(0, 2000)));
41
- else pending.resolve(message.result);
51
+ if (message.ok === false) {
52
+ if (typeof message.error !== "string" || !message.error || message.error.includes("\0") || message.error.length > 2000) {
53
+ pending.reject(invalidBrowserResponseSettlement(pending));
54
+ } else {
55
+ pending.reject(browserRequestResponseError(message.error, pending));
56
+ }
57
+ } else if (message.ok === true && message.result && typeof message.result === "object" && !Array.isArray(message.result)) {
58
+ pending.resolve(message.result);
59
+ } else {
60
+ pending.reject(invalidBrowserResponseSettlement(pending));
61
+ }
42
62
  return true;
43
63
  }
44
64
 
45
- cancelCall(callId, transport) {
65
+ cancelCall(callId, transport, reason = null) {
46
66
  if (!callId) return;
47
67
  for (const [id, pending] of this.pending) {
48
68
  if (pending.callId !== callId) continue;
49
69
  clearTimeout(pending.timeout);
50
70
  this.pending.delete(id);
51
- sendCancelQuietly(transport, id);
52
- pending.reject(new Error("browser request cancelled; a user-visible action may already have completed"));
71
+ const terminationRequested = sendCancelQuietly(transport, id);
72
+ const code = reason?.code === "timeout" ? "timeout" : "cancelled";
73
+ pending.reject(browserRequestTransportError({
74
+ pending,
75
+ code,
76
+ fallback: code === "timeout" ? "browser request timed out" : "browser request cancelled",
77
+ terminationRequested,
78
+ }));
53
79
  }
54
80
  }
55
81
 
56
82
  rejectAll(message) {
57
83
  for (const pending of this.pending.values()) {
58
84
  clearTimeout(pending.timeout);
59
- pending.reject(new Error(message));
85
+ pending.reject(browserRequestTransportError({ pending, code: "unavailable", fallback: message }));
60
86
  }
61
87
  this.pending.clear();
62
88
  }
63
89
  }
64
90
 
65
91
  function sendCancelQuietly(transport, id) {
66
- try { transport?.send(JSON.stringify({ type: "cancel", id })); } catch {}
92
+ try { transport?.send(JSON.stringify({ type: "cancel", id })); return true; } catch { return false; }
67
93
  }
@@ -0,0 +1,78 @@
1
+ // @ts-check
2
+ import { browserMethodMayMutate, browserPostDispatchTransportFailure } from "./browser-extension-protocol.mjs";
3
+ import { BridgeError } from "./errors.mjs";
4
+ /** @typedef {{method: unknown, dispatchAttempted?: boolean}} BrowserPendingSettlement */
5
+ /** @param {{pending?: BrowserPendingSettlement | null, code: string, fallback: unknown, terminationRequested?: boolean}} options */
6
+ export function browserRequestTransportError({ pending, code, fallback, terminationRequested = false }) {
7
+ const mutation = pending?.dispatchAttempted === true && browserMethodMayMutate(pending.method);
8
+ const message = mutation
9
+ ? browserPostDispatchTransportFailure(pending.method, boundedMessage(fallback))
10
+ : boundedMessage(fallback);
11
+ return new BridgeError(code, message, {
12
+ expose: true,
13
+ retryable: mutation ? false : code !== "cancelled",
14
+ ...(mutation ? {
15
+ details: {
16
+ request_delivery: "sent",
17
+ side_effects_started: "unknown",
18
+ termination_requested: terminationRequested === true,
19
+ effect_settlement: terminationRequested === true ? "pending" : "unknown",
20
+ },
21
+ } : {}),
22
+ });
23
+ }
24
+ /** @param {string} message @param {BrowserPendingSettlement | null | undefined} pending */
25
+ export function browserRequestResponseError(message, pending) {
26
+ const mutation = pending?.dispatchAttempted === true && browserMethodMayMutate(pending.method);
27
+ if (message === "browser broker is busy") return new BridgeError("limit_exceeded", message, { retryable: true });
28
+ if (["browser extension is not connected", "browser extension send failed"].includes(message)) return new BridgeError("unavailable", message, { retryable: true });
29
+ if (message.startsWith("browser mutation request may have been dispatched;")) {
30
+ return browserRequestTransportError({ pending, code: "unavailable", fallback: message });
31
+ }
32
+ if (message.startsWith("browser broker request timed out after dispatch")) {
33
+ return browserRequestTransportError({
34
+ pending,
35
+ code: "timeout",
36
+ fallback: message,
37
+ terminationRequested: mutation && message.includes("cancellation requested"),
38
+ });
39
+ }
40
+ if (message.startsWith("browser connection changed after request dispatch")) {
41
+ return browserRequestTransportError({ pending, code: "unavailable", fallback: message });
42
+ }
43
+ const partial = mutation && (
44
+ message.startsWith("browser mutation may have completed;")
45
+ || message.startsWith("trusted browser input may have been partially dispatched")
46
+ || message.startsWith("form submission failed after partial changes")
47
+ || message.startsWith("browser action may have been dispatched")
48
+ || message.startsWith("browser tab mutation may have been dispatched")
49
+ || message.startsWith("browser screenshot temporary tab activation may have been dispatched")
50
+ || message.startsWith("browser screenshot restoration may have been dispatched")
51
+ );
52
+ return partial
53
+ ? new BridgeError("execution_failed", message, {
54
+ expose: true,
55
+ retryable: false,
56
+ details: { side_effects_started: true, termination_requested: false, effect_settlement: "unknown" },
57
+ })
58
+ : new BridgeError("execution_failed", message, { expose: true, retryable: false });
59
+ }
60
+ /** @param {BrowserPendingSettlement | null | undefined} pending */
61
+ export function invalidBrowserResponseSettlement(pending) {
62
+ if (pending?.dispatchAttempted === true && browserMethodMayMutate(pending.method)) {
63
+ return browserRequestTransportError({
64
+ pending,
65
+ code: "execution_failed",
66
+ fallback: "browser extension response was malformed",
67
+ });
68
+ }
69
+ return new BridgeError("execution_failed", "browser extension response was malformed", {
70
+ expose: true,
71
+ retryable: false,
72
+ });
73
+ }
74
+ /** @param {unknown} value */
75
+ function boundedMessage(value) {
76
+ const message = value instanceof Error ? value.message : typeof value === "string" ? value : "browser transport failed";
77
+ return message ? message.slice(0, 2000) : "browser transport failed";
78
+ }
@@ -0,0 +1,74 @@
1
+ import { normalizeBrowserSelector, normalizeFormAction } from "./browser-command.mjs";
2
+
3
+ const MAX_FIELD_VALUE_CHARS = 128 * 1024;
4
+ const RESOURCE_NAME = /^[a-z][a-z0-9._-]{0,63}$/;
5
+
6
+ export function prepareBrowserFormField(input, index) {
7
+ if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error(`fields[${index}] must be an object`);
8
+ const allowed = new Set(["selector", "value", "value_resource", "action", "sensitive"]);
9
+ for (const key of Object.keys(input)) if (!allowed.has(key)) throw new Error(`unknown fields[${index}] property: ${key}`);
10
+ if (input.sensitive !== undefined && typeof input.sensitive !== "boolean") throw new Error(`fields[${index}].sensitive must be boolean`);
11
+ const action = input.action === undefined ? "fill" : normalizeFormAction(input.action);
12
+ const selector = normalizeBrowserSelector(input.selector, action);
13
+ const directValue = input.value === undefined ? null : boundedBrowserValue(input.value, `fields[${index}].value`);
14
+ const resourceName = input.value_resource === undefined ? "" : validateBrowserResource(input.value_resource);
15
+ if (directValue !== null && resourceName) throw new Error(`fields[${index}] value and value_resource are mutually exclusive`);
16
+ const hasValue = directValue !== null || Boolean(resourceName);
17
+ if (!hasValue && !["check", "uncheck", "click"].includes(action)) throw new Error(`fields[${index}] requires value or value_resource`);
18
+ if (hasValue && !["fill", "select"].includes(action)) throw new Error(`fields[${index}] value is not valid for action '${action}'`);
19
+ return { index, selector, action, sensitive: input.sensitive === true, value: directValue, resourceName };
20
+ }
21
+
22
+ export function normalizeUploadFilename(value, { derived = false } = {}) {
23
+ if (typeof value !== "string") {
24
+ if (derived) return "upload.bin";
25
+ throw new Error("filenames entries must be safe single-component filenames of at most 255 characters");
26
+ }
27
+ let name = value;
28
+ if (derived) name = name.replace(/[\u0000-\u001f\u007f/\\]+/g, "_").trim();
29
+ if (!name || name === "." || name === ".." || name.length > 255 || /[\u0000-\u001f\u007f/\\]/.test(name)) {
30
+ if (derived) return "upload.bin";
31
+ throw new Error("filenames entries must be safe single-component filenames of at most 255 characters");
32
+ }
33
+ return name;
34
+ }
35
+
36
+ export function normalizeMimeType(value) {
37
+ if (typeof value !== "string") throw new Error("mime_types entries must be valid media types");
38
+ const mime = value.trim().toLowerCase();
39
+ if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(mime) || mime.length > 200) {
40
+ throw new Error("mime_types entries must be valid media types");
41
+ }
42
+ return mime;
43
+ }
44
+
45
+ export function optionalStringArray(value, label, maxItems, maxLength) {
46
+ if (value === undefined) return [];
47
+ if (!Array.isArray(value) || value.length > maxItems) throw new Error(`${label} must be an array with at most ${maxItems} entries`);
48
+ return value.map((item, index) => {
49
+ if (typeof item !== "string" || item.includes("\0") || !item.length || item.length > maxLength) {
50
+ throw new Error(`${label}[${index}] must be a non-empty string of at most ${maxLength} characters`);
51
+ }
52
+ return item;
53
+ });
54
+ }
55
+
56
+ export async function resolveBrowserActionValue(args, readResourceText) {
57
+ let value = args.value === undefined ? null : boundedBrowserValue(args.value, "value");
58
+ if (args.value_resource !== undefined) {
59
+ if (value !== null) throw new Error("value and value_resource are mutually exclusive");
60
+ value = boundedBrowserValue(await readResourceText(validateBrowserResource(args.value_resource)), "value_resource");
61
+ }
62
+ return { value, source: args.value_resource !== undefined ? "local-resource" : value === null ? "none" : "mcp-argument" };
63
+ }
64
+
65
+ export function boundedBrowserValue(value, label) {
66
+ if (typeof value !== "string") throw new Error(`${label} must be a string`);
67
+ if (value.includes("\0") || value.length > MAX_FIELD_VALUE_CHARS) throw new Error(`${label} exceeds the maximum length or contains a NUL byte`);
68
+ return value;
69
+ }
70
+
71
+ export function validateBrowserResource(value) {
72
+ if (typeof value !== "string" || !RESOURCE_NAME.test(value)) throw new Error("value_resource is invalid");
73
+ return value;
74
+ }
@@ -0,0 +1,63 @@
1
+ import { BridgeError } from "./errors.mjs";
2
+
3
+ const TRUSTED_PAGE_ACTIONS = new Set(["click", "double_click", "hover", "press", "type_text"]);
4
+ const AMBIGUOUS_TRUSTED_INPUT = "trusted browser input may have been partially dispatched";
5
+ export const TRUSTED_INPUT_QUARANTINE_REASON = "browser_trusted_input_quarantined";
6
+ export const TRUSTED_INPUT_QUARANTINE_FALLBACK = "trusted_input_quarantined_after_ambiguous_failure";
7
+
8
+ export class BrowserTrustedInputHealth {
9
+ constructor({ logger = null } = {}) {
10
+ this.logger = logger || { event() {} };
11
+ this.quarantinedGeneration = 0;
12
+ }
13
+
14
+ status(bridge = {}) {
15
+ const connected = bridge.extensionConnected === true;
16
+ const generation = extensionGeneration(bridge);
17
+ const supported = bridge.extensionInfo?.capabilities?.includes("trusted_input") === true;
18
+ const quarantined = connected && generation > 0 && this.quarantinedGeneration === generation;
19
+ return Object.freeze({ connected, generation, supported, quarantined, available: connected && supported && !quarantined });
20
+ }
21
+
22
+ pageInputMode({ inputMode, action, bridge }) {
23
+ const health = this.status(bridge);
24
+ if (!health.quarantined || inputMode === "dom" || !TRUSTED_PAGE_ACTIONS.has(action)) {
25
+ return Object.freeze({ inputMode, fallback: false });
26
+ }
27
+ if (inputMode === "trusted") throw quarantinedError();
28
+ return Object.freeze({ inputMode: "dom", fallback: true });
29
+ }
30
+
31
+ assertTrustedAvailable(bridge) {
32
+ if (this.status(bridge).quarantined) throw quarantinedError();
33
+ }
34
+
35
+ noteAmbiguousFailure(error, bridge = {}) {
36
+ const message = String(error?.message || error || "");
37
+ if (!message.startsWith(AMBIGUOUS_TRUSTED_INPUT)) return false;
38
+ const health = this.status(bridge);
39
+ if (!health.connected || health.generation < 1) return false;
40
+ if (this.quarantinedGeneration !== health.generation) {
41
+ this.quarantinedGeneration = health.generation;
42
+ this.logger.event?.("warn", "browser.trusted_input.quarantined", {
43
+ reason: "ambiguous_input_settlement",
44
+ }, "trusted browser input quarantined for the current extension connection");
45
+ }
46
+ return true;
47
+ }
48
+ }
49
+
50
+ function extensionGeneration(bridge) {
51
+ const value = bridge.extensionGeneration;
52
+ if (Number.isSafeInteger(value) && value > 0) return value;
53
+ return bridge.extensionConnected === true ? 1 : 0;
54
+ }
55
+
56
+ function quarantinedError() {
57
+ return new BridgeError("unavailable",
58
+ "trusted browser input is quarantined for the current extension connection after an ambiguous dispatch",
59
+ {
60
+ retryable: false,
61
+ details: { reason: TRUSTED_INPUT_QUARANTINE_REASON, side_effects_started: false },
62
+ });
63
+ }
@@ -0,0 +1,21 @@
1
+ import { recordMatchesAuthorityRevocation } from "../shared/authority-revocation.mjs";
2
+
3
+ export function bindCallPrincipal(record, principal) {
4
+ if (!record || !principal || typeof principal !== "object") return false;
5
+ if (principal.kind !== "account") {
6
+ record.owner_kind = "local";
7
+ return true;
8
+ }
9
+ record.owner_kind = "account";
10
+ record.owner_account_id = String(principal.accountId || "");
11
+ record.owner_account_version = Number(principal.accountVersion);
12
+ record.owner_client_id = String(principal.clientId || "");
13
+ record.owner_family_id = String(principal.familyId || "");
14
+ return true;
15
+ }
16
+
17
+ export function callIdsForAuthority(records, revocation) {
18
+ return [...records]
19
+ .filter((record) => recordMatchesAuthorityRevocation(record, revocation))
20
+ .map((record) => record.id);
21
+ }
@@ -0,0 +1,45 @@
1
+ import { toolCallAdmission, toolCallCapacityConfig, toolCallCapacityUsage } from "../shared/tool-call-capacity.mjs";
2
+ import { BridgeError } from "./errors.mjs";
3
+
4
+ /** @param {unknown} maximumValue @param {unknown} reservedValue @param {Iterable<string>} [reservedTools] */
5
+ export function callCapacityConfig(maximumValue, reservedValue, reservedTools = []) {
6
+ const maximum = Number(maximumValue);
7
+ return toolCallCapacityConfig(
8
+ Number.isFinite(maximum) && maximum > 0 ? Math.floor(maximum) : 16,
9
+ reservedValue,
10
+ reservedTools,
11
+ );
12
+ }
13
+
14
+ /** @param {Map<string, any>} calls @param {ReturnType<typeof callCapacityConfig>} config @param {string} toolName */
15
+ export function assertCallCapacity(calls, config, toolName) {
16
+ const decision = toolCallAdmission(callSnapshot(calls), config, toolName);
17
+ if (decision.allowed) return;
18
+ if (decision.reason === "total_capacity") {
19
+ throw new BridgeError("limit_exceeded", `too many concurrent tool calls (${config.maximum})`, { retryable: true });
20
+ }
21
+ throw new BridgeError(
22
+ "limit_exceeded",
23
+ `ordinary tool-call capacity reached (${config.ordinaryMaximum}); control-plane capacity is reserved for diagnosis and recovery`,
24
+ { retryable: true },
25
+ );
26
+ }
27
+
28
+ /** @param {Map<string, any>} calls @param {ReturnType<typeof callCapacityConfig>} config */
29
+ export function callCapacitySnapshot(calls, config) {
30
+ const usage = toolCallCapacityUsage(callSnapshot(calls), config);
31
+ return {
32
+ maximum: usage.maximum,
33
+ ordinary_capacity: usage.ordinaryMaximum,
34
+ reserved_capacity: usage.reserved,
35
+ active_reserved: usage.activeReserved,
36
+ active_ordinary: usage.activeOrdinary,
37
+ };
38
+ }
39
+
40
+ /** @param {Map<string, any>} calls */
41
+ function callSnapshot(calls) {
42
+ const byTool = Object.create(null);
43
+ for (const call of calls.values()) byTool[call.tool] = (byTool[call.tool] || 0) + 1;
44
+ return { active: calls.size, byTool };
45
+ }
@@ -0,0 +1,54 @@
1
+ import { BridgeError } from "./errors.mjs";
2
+ import { createMonotonicDeadline } from "./monotonic-deadline.mjs";
3
+
4
+ const states = new WeakMap();
5
+
6
+ /** @typedef {{ calls: ReadonlyMap<string, unknown>, cancel: (callId: unknown, reason?: unknown, code?: string) => boolean }} DrainableCallRegistry */
7
+
8
+ export function assertCallRegistryOpen(registry) {
9
+ if (!stateFor(registry).draining) return;
10
+ throw new BridgeError("unavailable", "runtime is stopping and cannot accept new tool calls", { retryable: true });
11
+ }
12
+
13
+ /** @param {DrainableCallRegistry} registry @param {unknown} [reason] @param {number} [waitMs] */
14
+ export async function cancelAllCallsAndWait(registry, reason = "runtime stopped", waitMs = 5_000) {
15
+ const state = stateFor(registry);
16
+ state.draining = true;
17
+ for (const id of [...registry.calls.keys()]) registry.cancel(id, reason);
18
+ const deadline = createMonotonicDeadline(waitMs);
19
+ while (registry.calls.size && !deadline.expired()) {
20
+ await waitForRegistryChange(state, Math.max(1, deadline.remainingMs()));
21
+ }
22
+ if (registry.calls.size) {
23
+ throw new BridgeError("unavailable", "tool call shutdown did not settle before the runtime teardown deadline", {
24
+ retryable: true,
25
+ details: { active_calls: registry.calls.size },
26
+ });
27
+ }
28
+ }
29
+
30
+ export function notifyCallRegistryChanged(registry) {
31
+ for (const waiter of [...stateFor(registry).waiters]) waiter();
32
+ }
33
+
34
+ function stateFor(registry) {
35
+ let state = states.get(registry);
36
+ if (!state) {
37
+ state = { draining: false, waiters: new Set() };
38
+ states.set(registry, state);
39
+ }
40
+ return state;
41
+ }
42
+
43
+ function waitForRegistryChange(state, waitMs) {
44
+ return new Promise((resolvePromise) => {
45
+ let timer;
46
+ const done = () => {
47
+ if (timer) clearTimeout(timer);
48
+ state.waiters.delete(done);
49
+ resolvePromise();
50
+ };
51
+ state.waiters.add(done);
52
+ timer = setTimeout(done, waitMs);
53
+ });
54
+ }