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
@@ -0,0 +1,57 @@
1
+ // @ts-check
2
+
3
+ import { createSnapshotBudget, processGroupMembers } from "./process-tree-snapshot.mjs";
4
+ export { DEFAULT_PROCESS_OWNERSHIP_CHECK_BUDGET_MS } from "./process-tree-snapshot.mjs";
5
+ /** @typedef {import("./process-tree-ownership-types.d.ts").ChildProcessIdentity} ChildProcessIdentity */
6
+ /** @typedef {import("./process-tree-ownership-types.d.ts").ProcessOwnershipMember} ProcessOwnershipMember */
7
+ /** @typedef {import("./process-tree-ownership-types.d.ts").ProcessOwnershipSnapshot} ProcessOwnershipSnapshot */
8
+ /** @typedef {import("./process-tree-ownership-types.d.ts").ProcessOwnershipOptions} ProcessOwnershipOptions */
9
+
10
+ export async function captureProcessTreeOwnership(/** @type {ChildProcessIdentity} */ child, /** @type {ProcessOwnershipOptions} */ options = {}) {
11
+ const pid = positivePid(child?.pid);
12
+ const platform = String(options.platform || process.platform);
13
+ if (!pid) return { platform, pid: 0, members: [] };
14
+ if (platform === "win32") return { platform, pid, members: [{ pid, startedAt: null }] };
15
+ return { platform, pid, members: await processGroupMembers(pid, options) };
16
+ }
17
+
18
+ export async function refreshProcessTreeOwnership(/** @type {ProcessOwnershipSnapshot | null | undefined} */ snapshot, /** @type {ProcessOwnershipOptions} */ options = {}) {
19
+ if (!snapshot?.pid || snapshot.platform === "win32") return snapshot;
20
+ const members = [...(snapshot.members || [])];
21
+ for (const observed of await processGroupMembers(snapshot.pid, options)) {
22
+ if (!members.some((expected) => sameProcessIdentity(expected, observed))) members.push(observed);
23
+ }
24
+ return { ...snapshot, members };
25
+ }
26
+
27
+ export async function processTreeOwnershipStillCurrent(/** @type {ProcessOwnershipSnapshot | null | undefined} */ snapshot, /** @type {ChildProcessIdentity} */ child, /** @type {ProcessOwnershipOptions} */ options = {}) {
28
+ if (!snapshot?.pid) return false;
29
+ if (snapshot.platform === "win32") return !childHasExited(child);
30
+ const members = Array.isArray(snapshot.members) ? snapshot.members : [];
31
+ if (members.length === 0) return false;
32
+ const budget = createSnapshotBudget(options);
33
+ const fullTimeout = budget.take(members.length + 1);
34
+ if (!fullTimeout) return false;
35
+ const current = await processGroupMembers(snapshot.pid, options, 0, fullTimeout);
36
+ if (members.some((expected) => current.some((observed) => sameProcessIdentity(expected, observed)))) return true;
37
+ for (let index = 0; index < members.length; index += 1) {
38
+ const timeoutMs = budget.take(members.length - index);
39
+ if (!timeoutMs) return false;
40
+ const expected = members[index];
41
+ const targeted = await processGroupMembers(snapshot.pid, options, expected.pid, timeoutMs);
42
+ if (targeted.some((observed) => sameProcessIdentity(expected, observed))) return true;
43
+ }
44
+ return false;
45
+ }
46
+
47
+ function sameProcessIdentity(/** @type {ProcessOwnershipMember} */ left, /** @type {ProcessOwnershipMember} */ right) {
48
+ return left.pid === right.pid && Number.isFinite(left.startedAt) && Number.isFinite(right.startedAt)
49
+ && left.startedAt === right.startedAt;
50
+ }
51
+ /** @param {unknown} value */
52
+ function positivePid(value) { const parsed = Number(value); return Number.isInteger(parsed) && parsed > 0 ? parsed : 0; }
53
+ /** @param {ChildProcessIdentity} child */
54
+ function childHasExited(child) {
55
+ return child?.exitCode !== null && child?.exitCode !== undefined
56
+ || child?.signalCode !== null && child?.signalCode !== undefined;
57
+ }
@@ -0,0 +1,34 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ export function terminateProcessTree(child, signal = "SIGTERM", options = {}) {
4
+ if (!child?.pid) return false;
5
+ const platform = options.platform || process.platform;
6
+ if (platform === "win32") {
7
+ try {
8
+ const spawnProcess = typeof options.spawnProcess === "function" ? options.spawnProcess : spawn;
9
+ const force = signal === "SIGKILL";
10
+ const killer = spawnProcess("taskkill.exe", ["/PID", String(child.pid), "/T", ...(force ? ["/F"] : [])], {
11
+ stdio: "ignore", windowsHide: true, shell: false,
12
+ });
13
+ let fallbackUsed = false;
14
+ const fallback = () => {
15
+ if (fallbackUsed) return;
16
+ fallbackUsed = true;
17
+ try { child.kill(signal); }
18
+ catch { /* Callers that require proof wait for process settlement instead of trusting this best-effort fallback. */ }
19
+ };
20
+ killer?.once?.("error", fallback);
21
+ killer?.once?.("exit", (code) => { if (code !== 0) fallback(); });
22
+ killer?.unref?.();
23
+ return true;
24
+ } catch {
25
+ // Fall through to ChildProcess.kill for environments without taskkill.
26
+ }
27
+ }
28
+ const killProcess = typeof options.killProcess === "function" ? options.killProcess : process.kill.bind(process);
29
+ try { killProcess(-child.pid, signal); return true; }
30
+ catch {
31
+ try { return child.kill(signal) !== false; }
32
+ catch { return false; }
33
+ }
34
+ }
@@ -0,0 +1,77 @@
1
+ // @ts-check
2
+
3
+ import { execFile } from "node:child_process";
4
+ import { createMonotonicDeadline } from "./monotonic-deadline.mjs";
5
+
6
+ export const DEFAULT_PROCESS_OWNERSHIP_CHECK_BUDGET_MS = 3000;
7
+ /** @typedef {import("./process-tree-ownership-types.d.ts").ProcessGroupEntry} ProcessGroupEntry */
8
+ /** @typedef {import("./process-tree-ownership-types.d.ts").ProcessOwnershipOptions} ProcessOwnershipOptions */
9
+
10
+ /** @param {number} groupId @param {ProcessOwnershipOptions} [options] @param {number} [pid] @param {number} [timeoutMs] */
11
+ export async function processGroupMembers(groupId, options = {}, pid = 0, timeoutMs = snapshotTimeout(options)) {
12
+ const entries = typeof options.listProcessGroups === "function"
13
+ ? await options.listProcessGroups(options, pid, timeoutMs)
14
+ : await listProcessGroups(options, pid, timeoutMs, groupId);
15
+ return (Array.isArray(entries) ? entries : [])
16
+ .filter((entry) => entry.pgid === groupId)
17
+ .map(({ pid: memberPid, startedAt }) => ({ pid: memberPid, startedAt }));
18
+ }
19
+
20
+ /** @param {ProcessOwnershipOptions} [options] */
21
+ export function createSnapshotBudget(options = {}) {
22
+ const total = boundedPositive(options.ownershipCheckBudgetMs, DEFAULT_PROCESS_OWNERSHIP_CHECK_BUDGET_MS);
23
+ const deadline = createMonotonicDeadline(total, options.monotonicNow);
24
+ let unallocated = total;
25
+ return {
26
+ /** @param {number} slots */
27
+ take(slots) {
28
+ const remaining = Math.min(unallocated, Math.floor(deadline.remainingMs()));
29
+ if (remaining < 1) return 0;
30
+ const value = Math.max(1, Math.min(DEFAULT_PROCESS_OWNERSHIP_CHECK_BUDGET_MS, Math.floor(remaining / Math.max(1, slots))));
31
+ unallocated -= value;
32
+ return value;
33
+ },
34
+ };
35
+ }
36
+
37
+ /** @param {ProcessOwnershipOptions} options @param {number} pid @param {number} timeoutMs @param {number} groupId */
38
+ async function listProcessGroups(options, pid, timeoutMs, groupId) {
39
+ const run = typeof options.execFileProcess === "function" ? options.execFileProcess : defaultExecFileProcess;
40
+ const platform = String(options.platform || process.platform);
41
+ const args = pid ? ["-p", String(pid), "-o", "pid=,pgid=,lstart="]
42
+ : platform === "darwin" ? ["-g", String(groupId), "-o", "pid=,pgid=,lstart="] : ["-axo", "pid=,pgid=,lstart="];
43
+ const result = await run("ps", args, {
44
+ encoding: "utf8", timeout: timeoutMs, killSignal: "SIGKILL", maxBuffer: 512 * 1024,
45
+ windowsHide: true, env: { PATH: "/usr/bin:/bin", LC_ALL: "C", LANG: "C" },
46
+ });
47
+ if (result?.error || result?.status !== 0) return [];
48
+ return String(result.stdout || "").split(/\r?\n/).map(parseProcessRow).filter(isProcessGroupEntry);
49
+ }
50
+
51
+ /** @param {string} command @param {string[]} args @param {Record<string, any>} options */
52
+ function defaultExecFileProcess(command, args, options) {
53
+ return new Promise((resolvePromise) => {
54
+ execFile(command, args, options, (error, stdout) => resolvePromise({ error, status: error ? 1 : 0, stdout }));
55
+ });
56
+ }
57
+ /** @param {ProcessOwnershipOptions} options */
58
+ function snapshotTimeout(options) {
59
+ return boundedPositive(options.processSnapshotTimeoutMs, DEFAULT_PROCESS_OWNERSHIP_CHECK_BUDGET_MS);
60
+ }
61
+ /** @param {unknown} value @param {number} fallback */
62
+ function boundedPositive(value, fallback) {
63
+ const parsed = Number(value);
64
+ return Number.isFinite(parsed) && parsed >= 1 ? Math.min(DEFAULT_PROCESS_OWNERSHIP_CHECK_BUDGET_MS, Math.floor(parsed)) : fallback;
65
+ }
66
+ /** @param {unknown} line */
67
+ function parseProcessRow(line) {
68
+ const match = /^\s*(\d+)\s+(\d+)\s+(.+?)\s*$/.exec(String(line || ""));
69
+ if (!match) return null;
70
+ const pid = positivePid(match[1]);
71
+ const pgid = positivePid(match[2]);
72
+ const startedAt = Date.parse(match[3]);
73
+ return pid && pgid && Number.isFinite(startedAt) ? { pid, pgid, startedAt } : null;
74
+ }
75
+ function isProcessGroupEntry(/** @type {ProcessGroupEntry | null} */ value) { return value !== null; }
76
+ /** @param {unknown} value */
77
+ function positivePid(value) { const parsed = Number(value); return Number.isInteger(parsed) && parsed > 0 ? parsed : 0; }
@@ -0,0 +1,69 @@
1
+ import {
2
+ captureProcessTreeOwnership,
3
+ processTreeOwnershipStillCurrent,
4
+ refreshProcessTreeOwnership,
5
+ } from "./process-tree-ownership.mjs";
6
+ import { terminateProcessTree } from "./process-tree-signal.mjs";
7
+ import { createSnapshotBudget } from "./process-tree-snapshot.mjs";
8
+
9
+ export const DEFAULT_PROCESS_TERMINATION_GRACE_MS = 2000;
10
+
11
+ export function terminateProcessTreeWithEscalation(child, options = {}) {
12
+ const graceMs = Number.isFinite(Number(options.graceMs))
13
+ ? Math.max(0, Number(options.graceMs))
14
+ : DEFAULT_PROCESS_TERMINATION_GRACE_MS;
15
+ const schedule = typeof options.setTimeout === "function" ? options.setTimeout : setTimeout;
16
+ const terminate = typeof options.terminate === "function" ? options.terminate : terminateProcessTree;
17
+ const capture = typeof options.captureOwnership === "function"
18
+ ? options.captureOwnership : captureProcessTreeOwnership;
19
+ const refresh = typeof options.refreshOwnership === "function"
20
+ ? options.refreshOwnership : refreshProcessTreeOwnership;
21
+ const isOwned = typeof options.isTerminationTargetOwned === "function"
22
+ ? options.isTerminationTargetOwned : processTreeOwnershipStillCurrent;
23
+
24
+ const snapshotBudget = createSnapshotBudget(options);
25
+ let ownershipBeforeSignal;
26
+ try {
27
+ const captureOptions = boundedSnapshotOptions(snapshotBudget, options, 2);
28
+ ownershipBeforeSignal = captureOptions
29
+ ? Promise.resolve(capture(child, captureOptions)).catch(() => null)
30
+ : Promise.resolve(null);
31
+ } catch { ownershipBeforeSignal = Promise.resolve(null); }
32
+ try { terminate(child, "SIGTERM", options); }
33
+ catch { /* Ownership-checked forced escalation remains available after the grace period. */ }
34
+ const refreshedOwnership = ownershipBeforeSignal
35
+ .then((snapshot) => {
36
+ if (!snapshot) return null;
37
+ const refreshOptions = boundedSnapshotOptions(snapshotBudget, options, 1);
38
+ return refreshOptions ? refresh(snapshot, refreshOptions) : null;
39
+ })
40
+ .catch(() => null);
41
+
42
+ return schedule(() => superviseEscalation({
43
+ child, options, terminate, isOwned, refreshedOwnership,
44
+ }), graceMs);
45
+ }
46
+
47
+ async function superviseEscalation({ child, options, terminate, isOwned, refreshedOwnership }) {
48
+ try {
49
+ const ownership = await refreshedOwnership;
50
+ if (!ownership) return;
51
+ let owned = false;
52
+ try { owned = await isOwned(ownership, child, options); } catch { return; }
53
+ if (!owned) return;
54
+ try { terminate(child, "SIGKILL", options); } catch { return; }
55
+ try { options.onEscalated?.(); } catch { /* Observer failure must not suppress termination settlement. */ }
56
+ } finally {
57
+ try { options.onTerminationSettled?.(); } catch { /* Observer failure cannot change settled supervision. */ }
58
+ }
59
+ }
60
+
61
+ function boundedSnapshotOptions(budget, options, slots) {
62
+ const remainingMs = budget.take(slots);
63
+ if (!remainingMs) return null;
64
+ const configuredMs = Number(options.processSnapshotTimeoutMs);
65
+ const processSnapshotTimeoutMs = Number.isFinite(configuredMs) && configuredMs >= 1
66
+ ? Math.min(remainingMs, Math.floor(configuredMs))
67
+ : remainingMs;
68
+ return { ...options, processSnapshotTimeoutMs };
69
+ }
@@ -1,53 +1,11 @@
1
- import { spawn } from "node:child_process";
2
-
3
- export const DEFAULT_PROCESS_TERMINATION_GRACE_MS = 2000;
4
-
5
- export function terminateProcessTree(child, signal = "SIGTERM", options = {}) {
6
- if (!child?.pid) return false;
7
- const platform = options.platform || process.platform;
8
- if (platform === "win32") {
9
- try {
10
- const spawnProcess = typeof options.spawnProcess === "function" ? options.spawnProcess : spawn;
11
- const force = signal === "SIGKILL";
12
- const killer = spawnProcess("taskkill.exe", ["/PID", String(child.pid), "/T", ...(force ? ["/F"] : [])], {
13
- stdio: "ignore",
14
- windowsHide: true,
15
- shell: false,
16
- });
17
- const fallback = () => {
18
- try { child.kill(signal); } catch {}
19
- };
20
- killer?.once?.("error", fallback);
21
- killer?.once?.("exit", (code) => { if (code !== 0) fallback(); });
22
- killer?.unref?.();
23
- return true;
24
- } catch {
25
- // Fall through to ChildProcess.kill for environments without taskkill.
26
- }
27
- }
28
-
29
- const killProcess = typeof options.killProcess === "function" ? options.killProcess : process.kill.bind(process);
30
- try {
31
- killProcess(-child.pid, signal);
32
- return true;
33
- } catch {
34
- try {
35
- return child.kill(signal) !== false;
36
- } catch {
37
- return false;
38
- }
39
- }
40
- }
41
-
42
- export function terminateProcessTreeWithEscalation(child, options = {}) {
43
- const graceMs = Number.isFinite(Number(options.graceMs))
44
- ? Math.max(0, Number(options.graceMs))
45
- : DEFAULT_PROCESS_TERMINATION_GRACE_MS;
46
- const schedule = typeof options.setTimeout === "function" ? options.setTimeout : setTimeout;
47
- const terminate = typeof options.terminate === "function" ? options.terminate : terminateProcessTree;
48
- terminate(child, "SIGTERM", options);
49
- return schedule(() => {
50
- terminate(child, "SIGKILL", options);
51
- options.onEscalated?.();
52
- }, graceMs);
53
- }
1
+ export { terminateProcessTree } from "./process-tree-signal.mjs";
2
+ export {
3
+ DEFAULT_PROCESS_TERMINATION_GRACE_MS,
4
+ terminateProcessTreeWithEscalation,
5
+ } from "./process-tree-supervisor.mjs";
6
+ export {
7
+ DEFAULT_PROCESS_OWNERSHIP_CHECK_BUDGET_MS,
8
+ captureProcessTreeOwnership,
9
+ processTreeOwnershipStillCurrent,
10
+ refreshProcessTreeOwnership,
11
+ } from "./process-tree-ownership.mjs";
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { constants as fsConstants } from "node:fs";
4
4
  import { lstat, open } from "node:fs/promises";
5
+ import { filesystemIdentity, sameFilesystemIdentity } from "./filesystem-identity.mjs";
5
6
 
6
7
  const SKIPPABLE_METADATA_CODES = new Set(["ENOENT", "ENOTDIR", "EACCES", "EPERM", "ELOOP", "EBUSY"]);
7
8
 
@@ -22,23 +23,35 @@ export async function isRegularNonSymlink(filePath) {
22
23
  return Boolean(info && !info.isSymbolicLink() && info.isFile());
23
24
  }
24
25
 
25
- /** @param {string} filePath @param {number} maxBytes */
26
- export async function readOptionalRegularUtf8(filePath, maxBytes) {
27
- const handle = await open(filePath, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW || 0))
26
+ /**
27
+ * @param {string} filePath
28
+ * @param {number} maxBytes
29
+ * @param {{openFile?: typeof open, inspectPath?: typeof lstat}} [options]
30
+ */
31
+ export async function readOptionalRegularUtf8(filePath, maxBytes, options = {}) {
32
+ const limit = Number(maxBytes);
33
+ if (!Number.isSafeInteger(limit) || limit < 0) throw new TypeError("project metadata byte limit must be a non-negative safe integer");
34
+ const openFile = options.openFile || open;
35
+ const inspectPath = options.inspectPath || lstat;
36
+ const handle = await openFile(filePath, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW || 0), 0o600)
28
37
  .catch((error) => skippableMetadataError(error) ? null : Promise.reject(error));
29
38
  if (!handle) return null;
30
39
  try {
31
40
  let pathInfo;
32
41
  let current;
33
42
  try {
34
- [pathInfo, current] = await Promise.all([lstat(filePath), handle.stat()]);
43
+ [pathInfo, current] = await Promise.all([
44
+ inspectPath(filePath, { bigint: true }),
45
+ handle.stat({ bigint: true }),
46
+ ]);
35
47
  } catch (error) {
36
48
  if (skippableMetadataError(error)) return null;
37
49
  throw error;
38
50
  }
39
51
  if (pathInfo.isSymbolicLink() || !pathInfo.isFile() || !current.isFile()
40
- || pathInfo.dev !== current.dev || pathInfo.ino !== current.ino || current.size > maxBytes) return null;
41
- const buffer = Buffer.alloc(current.size);
52
+ || !sameFilesystemIdentity(filesystemIdentity(pathInfo, "project metadata"), filesystemIdentity(current, "project metadata"))
53
+ || current.size > BigInt(limit)) return null;
54
+ const buffer = Buffer.alloc(Number(current.size));
42
55
  let offset = 0;
43
56
  while (offset < buffer.length) {
44
57
  const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, offset);
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import relayContract from "../shared/relay-contract.json" with { type: "json" };
4
+
3
5
  /** @typedef {{id?: unknown, [key: string]: unknown}} RelayResult */
4
6
  /** @typedef {{event?: (level: string, name: string, fields: Record<string, unknown>, message: string) => void, warn?: (message: string) => void}} RecoveryLogger */
5
7
  /** @typedef {{setTimeout: (callback: () => void, delay: number) => any, clearTimeout: (handle: any) => void}} RecoveryScheduler */
@@ -17,7 +19,7 @@
17
19
  * }} RelayCallRecoveryOptions
18
20
  */
19
21
 
20
- const DEFAULT_RECONNECT_GRACE_MS = 30_000;
22
+ const DEFAULT_RECONNECT_GRACE_MS = relayContract.reconnectGraceMs;
21
23
 
22
24
  export class RelayCallRecovery {
23
25
  /** @param {RelayCallRecoveryOptions} [options] */
@@ -41,44 +43,56 @@ export class RelayCallRecovery {
41
43
  /** @param {RelayResult} response */
42
44
  deliver(response) {
43
45
  const callId = String(response?.id || "");
44
- if (this.send(response)) {
45
- if (callId) this.pendingResults.delete(callId);
46
- return true;
47
- }
48
- if (callId && this.isRecoverable()) {
49
- this.pendingResults.set(callId, response);
50
- this.scheduleExpiry();
51
- this.logger.event?.("debug", "relay.tool_result.queued", {
52
- call_id: shortCallId(callId), queued_results: this.pendingResults.size,
53
- }, "Queued a completed tool result while the relay reconnects");
46
+ if (!callId) return this.send(response);
47
+ if (!this.isRecoverable()) {
48
+ this.logger.event?.("debug", "relay.tool_result.discarded", {
49
+ call_id: shortCallId(callId), reason: "transport_unavailable",
50
+ }, "Discarded a tool result because the relay is no longer recoverable");
54
51
  return false;
55
52
  }
56
- this.logger.event?.("debug", "relay.tool_result.discarded", {
57
- call_id: shortCallId(callId), reason: "transport_unavailable",
58
- }, "Discarded a tool result because the relay is no longer recoverable");
53
+
54
+ // Retain sent results until the Worker commits and acknowledges them.
55
+ this.pendingResults.set(callId, response);
56
+ const sent = this.send(response);
57
+ if (sent) {
58
+ this.logger.event?.("debug", "relay.tool_result.awaiting_ack", {
59
+ call_id: shortCallId(callId), unacknowledged_results: this.pendingResults.size,
60
+ }, "Delivered a tool result and retained it until Worker acknowledgement");
61
+ return true;
62
+ }
63
+
64
+ this.scheduleExpiry();
65
+ this.logger.event?.("debug", "relay.tool_result.queued", {
66
+ call_id: shortCallId(callId), queued_results: this.pendingResults.size,
67
+ }, "Queued a completed tool result while the relay reconnects");
59
68
  return false;
60
69
  }
61
70
 
62
71
  /** @param {unknown} callId */
63
- discard(callId) {
64
- return this.pendingResults.delete(String(callId));
65
- }
72
+ acknowledge(callId) { return this.pendingResults.delete(String(callId)); }
73
+
74
+ /** @param {unknown} callId */
75
+ discard(callId) { return this.pendingResults.delete(String(callId)); }
76
+
77
+ ownedCallIds() { return [...new Set([...this.activeCallIds(), ...this.pendingResults.keys()])]; }
66
78
 
67
79
  /** @param {Iterable<string>} resumedCallIds @param {(callId: string) => boolean} cancelCall */
68
80
  reconcile(resumedCallIds, cancelCall) {
69
81
  const resumed = new Set(resumedCallIds);
70
- let cancelled = 0;
71
- let discarded = 0;
72
- for (const callId of this.activeCallIds()) {
73
- if (!resumed.has(callId) && cancelCall(callId)) cancelled += 1;
74
- }
82
+ const active = [...this.activeCallIds()];
83
+ const locallyOwned = new Set(this.ownedCallIds());
84
+ const missing = [...resumed].filter((callId) => !locallyOwned.has(callId));
85
+ let cancelled = 0; let discarded = 0;
86
+ for (const callId of active) if (!resumed.has(callId) && cancelCall(callId)) cancelled += 1;
75
87
  for (const callId of [...this.pendingResults.keys()]) {
76
88
  if (!resumed.has(callId) && this.pendingResults.delete(callId)) discarded += 1;
77
89
  }
78
- if (cancelled > 0 || discarded > 0) {
79
- this.logger.event?.("debug", "relay.calls.reconciled", { cancelled_calls: cancelled, discarded_results: discarded },
80
- "Cancelled relay work that no longer had a waiting client after reconnect");
81
- }
90
+ if (cancelled > 0 || discarded > 0 || missing.length > 0) this.logger.event?.(
91
+ "debug", "relay.calls.reconciled",
92
+ { cancelled_calls: cancelled, discarded_results: discarded, missing_resumed_calls: missing.length },
93
+ "Reconciled local relay-call ownership against the Worker reconnect set",
94
+ );
95
+ return missing;
82
96
  }
83
97
 
84
98
  disconnected() {
@@ -90,20 +104,26 @@ export class RelayCallRecovery {
90
104
  }, "Keeping in-flight tool calls alive during a brief relay interruption");
91
105
  }
92
106
 
93
- ready() {
94
- this.clearTimer();
107
+ ready() { this.clearTimer(); this.retryUnacknowledged("reconnected"); }
108
+
109
+ pulse() { this.retryUnacknowledged("heartbeat"); }
110
+
111
+ /** @param {string} reason */
112
+ retryUnacknowledged(reason) {
95
113
  let delivered = 0;
96
- for (const [callId, response] of [...this.pendingResults]) {
114
+ for (const response of this.pendingResults.values()) {
97
115
  if (!this.send(response)) {
98
116
  this.scheduleExpiry();
99
117
  break;
100
118
  }
101
- this.pendingResults.delete(callId);
102
119
  delivered += 1;
103
120
  }
104
121
  if (delivered > 0) {
105
- this.logger.event?.("info", "relay.tool_results.replayed", { delivered_results: delivered },
106
- "Delivered completed tool results after the relay reconnected");
122
+ this.logger.event?.(reason === "reconnected" ? "info" : "debug", "relay.tool_results.redelivered", {
123
+ delivered_results: delivered,
124
+ reason,
125
+ unacknowledged_results: this.pendingResults.size,
126
+ }, "Redelivered completed tool results awaiting Worker acknowledgement");
107
127
  }
108
128
  }
109
129
 
@@ -120,9 +140,12 @@ export class RelayCallRecovery {
120
140
  const cancelled = this.cancelOrigin("remote relay reconnect grace expired");
121
141
  const discarded = this.pendingResults.size;
122
142
  this.pendingResults.clear();
123
- this.terminate();
143
+ if (cancelled > 0) this.terminate();
124
144
  if (cancelled > 0 || discarded > 0) {
125
- this.logger.warn?.(`remote relay did not recover within ${this.graceMs / 1000} seconds; cancelled ${cancelled} call(s) and discarded ${discarded} queued result(s)`);
145
+ const message = `remote relay did not recover within ${this.graceMs / 1000} seconds; cancelled ${cancelled} call(s) and discarded ${discarded} queued result(s)`;
146
+ this.logger.event
147
+ ? this.logger.event("warn", "relay.calls.reconnect_expired", { cancelled_calls: cancelled, discarded_results: discarded, grace_ms: this.graceMs }, message)
148
+ : this.logger.warn?.(message);
126
149
  }
127
150
  }, this.graceMs);
128
151
  this.reconnectTimer?.unref?.();
@@ -136,10 +159,7 @@ export class RelayCallRecovery {
136
159
  }
137
160
 
138
161
  /** @param {unknown} value @param {number} fallback */
139
- function positiveInteger(value, fallback) {
140
- const number = Number(value);
141
- return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
142
- }
162
+ function positiveInteger(value, fallback) { const number = Number(value); return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback; }
143
163
 
144
164
  /** @param {unknown} value */
145
165
  function shortCallId(value) {
@@ -0,0 +1,131 @@
1
+ const MAX_PROTOCOL_ERROR_CODE_CHARS = 64;
2
+
3
+ export function relayServerErrorReconnectCategory(errorCode, state = {}) {
4
+ const code = sanitizeProtocolErrorCode(errorCode);
5
+ const authenticated = state?.authenticated === true;
6
+ const ready = state?.ready === true;
7
+ if (code === "daemon_hello_timeout" && !authenticated) return "relay_handshake_timeout";
8
+ if (code === "daemon_ready_timeout" && authenticated && !ready) return "relay_readiness_timeout";
9
+ if (code === "daemon_transport_error") return "relay_transport_error";
10
+ if (code === "daemon_liveness_timeout") return "relay_heartbeat_timeout";
11
+ return "";
12
+ }
13
+
14
+ export function relayCloseCategory(code, reason = "") {
15
+ const numeric = Number(code);
16
+ const reasonText = String(reason || "");
17
+ if (isSupersededClose(numeric, reasonText)) return "superseded";
18
+ if (numeric === 1008 && reasonText === "daemon hello timeout") return "relay_handshake_timeout";
19
+ if (numeric === 1008 && reasonText === "daemon ready timeout") return "relay_readiness_timeout";
20
+ if ([1008, 1012].includes(numeric) && ["daemon pong failed", "daemon send failed"].includes(reasonText)) return "relay_transport_error";
21
+ if ([1008, 1012].includes(numeric) && reasonText === "daemon liveness timeout") return "relay_heartbeat_timeout";
22
+ if (numeric === 1008 && ["stale daemon candidate", "expired daemon candidate"].includes(reasonText)) return "relay_restarting_or_unavailable";
23
+ if (numeric === 1008 && ["daemon hello required", "missing daemon attachment", "invalid daemon candidate timestamp"].includes(reasonText)) return "relay_protocol_error";
24
+ if (numeric === 1000) return "normal_close";
25
+ if (numeric === 1001 || numeric === 1012 || numeric === 1013) return "relay_restarting_or_unavailable";
26
+ if (numeric === 1006) return "connection_interrupted";
27
+ if (numeric === 1002) return "relay_protocol_error";
28
+ if (numeric === 1007) return "invalid_transport_payload";
29
+ if (numeric === 1008) return "relay_policy_rejected";
30
+ if (numeric === 1009) return "message_too_large";
31
+ if (numeric === 1011) return "relay_internal_error";
32
+ return "unexpected_close";
33
+ }
34
+
35
+ export function relayOutageUserAction(category, outageMs) {
36
+ if (Number(outageMs) < 5 * 60_000) return "";
37
+ if (String(category || "") === "local_authority_revocation_retry") {
38
+ return " If this persists, inspect local authority, process-session, and managed-job state; the retained revocation will retry automatically.";
39
+ }
40
+ return " If this persists, check internet access and the deployed Worker.";
41
+ }
42
+
43
+ export function relayFatalMessage(category) {
44
+ if (category === "relay_protocol_mismatch") {
45
+ return "remote relay identity or version does not match this daemon; upgrade and redeploy both components";
46
+ }
47
+ if (category === "relay_protocol_error") {
48
+ return "remote relay protocol error; upgrade and redeploy both components, then restart the daemon";
49
+ }
50
+ if (category === "relay_proxy_configuration") {
51
+ return "remote relay proxy configuration is invalid; check HTTP_PROXY, HTTPS_PROXY, and NO_PROXY";
52
+ }
53
+ return "remote relay rejected the daemon connection; verify credentials or redeploy the Worker";
54
+ }
55
+
56
+ export function relayCloseUserCause(category) {
57
+ const causes = {
58
+ connection_interrupted: "connection interrupted",
59
+ local_authority_revocation_retry: "local authority revocation requires retry",
60
+ relay_restarting_or_unavailable: "relay restarting or temporarily unavailable",
61
+ relay_policy_rejected: "relay rejected the connection",
62
+ relay_internal_error: "relay internal error",
63
+ relay_protocol_mismatch: "relay identity or version mismatch",
64
+ relay_authentication_failed: "relay authentication failed",
65
+ relay_connect_timeout: "relay connection attempt timed out",
66
+ relay_handshake_timeout: "relay authentication acknowledgement timed out",
67
+ relay_readiness_timeout: "end-to-end relay readiness verification timed out",
68
+ relay_heartbeat_timeout: "relay stopped responding",
69
+ relay_transport_timeout: "relay transport stopped responding",
70
+ relay_transport_error: "relay transport error",
71
+ relay_protocol_error: "relay protocol error",
72
+ relay_proxy_configuration: "relay proxy configuration invalid",
73
+ invalid_transport_payload: "invalid transport payload",
74
+ message_too_large: "message exceeded the relay limit",
75
+ normal_close: "connection closed",
76
+ unexpected_close: "unexpected connection close",
77
+ superseded: "connection superseded",
78
+ };
79
+ return causes[String(category || "")] || "connection interrupted";
80
+ }
81
+
82
+ export function welcomeMismatch(message, expectedServer = "", expectedVersion = "") {
83
+ if (!message || typeof message !== "object" || Array.isArray(message)) return "invalid_welcome";
84
+ if (message.type !== "welcome") return "unexpected_welcome_type";
85
+ if (expectedServer && message.server !== expectedServer) return "server_identity_mismatch";
86
+ if (expectedVersion && message.version !== expectedVersion) return "server_version_mismatch";
87
+ if (typeof message.server !== "string" || !message.server || typeof message.version !== "string" || !message.version) return "incomplete_welcome";
88
+ return "";
89
+ }
90
+
91
+ export function acknowledgementMismatch(message, expectedServer = "", expectedVersion = "") {
92
+ if (!message || typeof message !== "object" || Array.isArray(message)) return "invalid_acknowledgement";
93
+ if (message.type !== "hello_ack") return "unexpected_acknowledgement_type";
94
+ if (expectedServer && message.server !== expectedServer) return "server_identity_mismatch";
95
+ if (expectedVersion && message.version !== expectedVersion) return "server_version_mismatch";
96
+ if (typeof message.server !== "string" || !message.server || typeof message.version !== "string" || !message.version) return "incomplete_acknowledgement";
97
+ return "";
98
+ }
99
+
100
+ export function readinessMismatch(message, expectedServer = "", expectedVersion = "") {
101
+ if (!message || typeof message !== "object" || Array.isArray(message)) return "invalid_readiness_acknowledgement";
102
+ if (message.type !== "ready_ack") return "unexpected_readiness_acknowledgement_type";
103
+ if (expectedServer && message.server !== expectedServer) return "server_identity_mismatch";
104
+ if (expectedVersion && message.version !== expectedVersion) return "server_version_mismatch";
105
+ if (typeof message.server !== "string" || !message.server || typeof message.version !== "string" || !message.version) return "incomplete_readiness_acknowledgement";
106
+ return "";
107
+ }
108
+
109
+ export function isRelayReadyContext(relayContext = {}, relay = null) {
110
+ if (relayContext?.ready === true) return true;
111
+ if (relayContext?.ready === false) return false;
112
+ return Number(relayContext?.sessionId) > 0 && relay?.status?.()?.ready === true;
113
+ }
114
+
115
+ export function isSupersededClose(code, reason) {
116
+ return Number(code) === 1012 && String(reason || "") === "replaced by verified daemon";
117
+ }
118
+
119
+ export function reconnectDelay(attempt, random = Math.random, previousAttemptDurationMs = 0, connectTimeoutMs = 15_000) {
120
+ const safeAttempt = Math.max(0, Number.isFinite(Number(attempt)) ? Number(attempt) : 0);
121
+ const base = Math.min(1000 * (2 ** Math.min(safeAttempt, 4)), 15_000);
122
+ const spent = Math.max(0, Number(previousAttemptDurationMs) || 0);
123
+ const connectBudget = Math.max(1, Number(connectTimeoutMs) || 15_000);
124
+ const remaining = spent >= connectBudget * 0.8 ? Math.max(250, base - spent) : base;
125
+ return remaining + Math.floor((typeof random === "function" ? random() : Math.random()) * 500);
126
+ }
127
+
128
+ export function sanitizeProtocolErrorCode(value) {
129
+ const code = String(value || "unknown_error").replace(/[^A-Za-z0-9_-]/g, "_").slice(0, MAX_PROTOCOL_ERROR_CODE_CHARS);
130
+ return code || "unknown_error";
131
+ }