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
  import serverMetadata from "../shared/server-metadata.json" with { type: "json" };
2
2
  import { projectMcpResult } from "../shared/result-projection.mjs";
3
+ import { completeResult } from "../shared/mcp-protocol.mjs";
3
4
  export {
4
5
  DEFAULT_POLICY_PROFILE,
5
6
  DEFAULT_POLICY_REVISION,
@@ -10,6 +11,7 @@ export {
10
11
  allToolNames,
11
12
  assertCanonicalFullPolicy,
12
13
  assertToolAllowed,
14
+ intersectPolicies,
13
15
  isCanonicalFullPolicy,
14
16
  normalizePolicy,
15
17
  policyAllowsAvailability,
@@ -26,13 +28,13 @@ export const MCP_PROTOCOL_VERSION = String(serverMetadata.protocolVersion);
26
28
  export const MCP_SUPPORTED_PROTOCOL_VERSIONS = Object.freeze(serverMetadata.supportedProtocolVersions.map((value) => String(value)));
27
29
  export const MCP_INSTRUCTIONS = Object.freeze(serverMetadata.instructions.map((value) => String(value))).join("\n");
28
30
 
29
- export function toolResult(value, isError = false) {
31
+ export function toolResult(value, isError = false, serverInfo) {
30
32
  const special = specialMcpResult(value);
31
- if (special) return { ...special, isError };
33
+ if (special) return completeResult({ ...special, isError }, serverInfo);
32
34
  const projection = projectMcpResult(value);
33
35
  const result = { content: [{ type: "text", text: projection.text }], isError };
34
- if (projection.structuredContent) result.structuredContent = projection.structuredContent;
35
- return result;
36
+ if (projection.hasStructuredContent) result.structuredContent = structuredClone(projection.structuredContent);
37
+ return completeResult(result, serverInfo);
36
38
  }
37
39
 
38
40
  export function rpcResult(id, result) {
@@ -50,7 +52,7 @@ function specialMcpResult(value) {
50
52
  const special = value && typeof value === "object" && !Array.isArray(value) ? value.$mcp : null;
51
53
  if (!special || typeof special !== "object" || !Array.isArray(special.content)) return null;
52
54
  const result = { content: structuredClone(special.content) };
53
- if (special.structuredContent && typeof special.structuredContent === "object" && !Array.isArray(special.structuredContent)) {
55
+ if (Object.prototype.hasOwnProperty.call(special, "structuredContent")) {
54
56
  result.structuredContent = structuredClone(special.structuredContent);
55
57
  }
56
58
  return result;
@@ -0,0 +1,53 @@
1
+ import { accessSync, constants as fsConstants, realpathSync, statSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { BridgeError } from "./errors.mjs";
5
+
6
+ export function resolveTrustedExecutable(options = {}) {
7
+ const platform = String(options.platform || process.platform);
8
+ const candidates = Array.isArray(options.candidates) ? options.candidates : [];
9
+ const deniedRoots = [options.workspace, options.stateRoot, options.runtimeDir, options.home || safeHome()]
10
+ .filter(Boolean).map((value) => canonicalOrResolved(String(value), options.realpath || realpathSync));
11
+ const realpath = options.realpath || realpathSync;
12
+ const stat = options.stat || statSync;
13
+ const access = options.access || accessSync;
14
+ for (const candidate of candidates) {
15
+ if (!path.isAbsolute(String(candidate || ""))) continue;
16
+ let canonical;
17
+ let info;
18
+ try {
19
+ canonical = realpath(String(candidate));
20
+ info = stat(canonical);
21
+ if (!info.isFile()) continue;
22
+ if (platform !== "win32") access(canonical, fsConstants.X_OK);
23
+ } catch { continue; }
24
+ if (platform !== "win32" && (Number(info.mode) & 0o022) !== 0) continue;
25
+ if (deniedRoots.some((root) => isWithin(canonical, root, platform))) continue;
26
+ return canonical;
27
+ }
28
+ const label = String(options.label || "required executable").replace(/[^A-Za-z0-9._ -]/g, "").trim() || "required executable";
29
+ const reason = String(options.reason || "trusted_executable_unavailable").replace(/[^A-Za-z0-9_-]/g, "_");
30
+ throw new BridgeError("unavailable", `trusted ${label} is unavailable`, { details: { reason, platform } });
31
+ }
32
+
33
+ export function createTrustedExecutableResolver(options = {}) {
34
+ const injected = typeof options.resolve === "function" ? options.resolve : null;
35
+ let cached = "";
36
+ return () => {
37
+ if (cached) return cached;
38
+ cached = String(injected ? injected() : resolveTrustedExecutable(options));
39
+ if (!path.isAbsolute(cached)) throw new BridgeError("unavailable", `trusted ${options.label || "executable"} is unavailable`);
40
+ return cached;
41
+ };
42
+ }
43
+
44
+ function canonicalOrResolved(value, realpath) {
45
+ try { return realpath(path.resolve(value)); } catch { return path.resolve(value); }
46
+ }
47
+ function isWithin(candidate, root, platform) {
48
+ const left = platform === "win32" ? candidate.toLowerCase() : candidate;
49
+ const right = platform === "win32" ? root.toLowerCase() : root;
50
+ const relative = path.relative(right, left);
51
+ return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
52
+ }
53
+ function safeHome() { try { return os.homedir(); } catch { return ""; } }
@@ -0,0 +1,34 @@
1
+ import path from "node:path";
2
+ import { createTrustedExecutableResolver, resolveTrustedExecutable } from "./trusted-executable.mjs";
3
+
4
+ export function resolveTrustedGitExecutable(options = {}) {
5
+ const platform = String(options.platform || process.platform);
6
+ return resolveTrustedExecutable({
7
+ ...options,
8
+ platform,
9
+ label: "Git executable",
10
+ reason: "trusted_git_executable_unavailable",
11
+ candidates: Array.isArray(options.candidates) ? options.candidates : defaultGitCandidates(platform, options.env || process.env),
12
+ });
13
+ }
14
+
15
+ export function createTrustedGitResolver(options = {}) {
16
+ const platform = String(options.platform || process.platform);
17
+ return createTrustedExecutableResolver({
18
+ ...options,
19
+ platform,
20
+ label: "Git executable",
21
+ reason: "trusted_git_executable_unavailable",
22
+ candidates: Array.isArray(options.candidates) ? options.candidates : defaultGitCandidates(platform, options.env || process.env),
23
+ });
24
+ }
25
+
26
+ function defaultGitCandidates(platform, env) {
27
+ if (platform === "win32") {
28
+ return [env.ProgramFiles, env["ProgramFiles(x86)"]]
29
+ .filter(Boolean)
30
+ .flatMap((root) => [path.join(root, "Git", "cmd", "git.exe"), path.join(root, "Git", "bin", "git.exe")]);
31
+ }
32
+ if (platform === "darwin") return ["/usr/bin/git", "/opt/homebrew/bin/git", "/usr/local/bin/git"];
33
+ return ["/usr/bin/git", "/bin/git", "/usr/local/bin/git"];
34
+ }
@@ -0,0 +1,24 @@
1
+ import path from "node:path";
2
+ import { resolveTrustedExecutable } from "./trusted-executable.mjs";
3
+
4
+ export function resolveTrustedGithubCli(options = {}) {
5
+ const platform = String(options.platform || process.platform);
6
+ return resolveTrustedExecutable({
7
+ ...options,
8
+ platform,
9
+ label: "GitHub CLI executable",
10
+ reason: "trusted_github_cli_unavailable",
11
+ candidates: Array.isArray(options.candidates) ? options.candidates : githubCliCandidates(platform, options.env || process.env),
12
+ });
13
+ }
14
+
15
+ function githubCliCandidates(platform, env) {
16
+ if (platform === "win32") {
17
+ return [
18
+ env.ProgramFiles && path.join(env.ProgramFiles, "GitHub CLI", "gh.exe"),
19
+ env.LOCALAPPDATA && path.join(env.LOCALAPPDATA, "Programs", "GitHub CLI", "gh.exe"),
20
+ ].filter(Boolean);
21
+ }
22
+ if (platform === "darwin") return ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"];
23
+ return ["/usr/bin/gh", "/usr/local/bin/gh", "/snap/bin/gh"];
24
+ }
@@ -0,0 +1,49 @@
1
+ // @ts-check
2
+
3
+ import { waitForStableActiveStatus } from "./service-convergence.mjs";
4
+
5
+ /** @typedef {{ statusAttempts?: unknown, statusDelayMs?: unknown, stableSamples?: unknown, sleep?: (milliseconds: number) => Promise<void> }} WindowsConvergenceOptions */
6
+ /** @typedef {Record<string, unknown> & { active?: boolean }} WindowsStatus */
7
+
8
+ /** @param {() => WindowsStatus | Promise<WindowsStatus>} readStatus @param {WindowsConvergenceOptions} [options] */
9
+ export async function stableWindowsStatus(readStatus, options = {}) {
10
+ if (typeof readStatus !== "function") throw new TypeError("Windows status reader is required");
11
+ return waitForStableActiveStatus(readStatus, {
12
+ attempts: boundedPositiveInteger(options.statusAttempts, 10),
13
+ delayMs: boundedPositiveInteger(options.statusDelayMs, 100),
14
+ stableSamples: boundedPositiveInteger(options.stableSamples, 5),
15
+ sleep: typeof options.sleep === "function" ? options.sleep : undefined,
16
+ });
17
+ }
18
+
19
+ /** @param {() => WindowsStatus | Promise<WindowsStatus>} readStatus @param {(status: WindowsStatus) => boolean} predicate @param {WindowsConvergenceOptions} [options] */
20
+ export async function waitForWindowsStatus(readStatus, predicate, options = {}) {
21
+ if (typeof readStatus !== "function") throw new TypeError("Windows status reader is required");
22
+ if (typeof predicate !== "function") throw new TypeError("Windows status predicate is required");
23
+ const attempts = boundedPositiveInteger(options.statusAttempts, 10);
24
+ const delayMs = boundedPositiveInteger(options.statusDelayMs, 100);
25
+ const sleep = typeof options.sleep === "function"
26
+ ? options.sleep
27
+ : (/** @type {number} */ milliseconds) => new Promise(resolve => { setTimeout(resolve, milliseconds); });
28
+ let status = await readStatus();
29
+ for (let index = 1; index < attempts && !predicate(status); index += 1) {
30
+ await sleep(delayMs);
31
+ status = await readStatus();
32
+ }
33
+ return status;
34
+ }
35
+
36
+ /** @param {WindowsConvergenceOptions} [options] */
37
+ export function windowsStatusWaitOptions(options = {}) {
38
+ return {
39
+ attempts: boundedPositiveInteger(options.statusAttempts, 10),
40
+ delayMs: boundedPositiveInteger(options.statusDelayMs, 100),
41
+ sleep: typeof options.sleep === "function" ? options.sleep : undefined,
42
+ };
43
+ }
44
+
45
+ /** @param {unknown} value @param {number} fallback */
46
+ export function boundedPositiveInteger(value, fallback) {
47
+ const number = Number(value);
48
+ return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
49
+ }
@@ -1,11 +1,8 @@
1
1
  import { waitForInactiveStatus } from "./service-convergence.mjs";
2
- import {
3
- windowsLauncherPath, windowsTaskAction, writeWindowsLauncher,
4
- } from "./windows-launcher.mjs";
5
- export {
6
- windowsBatchArgument, windowsCommandLineArgument, windowsLauncherContent,
7
- windowsLauncherPath, windowsTaskAction, writeWindowsLauncher,
8
- } from "./windows-launcher.mjs";
2
+ import { stableWindowsStatus, waitForWindowsStatus, windowsStatusWaitOptions } from "./windows-service-convergence.mjs";
3
+ import { windowsLauncherPath, windowsTaskAction, writeWindowsLauncher } from "./windows-launcher.mjs";
4
+ import { removeServiceDefinitionIfCurrent, snapshotServiceDefinition } from "./service-definition.mjs";
5
+ export { windowsBatchArgument, windowsCommandLineArgument, windowsLauncherContent, windowsLauncherPath, windowsTaskAction, writeWindowsLauncher } from "./windows-launcher.mjs";
9
6
 
10
7
  export const WINDOWS_TASK = "MachineBridgeMCP";
11
8
  const WINDOWS_STATUS_SCRIPT = [
@@ -28,7 +25,9 @@ export async function installWindowsTask(spec, logger = console, options = {}) {
28
25
  "/RL", "LIMITED",
29
26
  "/F",
30
27
  ]);
31
- const status = await waitForWindowsStatus(value => value.installed === true, { ...options, run });
28
+ const status = await waitForWindowsStatus(
29
+ () => statusWindowsTask({ ...options, run }), value => value.installed === true, options,
30
+ );
32
31
  const ok = create?.code === 0 && status.installed === true;
33
32
  if (ok) logger.info?.("Windows Scheduled Task installed for user logon");
34
33
  else logger.warn?.("Windows Scheduled Task installation failed", { reason: windowsServiceFailureReason(create, status) });
@@ -50,24 +49,46 @@ export async function startWindowsTask(logger = console, options = {}) {
50
49
  const before = await statusWindowsTask({ ...options, run });
51
50
  if (before.installed === null) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, reason: "task_status_unavailable", status: before };
52
51
  if (before.installed === false) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, reason: "not_installed", status: before };
52
+ if (before.active === true) {
53
+ const existing = await stableWindowsStatus(() => statusWindowsTask({ ...options, run }), options);
54
+ if (existing.stable) {
55
+ logger.info?.("Windows Scheduled Task is already running");
56
+ return {
57
+ ok: true, provider: "schtasks", task: WINDOWS_TASK, installed: true,
58
+ active_before: true, active: true, already_running: true, reason: "already_running", status: existing.status,
59
+ };
60
+ }
61
+ }
53
62
  const command = await run("schtasks", ["/Run", "/TN", WINDOWS_TASK]);
54
63
  const after = await waitForWindowsStatus(
55
- status => status.active === true || completedSince(before, status),
56
- { ...options, run },
64
+ () => statusWindowsTask({ ...options, run }),
65
+ status => status.active === true || completedSince(before, status), options,
57
66
  );
58
- const completed = completedSince(before, after);
59
- const ok = after.active === true || completed;
67
+ const stability = after.active === true
68
+ ? await stableWindowsStatus(() => statusWindowsTask({ ...options, run }), options)
69
+ : { stable: false, status: after };
70
+ const observed = stability.status;
71
+ const completed = completedSince(before, observed);
72
+ const ok = stability.stable;
60
73
  if (ok) logger.info?.("Windows Scheduled Task started");
61
- else logger.warn?.("Windows Scheduled Task did not reach a running or successful completed state");
74
+ else logger.warn?.("Windows Scheduled Task did not remain active after the start request");
62
75
  return {
63
76
  ok,
64
77
  provider: "schtasks",
65
78
  task: WINDOWS_TASK,
66
79
  command,
67
- status: after,
68
- reason: after.active === true ? "started" : completed ? "completed" : "start_not_observed",
80
+ status: observed,
81
+ active_before: false,
82
+ active: observed?.active === true && stability.stable,
83
+ reason: stability.stable ? "started" : completed ? "completed_without_persistence" : "start_not_stable",
69
84
  };
70
85
  }
86
+ export async function restartWindowsTask(logger = console, options = {}) {
87
+ const stopped = await stopWindowsTask(logger, options);
88
+ if (!stopped.ok) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, reason: "stop_failed", stop: stopped };
89
+ const started = await startWindowsTask(logger, options);
90
+ return { ...started, restarted: started.ok === true, stop: stopped };
91
+ }
71
92
 
72
93
  export async function stopWindowsTask(logger = console, options = {}) {
73
94
  const run = requiredRun(options.run);
@@ -81,6 +102,7 @@ export async function stopWindowsTask(logger = console, options = {}) {
81
102
  installed: before.installed,
82
103
  active_before: false,
83
104
  active: false,
105
+ restore_required: false,
84
106
  already_stopped: true,
85
107
  reason: before.installed ? "already_stopped" : "not_installed",
86
108
  status: before,
@@ -101,6 +123,7 @@ export async function stopWindowsTask(logger = console, options = {}) {
101
123
  installed: after?.installed !== false,
102
124
  active_before: true,
103
125
  active: after?.active !== false,
126
+ restore_required: ok,
104
127
  already_stopped: false,
105
128
  command,
106
129
  status: after,
@@ -110,17 +133,25 @@ export async function stopWindowsTask(logger = console, options = {}) {
110
133
 
111
134
  export async function uninstallWindowsTask(logger = console, options = {}) {
112
135
  const run = requiredRun(options.run);
136
+ const launcher = options.stateRoot ? windowsLauncherPath(options.stateRoot) : "";
137
+ const launcherIdentity = launcher ? snapshotServiceDefinition(launcher, "Windows service launcher") : null;
113
138
  const stopped = await stopWindowsTask(logger, { ...options, run });
114
139
  if (!stopped.ok) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, reason: "stop_failed" };
115
140
  const command = await run("schtasks", ["/Delete", "/TN", WINDOWS_TASK, "/F"]);
116
141
  const status = await waitForWindowsStatus(
117
- value => value.installed === false,
118
- { ...options, run },
142
+ () => statusWindowsTask({ ...options, run }), value => value.installed === false, options,
119
143
  );
120
- const ok = status.installed === false;
121
- if (ok) logger.info?.("Windows Scheduled Task removed");
122
- else logger.warn?.("Windows Scheduled Task removal could not be verified");
123
- return { ok, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, command, status, reason: ok ? "removed" : "removal_not_observed" };
144
+ const taskRemoved = status.installed === false;
145
+ if (!taskRemoved) {
146
+ logger.warn?.("Windows Scheduled Task removal could not be verified");
147
+ return { ok: false, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, command, status, reason: "removal_not_observed" };
148
+ }
149
+ if (launcher && !removeServiceDefinitionIfCurrent(launcher, launcherIdentity, "Windows service launcher")) {
150
+ logger.warn?.("Windows service launcher changed during removal; the replacement was retained.");
151
+ return { ok: false, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, command, status, reason: "launcher_changed" };
152
+ }
153
+ logger.info?.("Windows Scheduled Task removed");
154
+ return { ok: true, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, command, status, reason: "removed" };
124
155
  }
125
156
 
126
157
  export async function statusWindowsTask(options = {}) {
@@ -168,32 +199,8 @@ function windowsServiceFailureReason(command, status) {
168
199
  return "task_installation_unverified";
169
200
  }
170
201
 
171
- async function waitForWindowsStatus(predicate, options) {
172
- const attempts = boundedPositiveInteger(options.statusAttempts, 10);
173
- const delayMs = boundedPositiveInteger(options.statusDelayMs, 100);
174
- const sleep = typeof options.sleep === "function" ? options.sleep : milliseconds => new Promise(resolve => { setTimeout(resolve, milliseconds); });
175
- let status = await statusWindowsTask(options);
176
- for (let index = 1; index < attempts && !predicate(status); index += 1) {
177
- await sleep(delayMs);
178
- status = await statusWindowsTask(options);
179
- }
180
- return status;
181
- }
182
-
183
- function windowsStatusWaitOptions(options) {
184
- return {
185
- attempts: boundedPositiveInteger(options.statusAttempts, 10),
186
- delayMs: boundedPositiveInteger(options.statusDelayMs, 100),
187
- sleep: typeof options.sleep === "function" ? options.sleep : undefined,
188
- };
189
- }
190
202
 
191
203
  function requiredRun(run) {
192
204
  if (typeof run !== "function") throw new Error("Windows service command runner is required");
193
205
  return run;
194
206
  }
195
-
196
- function boundedPositiveInteger(value, fallback) {
197
- const number = Number(value);
198
- return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
199
- }
@@ -0,0 +1,102 @@
1
+ import { createHmac } from "node:crypto";
2
+ import { lstatSync, readdirSync, realpathSync } from "node:fs";
3
+ import path, { resolve } from "node:path";
4
+ import { publicDeviceJwkJson } from "./device-identity.mjs";
5
+ import { readBoundedRegularFileSync } from "./secure-file.mjs";
6
+ import { deploymentDeviceIdentity } from "./state.mjs";
7
+ import { packageRoot } from "./package-identity.mjs";
8
+
9
+ const MAX_WORKER_DEPLOY_SOURCE_BYTES = 16 * 1024 * 1024;
10
+ const REQUIRED_DEPLOYMENT_PATHS = Object.freeze([
11
+ "src/worker",
12
+ "src/shared",
13
+ "wrangler.jsonc",
14
+ "tsconfig.json",
15
+ ]);
16
+
17
+ export function workerDeploymentFingerprint(state, options = {}) {
18
+ const source = workerDeployHashFiles(options.packageRoot || packageRoot);
19
+ const keyMaterial = [
20
+ publicDeviceJwkJson(deploymentDeviceIdentity(state)),
21
+ String(state.worker.oauthTokenVersion || ""),
22
+ ].join("\0");
23
+ const fingerprint = createHmac("sha256", keyMaterial);
24
+ addFingerprintField(fingerprint, "mbm-worker-deploy-v5");
25
+ addFingerprintField(fingerprint, String(state.worker.name || ""));
26
+ addFingerprintField(fingerprint, String(source.files.length));
27
+ for (const file of source.files) {
28
+ addFingerprintField(fingerprint, path.relative(source.root, file).replaceAll(path.sep, "/"));
29
+ addFingerprintField(fingerprint, readBoundedRegularFileSync(
30
+ file,
31
+ MAX_WORKER_DEPLOY_SOURCE_BYTES,
32
+ "Worker deployment source",
33
+ { verifyPathIdentity: true, rejectMultipleLinks: true },
34
+ ));
35
+ }
36
+ return fingerprint.digest("hex");
37
+ }
38
+
39
+ function addFingerprintField(hash, value) {
40
+ const bytes = Buffer.isBuffer(value) ? value : Buffer.from(String(value), "utf8");
41
+ const length = Buffer.alloc(8);
42
+ length.writeBigUInt64BE(BigInt(bytes.length));
43
+ hash.update(length);
44
+ hash.update(bytes);
45
+ }
46
+
47
+ function workerDeployHashFiles(root) {
48
+ const canonicalRoot = requireRealDeploymentRoot(root);
49
+ const files = [];
50
+ for (const item of REQUIRED_DEPLOYMENT_PATHS) collectRequiredHashPath(canonicalRoot, item, files);
51
+ return Object.freeze({ root: canonicalRoot, files: files.sort() });
52
+ }
53
+
54
+ function requireRealDeploymentRoot(root) {
55
+ const target = resolve(root);
56
+ let info;
57
+ try { info = lstatSync(target); }
58
+ catch (error) {
59
+ if (error?.code === "ENOENT") throw new Error(`Worker deployment package root is missing: ${target}`);
60
+ throw error;
61
+ }
62
+ if (info.isSymbolicLink() || !info.isDirectory()) {
63
+ throw new Error(`Worker deployment package root must be a real directory: ${target}`);
64
+ }
65
+ return realpathSync(target);
66
+ }
67
+
68
+ function collectRequiredHashPath(root, relativePath, out) {
69
+ let current = root;
70
+ const parts = relativePath.split("/");
71
+ for (let index = 0; index < parts.length; index += 1) {
72
+ current = resolve(current, parts[index]);
73
+ const info = requiredPathInfo(current);
74
+ if (info.isSymbolicLink()) throw new Error(`Worker deployment source must not be a symbolic link: ${current}`);
75
+ if (index < parts.length - 1 && !info.isDirectory()) {
76
+ throw new Error(`Worker deployment source ancestor must be a real directory: ${current}`);
77
+ }
78
+ }
79
+ collectHashFiles(current, out);
80
+ }
81
+
82
+ function requiredPathInfo(target) {
83
+ try { return lstatSync(target); }
84
+ catch (error) {
85
+ if (error?.code === "ENOENT") throw new Error(`Worker deployment required source is missing: ${target}`);
86
+ throw error;
87
+ }
88
+ }
89
+
90
+ function collectHashFiles(target, out) {
91
+ const info = requiredPathInfo(target);
92
+ if (info.isSymbolicLink()) throw new Error(`Worker deployment source must not be a symbolic link: ${target}`);
93
+ if (info.isFile()) {
94
+ if (/\.(ts|js|mjs|json|jsonc|yaml|yml|lock)$/.test(target)) out.push(target);
95
+ return;
96
+ }
97
+ if (!info.isDirectory()) throw new Error(`Worker deployment source must be a regular file or directory: ${target}`);
98
+ for (const entry of readdirSync(target, { withFileTypes: true })) {
99
+ if (entry.name === "node_modules" || entry.name === ".wrangler" || entry.name.endsWith(".d.ts")) continue;
100
+ collectHashFiles(resolve(target, entry.name), out);
101
+ }
102
+ }
@@ -1,10 +1,11 @@
1
- import { createHmac } from "node:crypto";
2
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
- import path, { resolve } from "node:path";
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
4
3
  import { runWrangler } from "./shell.mjs";
5
- import { packageRoot, saveState } from "./state.mjs";
4
+ import { saveState } from "./state.mjs";
5
+ import { packageRoot } from "./package-identity.mjs";
6
6
  import { withWorkerSecretsFile } from "./worker-secret-file.mjs";
7
- import { publicDeviceJwkJson } from "./device-identity.mjs";
7
+ import { workerDeploymentFingerprint } from "./worker-deployment-fingerprint.mjs";
8
+ export { workerDeploymentFingerprint } from "./worker-deployment-fingerprint.mjs";
8
9
  import {
9
10
  normalizeWorkerOrigin,
10
11
  retryWorkerHealth,
@@ -12,11 +13,24 @@ import {
12
13
  workerHealthUserReason,
13
14
  } from "./worker-health.mjs";
14
15
 
16
+ const DEFAULT_DEPLOYMENT_HEALTH_ATTEMPTS = 20;
17
+
18
+ async function ensureWranglerAuthenticated({ args, runWranglerFn, stateRoot, logger }) {
19
+ const whoami = await runWranglerFn(["whoami"], { capture: true, allowFailure: true, stateRoot });
20
+ if (whoami.code === 0) return;
21
+ if (args.json) throw workerAuthenticationRequiredError();
22
+ logger.info?.("Wrangler is not logged in; opening Cloudflare login");
23
+ await runWranglerFn(["login"], { stateRoot });
24
+ const verified = await runWranglerFn(["whoami"], { capture: true, allowFailure: true, stateRoot });
25
+ if (verified.code !== 0) throw workerAuthenticationRequiredError();
26
+ }
27
+
15
28
  export async function ensureWorkerDeployment(state, args = {}, options = {}) {
16
29
  const logger = options.logger || console;
17
30
  const expectedVersion = options.expectedVersion || currentPackageVersion(options.packageRoot || packageRoot);
18
31
  const desiredHash = workerDeploymentFingerprint(state, { packageRoot: options.packageRoot || packageRoot });
19
32
  const runWranglerFn = options.runWrangler || runWrangler;
33
+ const wranglerStateRoot = options.stateRoot || state.paths?.stateRoot;
20
34
  const saveStateFn = options.saveState || saveState;
21
35
  const retryHealthFn = options.retryHealth || retryWorkerHealth;
22
36
  const withSecretsFileFn = options.withSecretsFile || withWorkerSecretsFile;
@@ -24,35 +38,37 @@ export async function ensureWorkerDeployment(state, args = {}, options = {}) {
24
38
  const complete = hasCompleteWorkerState(state.worker);
25
39
 
26
40
  if (!args.forceWorker && !args.rotateSecrets && complete && state.worker.deployHash === desiredHash) {
27
- const health = await retryHealthFn(state.worker.url, expectedVersion, options.existingHealthAttempts || 2, healthOptions);
41
+ const recordedCurrentDeployment = state.worker.deployedVersion === expectedVersion;
42
+ const attempts = recordedCurrentDeployment
43
+ ? (options.recordedDeploymentHealthAttempts ?? DEFAULT_DEPLOYMENT_HEALTH_ATTEMPTS)
44
+ : (options.existingHealthAttempts ?? 2);
45
+ const health = await retryHealthFn(state.worker.url, expectedVersion, attempts, healthOptions);
28
46
  if (health.ok) {
29
- logger.success?.("Worker unchanged and healthy", { url: state.worker.url });
47
+ logger.success?.("Worker unchanged and healthy");
30
48
  logger.debug?.("Worker health route", { network_route: health.networkRoute || "unknown" });
31
49
  return state.worker;
32
50
  }
51
+ logger.debug?.("Worker health check detail", { health_error: health.error, network_route: health.networkRoute || "unknown" });
52
+ if (recordedCurrentDeployment) {
53
+ throw workerVerificationError(health.error, { deploymentSucceeded: false, recordedCurrentDeployment: true });
54
+ }
33
55
  if (!workerHealthRequiresRedeploy(health.error)) {
34
- logger.debug?.("Worker health check detail", { health_error: health.error, network_route: health.networkRoute || "unknown" });
35
56
  throw workerVerificationError(health.error, { deploymentSucceeded: false });
36
57
  }
37
58
  logger.warn?.("Recorded Worker is stale; redeploying the same Worker", { reason: workerHealthUserReason(health.error) });
38
- logger.debug?.("Worker health check detail", { health_error: health.error, network_route: health.networkRoute || "unknown" });
39
59
  }
40
60
 
41
61
  logger.info?.("Checking Cloudflare Wrangler login");
42
- const whoami = await runWranglerFn(["whoami"], { capture: true, allowFailure: true });
43
- if (whoami.code !== 0) {
44
- logger.info?.("Wrangler is not logged in; opening Cloudflare login");
45
- await runWranglerFn(["login"]);
46
- }
62
+ await ensureWranglerAuthenticated({ args, runWranglerFn, stateRoot: wranglerStateRoot, logger });
47
63
 
48
- logger.info?.("Deploying Cloudflare Worker", { name: state.worker.name });
64
+ logger.info?.("Deploying Cloudflare Worker");
49
65
  const deploy = await withSecretsFileFn(state, secretFile => runWranglerFn([
50
66
  "deploy",
51
67
  "--name", state.worker.name,
52
68
  "--minify",
53
69
  "--keep-vars",
54
70
  "--secrets-file", secretFile,
55
- ], { capture: true }));
71
+ ], { capture: true, stateRoot: wranglerStateRoot }));
56
72
 
57
73
  const detectedUrl = extractWorkerUrl(deploy.stdout, state.worker.name) || extractWorkerUrl(deploy.stderr, state.worker.name);
58
74
  const recordedUrl = workerUrlMatchesName(state.worker.url, state.worker.name) ? state.worker.url : "";
@@ -68,33 +84,16 @@ export async function ensureWorkerDeployment(state, args = {}, options = {}) {
68
84
  state.worker.updatedAt = new Date().toISOString();
69
85
  saveStateFn(state);
70
86
 
71
- const health = await retryHealthFn(state.worker.url, expectedVersion, options.deploymentHealthAttempts || 8, healthOptions);
87
+ const health = await retryHealthFn(state.worker.url, expectedVersion, options.deploymentHealthAttempts ?? DEFAULT_DEPLOYMENT_HEALTH_ATTEMPTS, healthOptions);
72
88
  if (!health.ok) {
73
89
  logger.debug?.("Worker post-deployment health detail", { health_error: health.error, network_route: health.networkRoute || "unknown" });
74
90
  throw workerVerificationError(health.error, { deploymentSucceeded: true });
75
91
  }
76
- logger.success?.("Worker ready", { url: state.worker.url, version: health.version });
92
+ logger.success?.("Worker ready", { version: health.version });
77
93
  logger.debug?.("Worker health route", { network_route: health.networkRoute || "unknown" });
78
94
  return state.worker;
79
95
  }
80
96
 
81
- export function workerDeploymentFingerprint(state, options = {}) {
82
- const root = resolve(options.packageRoot || packageRoot);
83
- const keyMaterial = [
84
- String(state.worker.accountAdminSecret || ""),
85
- publicDeviceJwkJson(state.worker.deviceIdentity),
86
- String(state.worker.oauthTokenVersion || ""),
87
- ].join("\0");
88
- const fingerprint = createHmac("sha256", keyMaterial);
89
- fingerprint.update("mbm-worker-deploy-v4");
90
- fingerprint.update(String(state.worker.name || ""));
91
- for (const file of workerDeployHashFiles(root)) {
92
- fingerprint.update(path.relative(root, file));
93
- fingerprint.update(readFileSync(file, "utf8"));
94
- }
95
- return fingerprint.digest("hex");
96
- }
97
-
98
97
  export function extractWorkerUrl(text = "", workerName = "") {
99
98
  const candidates = [...String(text).matchAll(/https:\/\/[^\s"'<>]+/g)]
100
99
  .map((match) => match[0].replace(/[),.;:!?]+$/, ""));
@@ -119,37 +118,17 @@ export function workerUrlMatchesName(workerUrl, workerName) {
119
118
  }
120
119
 
121
120
  function hasCompleteWorkerState(worker = {}) {
122
- return Boolean(worker.url && worker.mcpServerUrl && worker.accountAdminSecret && worker.deviceIdentity && worker.oauthTokenVersion && worker.name);
121
+ return Boolean(worker.url && worker.mcpServerUrl && worker.deviceIdentity && worker.oauthTokenVersion && worker.name);
123
122
  }
124
123
 
125
- function workerDeployHashFiles(root) {
126
- const files = [];
127
- for (const item of ["src/worker", "src/shared", "wrangler.jsonc", "tsconfig.json"]) {
128
- collectHashFiles(resolve(root, item), files);
129
- }
130
- return files.sort();
131
- }
132
-
133
- function collectHashFiles(target, out) {
134
- if (!existsSync(target)) return;
135
- const info = statSync(target);
136
- if (info.isFile()) {
137
- if (/\.(ts|js|mjs|json|jsonc|yaml|yml|lock)$/.test(target)) out.push(target);
138
- return;
139
- }
140
- if (!info.isDirectory()) return;
141
- for (const entry of readdirSync(target, { withFileTypes: true })) {
142
- if (entry.name === "node_modules" || entry.name === ".wrangler" || entry.name.endsWith(".d.ts")) continue;
143
- collectHashFiles(resolve(target, entry.name), out);
144
- }
145
- }
146
-
147
- function workerVerificationError(reason, { deploymentSucceeded }) {
124
+ function workerVerificationError(reason, { deploymentSucceeded, recordedCurrentDeployment = false }) {
148
125
  const readable = workerHealthUserReason(reason);
149
126
  const guidance = workerVerificationGuidance(reason);
150
127
  const message = deploymentSucceeded
151
128
  ? `Cloudflare reported the Worker deployment succeeded, but ${readable}. The deployment fingerprint was saved, so retrying will verify the same Worker instead of deploying again. ${guidance}`
152
- : `The recorded Worker could not be verified because ${readable}. No deployment was attempted. ${guidance}`;
129
+ : recordedCurrentDeployment
130
+ ? `The current Worker deployment fingerprint is already recorded, but ${readable}. No deployment was attempted because duplicating the recorded deployment is unsafe; use --force-worker only after diagnosis. ${guidance}`
131
+ : `The recorded Worker could not be verified because ${readable}. No deployment was attempted. ${guidance}`;
153
132
  const error = new Error(message);
154
133
  error.code = "worker_health_unverified";
155
134
  error.healthError = reason;
@@ -170,6 +149,15 @@ function workerVerificationGuidance(reason) {
170
149
  return "Run machine-mcp doctor and inspect the health endpoint before forcing another deployment.";
171
150
  }
172
151
 
152
+ function workerAuthenticationRequiredError() {
153
+ const error = new Error(
154
+ "Cloudflare Wrangler is not authenticated; JSON mode will not start an interactive login. Complete Wrangler authentication in an ordinary owner terminal before retrying.",
155
+ );
156
+ error.code = "worker_authentication_required";
157
+ error.sideEffectsStarted = false;
158
+ return error;
159
+ }
160
+
173
161
  function currentPackageVersion(root) {
174
162
  const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8"));
175
163
  return String(pkg.version);