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,18 +1,29 @@
1
1
  import WebSocket from "ws";
2
+ import { performance } from "node:perf_hooks";
2
3
  import { classifyOperationalError } from "./log.mjs";
3
4
  import { proxyAgentForWebSocket } from "./network-proxy.mjs";
4
-
5
- const DEFAULT_HEARTBEAT_INTERVAL_MS = 25_000;
6
- const DEFAULT_HEARTBEAT_TIMEOUT_MS = 75_000;
5
+ import { RelayLiveness } from "./relay-liveness.mjs";
6
+ import {
7
+ boundedPositiveInteger, classifyRelayTransportError, formatAttempts, formatDuration, normalizeWorkerUrl, redactUrl,
8
+ relayHttpStatusFromError, sanitizeCloseReason, terminateSocket, tracedTlsConnection,
9
+ } from "./relay-connection-support.mjs";
10
+ import {
11
+ APPLICATION_PROXY_ROUTE_SCOPE, preferredRelayCloseCategory, relayOutageFields, relayRecoveryFields, relayStatusSnapshot,
12
+ } from "./relay-diagnostics.mjs";
13
+ import {
14
+ acknowledgementMismatch, isSupersededClose, readinessMismatch, reconnectDelay, relayCloseCategory, relayCloseUserCause,
15
+ relayFatalMessage, relayOutageUserAction, relayServerErrorReconnectCategory, sanitizeProtocolErrorCode, welcomeMismatch,
16
+ } from "./relay-connection-classification.mjs";
17
+ export {
18
+ acknowledgementMismatch, isRelayReadyContext, isSupersededClose, readinessMismatch, reconnectDelay, relayCloseCategory,
19
+ relayOutageUserAction, relayServerErrorReconnectCategory, welcomeMismatch,
20
+ } from "./relay-connection-classification.mjs";
7
21
  const DEFAULT_CONNECT_TIMEOUT_MS = 15_000;
8
22
  const DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;
9
23
  const DEFAULT_READINESS_TIMEOUT_MS = 15_000;
10
24
  const DEFAULT_OUTAGE_WARN_AFTER_MS = 10_000;
11
25
  const DEFAULT_OUTAGE_WARN_REPEAT_MS = 60_000;
12
26
  const DEFAULT_OUTAGE_WARN_MAX_REPEAT_MS = 15 * 60_000;
13
- const MAX_CLOSE_REASON_CHARS = 128;
14
- const MAX_PROTOCOL_ERROR_CODE_CHARS = 64;
15
-
16
27
  const DEFAULT_SCHEDULER = Object.freeze({
17
28
  setTimeout: (callback, delay) => setTimeout(callback, delay),
18
29
  clearTimeout: (timer) => clearTimeout(timer),
@@ -35,13 +46,13 @@ export class RelayConnection {
35
46
  this.onFatal = typeof options.onFatal === "function" ? options.onFatal : () => {};
36
47
  this.WebSocketClass = options.WebSocketClass || WebSocket;
37
48
  this.scheduler = options.scheduler || DEFAULT_SCHEDULER;
38
- this.now = typeof options.now === "function" ? options.now : Date.now;
49
+ this.now = typeof options.now === "function" ? options.now : () => performance.now();
50
+ this.wallNow = typeof options.wallNow === "function" ? options.wallNow : Date.now;
39
51
  this.reconnectDelay = typeof options.reconnectDelay === "function" ? options.reconnectDelay : reconnectDelay;
40
52
  this.proxyAgentForUrl = typeof options.proxyAgentForUrl === "function" ? options.proxyAgentForUrl : proxyAgentForWebSocket;
41
53
  this.networkRoute = "unresolved";
54
+ this.networkRouteScope = APPLICATION_PROXY_ROUTE_SCOPE;
42
55
  this.maxPayload = boundedPositiveInteger(options.maxPayload, 8 * 1024 * 1024);
43
- this.heartbeatIntervalMs = boundedPositiveInteger(options.heartbeatIntervalMs, DEFAULT_HEARTBEAT_INTERVAL_MS);
44
- this.heartbeatTimeoutMs = boundedPositiveInteger(options.heartbeatTimeoutMs, DEFAULT_HEARTBEAT_TIMEOUT_MS);
45
56
  this.connectTimeoutMs = boundedPositiveInteger(options.connectTimeoutMs, DEFAULT_CONNECT_TIMEOUT_MS);
46
57
  this.handshakeTimeoutMs = boundedPositiveInteger(options.handshakeTimeoutMs, DEFAULT_HANDSHAKE_TIMEOUT_MS);
47
58
  this.readinessTimeoutMs = boundedPositiveInteger(options.readinessTimeoutMs, DEFAULT_READINESS_TIMEOUT_MS);
@@ -59,40 +70,71 @@ export class RelayConnection {
59
70
  this.readinessProbeDelivered = false;
60
71
  this.hasConnected = false;
61
72
  this.connectedAt = 0;
62
- this.lastInboundAt = 0;
73
+ this.lastReadyInboundSilenceMs = 0;
63
74
  this.reconnectAttempt = 0;
64
75
  this.reconnectTimer = null;
65
76
  this.connectTimer = null;
66
- this.heartbeatTimer = null;
67
77
  this.handshakeTimer = null;
68
78
  this.readinessTimer = null;
69
79
  this.outageWarnTimer = null;
70
80
  this.outageStartedAt = 0;
81
+ this.outageStartedWallAt = 0;
71
82
  this.outageAttempts = 0;
72
83
  this.outageNoticeEmitted = false;
73
84
  this.outageWarningCount = 0;
74
85
  this.lastOutageWarnAt = 0;
86
+ this.outageCount = 0;
75
87
  this.lastCloseCategory = "connection_interrupted";
88
+ this.lastCloseCode = 0;
76
89
  this.lastTransportErrorClass = "";
90
+ this.lastDisconnectedAt = 0;
91
+ this.lastReadyAt = 0;
92
+ this.lastReadyWallAt = 0;
93
+ this.lastReadyDurationMs = 0;
94
+ this.lastReconnectDelayMs = 0;
95
+ this.nextReconnectAt = 0;
96
+ this.nextReconnectWallAt = 0;
97
+ this.connectAttemptStartedAt = null;
98
+ this.lastConnectDurationMs = 0;
99
+ this.lastConnectStage = "idle";
100
+ this.lastConnectHttpStatus = null;
77
101
  this.pendingCloseCategory = "";
78
102
  this.connectedOnce = null;
79
103
  this.connectedOnceResolve = null;
80
104
  this.connectedOnceReject = null;
81
105
  this.sessionGeneration = 0;
82
106
  this.activeSessionId = 0;
107
+ this.liveness = new RelayLiveness({
108
+ ...options,
109
+ scheduler: this.scheduler,
110
+ now: this.now,
111
+ logger: this.logger,
112
+ currentSocket: () => this.socket,
113
+ isActive: () => !this.closed && this.authenticated && this.isSocketOpen(this.socket),
114
+ sendApplicationHeartbeat: (_now, socket) => this.sendOnSocket(socket, { type: "heartbeat", ts: this.wallNow() }),
115
+ onTransportError: (error, socket) => {
116
+ this.lastTransportErrorClass = classifyRelayTransportError(error);
117
+ this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_transport_error");
118
+ this.logger.debug?.("remote relay transport ping failed", { error_class: this.lastTransportErrorClass });
119
+ terminateSocket(socket);
120
+ },
121
+ onTransportTimeout: ({ silentForMs, eventLoopLagMs }, socket) => {
122
+ if (!socket) return;
123
+ this.logger.debug?.("remote relay transport pong timed out", { silent_for_ms: silentForMs, event_loop_lag_ms: eventLoopLagMs });
124
+ this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_transport_timeout");
125
+ terminateSocket(socket);
126
+ },
127
+ onApplicationTimeout: ({ silentForMs, eventLoopLagMs }, socket) => {
128
+ if (!socket) return;
129
+ this.logger.debug?.("remote relay application heartbeat timed out", { silent_for_ms: silentForMs, event_loop_lag_ms: eventLoopLagMs });
130
+ this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_heartbeat_timeout");
131
+ terminateSocket(socket);
132
+ },
133
+ });
83
134
  }
84
135
 
85
136
  status() {
86
- return {
87
- authenticated: this.authenticated,
88
- ready: this.ready,
89
- readiness_probe_delivered: this.readinessProbeDelivered,
90
- closed: this.closed,
91
- network_route: this.networkRoute,
92
- reconnect_attempt: this.reconnectAttempt,
93
- outage_active: this.outageStartedAt > 0,
94
- session_generation: this.sessionGeneration,
95
- };
137
+ return relayStatusSnapshot(this, this.now());
96
138
  }
97
139
 
98
140
  currentSessionId() {
@@ -111,12 +153,15 @@ export class RelayConnection {
111
153
  }
112
154
 
113
155
  stop() {
156
+ const settlePendingStart = this.connectedOnceResolve;
157
+ this.connectedOnceResolve = null;
158
+ this.connectedOnceReject = null;
114
159
  this.closed = true;
115
160
  this.authenticated = false;
116
161
  this.ready = false;
117
162
  this.readinessProbeDelivered = false;
118
163
  this.activeSessionId = 0;
119
- this.clearTimer("heartbeatTimer", "clearInterval");
164
+ this.liveness.stop();
120
165
  this.clearTimer("connectTimer", "clearTimeout");
121
166
  this.clearTimer("handshakeTimer", "clearTimeout");
122
167
  this.clearTimer("readinessTimer", "clearTimeout");
@@ -125,10 +170,12 @@ export class RelayConnection {
125
170
  const socket = this.socket;
126
171
  this.socket = null;
127
172
  if (socket) {
128
- try { socket.close(1000, "daemon shutdown"); } catch {}
173
+ try { socket.close(1000, "daemon shutdown"); }
174
+ catch { /* Relay shutdown state is already authoritative and the transport may have closed concurrently. */ }
129
175
  }
130
176
  this.resetOutage();
131
177
  this.reconnectAttempt = 0;
178
+ settlePendingStart?.(false);
132
179
  }
133
180
 
134
181
  send(value) {
@@ -140,7 +187,8 @@ export class RelayConnection {
140
187
  const sessionId = Number(expectedSessionId) || 0;
141
188
  if (!sessionId || sessionId !== this.activeSessionId) return { ok: false, reason: "session_ended" };
142
189
  if (!this.authenticated || !this.isSocketOpen(this.socket)) return { ok: false, reason: "transport_unavailable" };
143
- if (!this.ready && value?.type !== "relay_probe_result") return { ok: false, reason: "transport_unavailable" };
190
+ const preReadyControl = ["relay_probe_result", "resume_calls_ack", "authority_revoke_ack"].includes(String(value?.type || ""));
191
+ if (!this.ready && !preReadyControl) return { ok: false, reason: "transport_unavailable" };
144
192
  if (!this.sendOnSocket(this.socket, value)) return { ok: false, reason: "send_failed" };
145
193
  if (value?.type === "relay_probe_result") this.readinessProbeDelivered = true;
146
194
  return { ok: true, reason: "sent" };
@@ -148,7 +196,7 @@ export class RelayConnection {
148
196
 
149
197
  interrupt(category = "relay_transport_error") {
150
198
  if (this.closed || !this.socket) return false;
151
- this.pendingCloseCategory = String(category || "relay_transport_error");
199
+ this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, category);
152
200
  terminateSocket(this.socket);
153
201
  return true;
154
202
  }
@@ -163,16 +211,16 @@ export class RelayConnection {
163
211
  return false;
164
212
  }
165
213
  this.logger.debug?.("remote relay welcome received");
166
- Promise.resolve(this.helloMessage(message)).then((hello) => {
214
+ Promise.resolve(this.helloMessage(message, this.status())).then((hello) => {
167
215
  if (this.socket !== socket || this.closed || this.authenticated) return;
168
- if (!this.sendOnSocket(socket, hello)) this.failPermanently("relay_authentication_failed");
216
+ this.sendOnSocket(socket, hello);
169
217
  }).catch((error) => {
218
+ if (this.socket !== socket || this.closed || this.authenticated) return;
170
219
  this.logger.debug?.("could not create daemon authentication proof", { error_class: classifyOperationalError(error) });
171
220
  this.failPermanently("relay_authentication_failed");
172
221
  });
173
222
  return true;
174
223
  }
175
-
176
224
  acknowledge(message = {}) {
177
225
  const socket = this.socket;
178
226
  if (this.closed || this.authenticated || !this.isSocketOpen(socket)) return false;
@@ -188,13 +236,12 @@ export class RelayConnection {
188
236
  this.activeSessionId = this.sessionGeneration;
189
237
  this.clearTimer("handshakeTimer", "clearTimeout");
190
238
  this.connectedAt = this.now();
191
- this.lastInboundAt = this.connectedAt;
192
- this.startHeartbeat();
239
+ this.liveness.start();
193
240
  this.clearTimer("readinessTimer", "clearTimeout");
194
241
  this.readinessTimer = this.scheduler.setTimeout(() => {
195
242
  if (this.socket !== socket || this.closed || this.ready) return;
196
243
  this.logger.debug?.("remote relay end-to-end readiness probe timed out", { timeout_ms: this.readinessTimeoutMs });
197
- this.pendingCloseCategory = "relay_readiness_timeout";
244
+ this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_readiness_timeout");
198
245
  terminateSocket(socket);
199
246
  }, this.readinessTimeoutMs);
200
247
  this.readinessTimer?.unref?.();
@@ -218,17 +265,20 @@ export class RelayConnection {
218
265
  this.ready = true;
219
266
  this.clearTimer("readinessTimer", "clearTimeout");
220
267
  this.reconnectAttempt = 0;
268
+ this.lastReadyAt = this.now();
269
+ this.lastReadyWallAt = this.wallNow();
270
+ this.nextReconnectAt = 0;
271
+ this.nextReconnectWallAt = 0;
272
+ this.lastReconnectDelayMs = 0;
221
273
 
222
274
  if (!this.hasConnected) {
223
275
  this.logger.info?.("remote relay connected and end-to-end result delivery verified");
224
276
  } else if (this.outageStartedAt > 0) {
225
277
  const outageMs = Math.max(0, this.now() - this.outageStartedAt);
226
278
  if (this.outageNoticeEmitted) {
227
- this.logger.info?.(`remote relay connection restored after ${formatDuration(outageMs)} (${formatAttempts(this.outageAttempts)})`);
228
- this.logger.debug?.("remote relay outage recovery details", {
229
- outage_seconds: roundSeconds(outageMs),
230
- attempts: this.outageAttempts,
231
- });
279
+ const recoveryFields = relayRecoveryFields(this, outageMs);
280
+ this.logger.warn?.(`remote relay connection restored after ${formatDuration(outageMs)} (${formatAttempts(this.outageAttempts)})`, recoveryFields);
281
+ this.logger.debug?.("remote relay outage recovery details", recoveryFields);
232
282
  } else {
233
283
  this.logger.debug?.("remote relay connection recovered after a brief interruption", {
234
284
  outage_ms: outageMs,
@@ -253,18 +303,18 @@ export class RelayConnection {
253
303
 
254
304
  handleServerError(message = {}) {
255
305
  const errorCode = sanitizeProtocolErrorCode(message?.error);
256
- this.logger.debug?.("remote relay reported a protocol error", { error_code: errorCode });
257
- if (errorCode === "daemon_hello_timeout" && !this.authenticated) {
258
- const socket = this.socket;
259
- if (this.closed || !socket) return true;
260
- this.pendingCloseCategory = "relay_handshake_timeout";
261
- terminateSocket(socket);
262
- return true;
263
- }
264
- if (errorCode === "daemon_ready_timeout" && this.authenticated && !this.ready) {
306
+ const reconnectCategory = relayServerErrorReconnectCategory(errorCode, {
307
+ authenticated: this.authenticated,
308
+ ready: this.ready,
309
+ });
310
+ this.logger.debug?.(
311
+ reconnectCategory ? "remote relay requested connection recovery" : "remote relay reported a protocol error",
312
+ { error_code: errorCode, reconnect_category: reconnectCategory || "none" },
313
+ );
314
+ if (reconnectCategory) {
265
315
  const socket = this.socket;
266
316
  if (this.closed || !socket) return true;
267
- this.pendingCloseCategory = "relay_readiness_timeout";
317
+ this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, reconnectCategory);
268
318
  terminateSocket(socket);
269
319
  return true;
270
320
  }
@@ -275,26 +325,36 @@ export class RelayConnection {
275
325
  connect() {
276
326
  if (this.closed || this.socket) return;
277
327
  const wsUrl = `${this.workerUrl.replace(/^http/i, "ws")}/daemon/ws`;
328
+ this.connectAttemptStartedAt = this.now();
329
+ this.lastConnectStage = "socket_constructing";
330
+ this.lastConnectHttpStatus = null;
278
331
  this.logger.debug?.("connecting to remote relay", { endpoint: redactUrl(wsUrl), attempt: this.reconnectAttempt + 1 });
279
332
  let socket;
280
333
  try {
281
334
  const proxy = this.proxyAgentForUrl(wsUrl);
282
335
  const headers = this.connectionHeaders();
283
336
  if (!headers || typeof headers !== "object" || Array.isArray(headers)) throw new Error("relay connection headers are invalid");
284
- this.networkRoute = proxy?.agent ? "proxy" : "direct";
337
+ this.networkRoute = proxy?.agent ? "application-http-proxy" : "system-network-stack";
338
+ this.lastConnectStage = proxy?.agent ? "proxy_connecting" : "tcp_connecting";
285
339
  socket = new this.WebSocketClass(wsUrl, {
286
340
  headers,
287
341
  maxPayload: this.maxPayload,
288
342
  ...(proxy?.agent ? { agent: proxy.agent } : {}),
343
+ ...(!proxy?.agent && this.WebSocketClass === WebSocket ? {
344
+ createConnection: tracedTlsConnection((stage) => this.observeConnectStage(stage)),
345
+ } : {}),
289
346
  });
290
347
  this.logger.debug?.("remote relay network route selected", { route: this.networkRoute });
291
348
  } catch (error) {
292
349
  if (error?.code === "relay_proxy_configuration") {
293
- this.networkRoute = "invalid-proxy-configuration";
350
+ this.networkRoute = "invalid-application-proxy-configuration";
294
351
  this.failPermanently("relay_proxy_configuration");
295
352
  return;
296
353
  }
297
354
  this.lastTransportErrorClass = classifyRelayTransportError(error);
355
+ this.lastCloseCode = 0;
356
+ this.lastDisconnectedAt = this.wallNow();
357
+ this.finishConnectAttempt();
298
358
  this.logger.debug?.("remote relay connection could not be created", { error_class: this.lastTransportErrorClass });
299
359
  this.scheduleReconnect("connection_interrupted");
300
360
  return;
@@ -304,7 +364,8 @@ export class RelayConnection {
304
364
  this.connectTimer = this.scheduler.setTimeout(() => {
305
365
  if (this.socket !== socket || this.closed || this.isSocketOpen(socket)) return;
306
366
  this.logger.debug?.("remote relay transport connection timed out", { timeout_ms: this.connectTimeoutMs });
307
- this.pendingCloseCategory = "relay_connect_timeout";
367
+ this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_connect_timeout");
368
+ this.finishConnectAttempt();
308
369
  terminateSocket(socket);
309
370
  }, this.connectTimeoutMs);
310
371
  this.connectTimer?.unref?.();
@@ -312,16 +373,18 @@ export class RelayConnection {
312
373
  socket.on("open", () => {
313
374
  this.clearTimer("connectTimer", "clearTimeout");
314
375
  if (this.socket !== socket || this.closed) {
315
- try { socket.close(1000, "stale daemon connection"); } catch {}
376
+ try { socket.close(1000, "stale daemon connection"); }
377
+ catch { /* Stale transport ownership has already been rejected; concurrent close is harmless. */ }
316
378
  return;
317
379
  }
318
- this.lastInboundAt = this.now();
319
380
  this.logger.debug?.("remote relay transport opened; awaiting device challenge");
381
+ this.observeConnectStage("websocket_open");
382
+ this.finishConnectAttempt();
320
383
  this.clearTimer("handshakeTimer", "clearTimeout");
321
384
  this.handshakeTimer = this.scheduler.setTimeout(() => {
322
385
  if (this.socket !== socket || this.closed || this.ready) return;
323
386
  this.logger.debug?.("remote relay authentication acknowledgement timed out", { timeout_ms: this.handshakeTimeoutMs });
324
- this.pendingCloseCategory = "relay_handshake_timeout";
387
+ this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_handshake_timeout");
325
388
  terminateSocket(socket);
326
389
  }, this.handshakeTimeoutMs);
327
390
  this.handshakeTimer?.unref?.();
@@ -329,12 +392,13 @@ export class RelayConnection {
329
392
 
330
393
  socket.on("message", (data) => {
331
394
  if (this.socket !== socket || this.closed) return;
332
- this.lastInboundAt = this.now();
395
+ this.liveness.observeApplicationInbound();
333
396
  // Bind results to the generation that received this message.
334
397
  const relayContext = {
335
398
  sessionId: this.activeSessionId,
336
399
  authenticated: this.authenticated === true,
337
400
  ready: this.ready === true,
401
+ transport: "websocket",
338
402
  };
339
403
  try {
340
404
  const outcome = this.onMessage(data, relayContext);
@@ -346,25 +410,37 @@ export class RelayConnection {
346
410
  }
347
411
  });
348
412
 
413
+ socket.on("pong", () => {
414
+ if (this.socket !== socket || this.closed) return;
415
+ this.liveness.observeInbound();
416
+ });
417
+
349
418
  socket.on("close", (code, reason) => {
350
419
  if (this.socket !== socket) return;
351
420
  const wasReady = this.ready;
352
421
  const wasAuthenticated = this.authenticated;
422
+ const readyInboundSilenceMs = this.liveness.silenceMs(this.now());
353
423
  this.socket = null;
354
424
  this.authenticated = false;
355
425
  this.ready = false;
356
426
  this.readinessProbeDelivered = false;
357
427
  this.activeSessionId = 0;
428
+ this.liveness.stop();
358
429
  this.clearTimer("connectTimer", "clearTimeout");
359
- this.clearTimer("heartbeatTimer", "clearInterval");
360
430
  this.clearTimer("handshakeTimer", "clearTimeout");
361
431
  this.clearTimer("readinessTimer", "clearTimeout");
362
432
  const reasonText = sanitizeCloseReason(reason);
363
433
  const category = this.pendingCloseCategory || relayCloseCategory(code, reasonText);
364
434
  this.pendingCloseCategory = "";
365
- const connectedForMs = wasAuthenticated && this.connectedAt > 0 ? Math.max(0, this.now() - this.connectedAt) : 0;
435
+ if (category !== "relay_transport_error") this.lastTransportErrorClass = "";
436
+ const disconnectedAt = this.now();
437
+ const connectedForMs = wasAuthenticated && this.connectedAt > 0 ? Math.max(0, disconnectedAt - this.connectedAt) : 0;
438
+ if (wasReady) this.lastReadyInboundSilenceMs = readyInboundSilenceMs;
439
+ this.lastDisconnectedAt = this.wallNow();
440
+ if (wasReady && this.lastReadyAt > 0) this.lastReadyDurationMs = Math.max(0, disconnectedAt - this.lastReadyAt);
441
+ this.lastCloseCode = Number(code) || 0;
366
442
  this.logger.debug?.("remote relay transport closed", {
367
- close_code: Number(code) || 0,
443
+ close_code: this.lastCloseCode,
368
444
  close_reason: reasonText || "<none>",
369
445
  category,
370
446
  ready: wasReady,
@@ -387,10 +463,10 @@ export class RelayConnection {
387
463
  this.clearTimer("reconnectTimer", "clearTimeout");
388
464
  this.clearTimer("outageWarnTimer", "clearTimeout");
389
465
  this.logger.warn?.("daemon connection was replaced by a newer verified instance");
390
- queueMicrotask(() => {
391
- try { this.onSuperseded(); } catch (error) {
392
- this.logger.error?.("daemon superseded callback failed", { error_class: classifyOperationalError(error) });
393
- }
466
+ queueMicrotask(async () => {
467
+ try {
468
+ await this.onSuperseded();
469
+ } catch (error) { this.logger.error?.("daemon superseded callback failed", { error_class: classifyOperationalError(error) }); }
394
470
  });
395
471
  return;
396
472
  }
@@ -402,12 +478,18 @@ export class RelayConnection {
402
478
  socket.on("error", (error) => {
403
479
  if (this.socket !== socket || this.closed) return;
404
480
  this.lastTransportErrorClass = classifyRelayTransportError(error);
481
+ const httpStatus = relayHttpStatusFromError(error);
482
+ if (httpStatus) {
483
+ this.lastConnectStage = "http_rejected";
484
+ this.lastConnectHttpStatus = httpStatus;
485
+ }
486
+ this.finishConnectAttempt();
405
487
  this.logger.debug?.("remote relay transport error", { error_class: this.lastTransportErrorClass });
406
488
  if (this.lastTransportErrorClass === "authentication_failed") {
407
489
  this.failPermanently("relay_authentication_failed");
408
490
  return;
409
491
  }
410
- this.pendingCloseCategory = "relay_transport_error";
492
+ this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_transport_error");
411
493
  terminateSocket(socket);
412
494
  });
413
495
  }
@@ -420,9 +502,9 @@ export class RelayConnection {
420
502
  this.ready = false;
421
503
  this.readinessProbeDelivered = false;
422
504
  this.activeSessionId = 0;
505
+ this.liveness.stop();
423
506
  this.socket = null;
424
507
  this.clearTimer("connectTimer", "clearTimeout");
425
- this.clearTimer("heartbeatTimer", "clearInterval");
426
508
  this.clearTimer("handshakeTimer", "clearTimeout");
427
509
  this.clearTimer("readinessTimer", "clearTimeout");
428
510
  this.clearTimer("reconnectTimer", "clearTimeout");
@@ -446,21 +528,33 @@ export class RelayConnection {
446
528
  }
447
529
  this.logger.error?.(message);
448
530
  this.logger.debug?.("remote relay fatal details", { category, cause: relayCloseUserCause(category) });
449
- queueMicrotask(() => {
450
- try { this.onFatal(error); } catch (callbackError) {
451
- this.logger.error?.("relay fatal callback failed", { error_class: classifyOperationalError(callbackError) });
452
- }
531
+ queueMicrotask(async () => {
532
+ try {
533
+ await this.onFatal(error);
534
+ } catch (callbackError) { this.logger.error?.("relay fatal callback failed", { error_class: classifyOperationalError(callbackError) }); }
453
535
  });
454
536
  }
455
537
 
456
538
  scheduleReconnect(category) {
457
539
  if (this.closed || this.reconnectTimer) return;
458
540
  this.recordOutage(category);
459
- const delay = this.reconnectDelay(this.reconnectAttempt++);
541
+ const delay = this.reconnectDelay(this.reconnectAttempt++, Math.random, this.lastConnectDurationMs, this.connectTimeoutMs);
542
+ this.lastReconnectDelayMs = delay;
543
+ this.nextReconnectAt = this.now() + delay;
544
+ this.nextReconnectWallAt = this.wallNow() + delay;
460
545
  this.scheduleOutageWarning();
461
- this.logger.debug?.("scheduling daemon reconnect", { delay_ms: delay, attempt: this.outageAttempts });
546
+ this.logger.debug?.("scheduling daemon reconnect", {
547
+ delay_ms: delay,
548
+ next_reconnect_at: new Date(this.nextReconnectWallAt).toISOString(),
549
+ attempt: this.outageAttempts,
550
+ close_category: this.lastCloseCategory,
551
+ network_route: this.networkRoute,
552
+ network_route_scope: this.networkRouteScope,
553
+ });
462
554
  this.reconnectTimer = this.scheduler.setTimeout(() => {
463
555
  this.reconnectTimer = null;
556
+ this.nextReconnectAt = 0;
557
+ this.nextReconnectWallAt = 0;
464
558
  this.connect();
465
559
  }, delay);
466
560
  this.reconnectTimer?.unref?.();
@@ -473,34 +567,19 @@ export class RelayConnection {
473
567
  return true;
474
568
  } catch (error) {
475
569
  this.lastTransportErrorClass = classifyRelayTransportError(error);
476
- this.pendingCloseCategory = "relay_transport_error";
570
+ this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_transport_error");
477
571
  this.logger.debug?.("remote relay send failed", { error_class: this.lastTransportErrorClass });
478
572
  terminateSocket(socket);
479
573
  return false;
480
574
  }
481
575
  }
482
576
 
483
- startHeartbeat() {
484
- this.clearTimer("heartbeatTimer", "clearInterval");
485
- this.heartbeatTimer = this.scheduler.setInterval(() => {
486
- const socket = this.socket;
487
- if (this.closed || !this.authenticated || !this.isSocketOpen(socket)) return;
488
- const silentForMs = Math.max(0, this.now() - this.lastInboundAt);
489
- if (silentForMs >= this.heartbeatTimeoutMs) {
490
- this.logger.debug?.("remote relay heartbeat timed out", { silent_for_ms: silentForMs });
491
- this.pendingCloseCategory = "relay_heartbeat_timeout";
492
- terminateSocket(socket);
493
- return;
494
- }
495
- this.sendOnSocket(socket, { type: "heartbeat", ts: this.now() });
496
- }, this.heartbeatIntervalMs);
497
- this.heartbeatTimer?.unref?.();
498
- }
499
-
500
577
  recordOutage(category) {
501
578
  const now = this.now();
502
579
  if (this.outageStartedAt === 0) {
503
580
  this.outageStartedAt = now;
581
+ this.outageStartedWallAt = this.wallNow();
582
+ this.outageCount += 1;
504
583
  this.outageAttempts = 0;
505
584
  this.outageNoticeEmitted = false;
506
585
  this.lastOutageWarnAt = 0;
@@ -535,27 +614,22 @@ export class RelayConnection {
535
614
  this.outageWarningCount += 1;
536
615
  this.lastOutageWarnAt = this.now();
537
616
  const cause = relayCloseUserCause(this.lastCloseCategory);
538
- const action = outageMs >= 5 * 60_000
539
- ? " If this persists, check internet access and the deployed Worker."
540
- : "";
541
- this.logger.warn?.(`remote relay unavailable for ${formatDuration(outageMs)}; reconnecting automatically (${formatAttempts(this.outageAttempts)}; ${cause}).${action}`);
542
- this.logger.debug?.("remote relay outage details", {
543
- outage_seconds: roundSeconds(outageMs),
544
- attempts: this.outageAttempts,
545
- cause,
546
- ...(this.lastTransportErrorClass ? { error_class: this.lastTransportErrorClass } : {}),
547
- });
617
+ const action = relayOutageUserAction(this.lastCloseCategory, outageMs);
618
+ const outageFields = relayOutageFields(this, this.now(), cause);
619
+ this.logger.warn?.(`remote relay unavailable for ${formatDuration(outageMs)}; reconnecting automatically (${formatAttempts(this.outageAttempts)}; ${cause}).${action}`, outageFields);
620
+ this.logger.debug?.("remote relay outage details", outageFields);
548
621
  }
549
622
 
550
623
  resetOutage() {
551
624
  this.clearTimer("outageWarnTimer", "clearTimeout");
552
625
  this.outageStartedAt = 0;
626
+ this.outageStartedWallAt = 0;
553
627
  this.outageAttempts = 0;
554
628
  this.outageNoticeEmitted = false;
555
629
  this.outageWarningCount = 0;
556
630
  this.lastOutageWarnAt = 0;
557
- this.lastCloseCategory = "connection_interrupted";
558
- this.lastTransportErrorClass = "";
631
+ this.nextReconnectAt = 0;
632
+ this.nextReconnectWallAt = 0;
559
633
  this.pendingCloseCategory = "";
560
634
  }
561
635
 
@@ -569,182 +643,15 @@ export class RelayConnection {
569
643
  isSocketOpen(socket) {
570
644
  return Boolean(socket && socket.readyState === this.WebSocketClass.OPEN);
571
645
  }
572
- }
573
-
574
- export function relayCloseCategory(code, reason = "") {
575
- const numeric = Number(code);
576
- const reasonText = String(reason || "");
577
- if (isSupersededClose(numeric, reasonText)) return "superseded";
578
- if (numeric === 1008 && reasonText === "daemon hello timeout") return "relay_handshake_timeout";
579
- if (numeric === 1008 && reasonText === "daemon ready timeout") return "relay_readiness_timeout";
580
- if (numeric === 1008 && ["stale daemon candidate", "expired daemon candidate"].includes(reasonText)) return "relay_restarting_or_unavailable";
581
- if (numeric === 1008 && ["daemon hello required", "missing daemon attachment", "invalid daemon candidate timestamp"].includes(reasonText)) return "relay_protocol_error";
582
- if (numeric === 1000) return "normal_close";
583
- if (numeric === 1001 || numeric === 1012 || numeric === 1013) return "relay_restarting_or_unavailable";
584
- if (numeric === 1006) return "connection_interrupted";
585
- if (numeric === 1002) return "relay_protocol_error";
586
- if (numeric === 1007) return "invalid_transport_payload";
587
- if (numeric === 1008) return "relay_policy_rejected";
588
- if (numeric === 1009) return "message_too_large";
589
- if (numeric === 1011) return "relay_internal_error";
590
- return "unexpected_close";
591
- }
592
-
593
- function relayFatalMessage(category) {
594
- if (category === "relay_protocol_mismatch") {
595
- return "remote relay identity or version does not match this daemon; upgrade and redeploy both components";
596
- }
597
- if (category === "relay_protocol_error") {
598
- return "remote relay protocol error; upgrade and redeploy both components, then restart the daemon";
599
- }
600
- if (category === "relay_proxy_configuration") {
601
- return "remote relay proxy configuration is invalid; check HTTP_PROXY, HTTPS_PROXY, and NO_PROXY";
602
- }
603
- return "remote relay rejected the daemon connection; verify credentials or redeploy the Worker";
604
- }
605
-
606
- function relayCloseUserCause(category) {
607
- const causes = {
608
- connection_interrupted: "connection interrupted",
609
- relay_restarting_or_unavailable: "relay restarting or temporarily unavailable",
610
- relay_policy_rejected: "relay rejected the connection",
611
- relay_internal_error: "relay internal error",
612
- relay_protocol_mismatch: "relay identity or version mismatch",
613
- relay_authentication_failed: "relay authentication failed",
614
- relay_connect_timeout: "relay connection attempt timed out",
615
- relay_handshake_timeout: "relay authentication acknowledgement timed out",
616
- relay_readiness_timeout: "end-to-end relay readiness verification timed out",
617
- relay_heartbeat_timeout: "relay stopped responding",
618
- relay_transport_error: "relay transport error",
619
- relay_protocol_error: "relay protocol error",
620
- relay_proxy_configuration: "relay proxy configuration invalid",
621
- invalid_transport_payload: "invalid transport payload",
622
- message_too_large: "message exceeded the relay limit",
623
- normal_close: "connection closed",
624
- unexpected_close: "unexpected connection close",
625
- superseded: "connection superseded",
626
- };
627
- return causes[String(category || "")] || "connection interrupted";
628
- }
629
-
630
- export function welcomeMismatch(message, expectedServer = "", expectedVersion = "") {
631
- if (!message || typeof message !== "object" || Array.isArray(message)) return "invalid_welcome";
632
- if (message.type !== "welcome") return "unexpected_welcome_type";
633
- if (expectedServer && message.server !== expectedServer) return "server_identity_mismatch";
634
- if (expectedVersion && message.version !== expectedVersion) return "server_version_mismatch";
635
- if (typeof message.server !== "string" || !message.server || typeof message.version !== "string" || !message.version) return "incomplete_welcome";
636
- return "";
637
- }
638
-
639
- export function acknowledgementMismatch(message, expectedServer = "", expectedVersion = "") {
640
- if (!message || typeof message !== "object" || Array.isArray(message)) return "invalid_acknowledgement";
641
- if (message.type !== "hello_ack") return "unexpected_acknowledgement_type";
642
- if (expectedServer && message.server !== expectedServer) return "server_identity_mismatch";
643
- if (expectedVersion && message.version !== expectedVersion) return "server_version_mismatch";
644
- if (typeof message.server !== "string" || !message.server || typeof message.version !== "string" || !message.version) return "incomplete_acknowledgement";
645
- return "";
646
- }
647
-
648
- export function readinessMismatch(message, expectedServer = "", expectedVersion = "") {
649
- if (!message || typeof message !== "object" || Array.isArray(message)) return "invalid_readiness_acknowledgement";
650
- if (message.type !== "ready_ack") return "unexpected_readiness_acknowledgement_type";
651
- if (expectedServer && message.server !== expectedServer) return "server_identity_mismatch";
652
- if (expectedVersion && message.version !== expectedVersion) return "server_version_mismatch";
653
- if (typeof message.server !== "string" || !message.server || typeof message.version !== "string" || !message.version) return "incomplete_readiness_acknowledgement";
654
- return "";
655
- }
656
-
657
- export function isRelayReadyContext(relayContext = {}, relay = null) {
658
- if (relayContext?.ready === true) return true;
659
- if (relayContext?.ready === false) return false;
660
- return Number(relayContext?.sessionId) > 0 && relay?.status?.()?.ready === true;
661
- }
662
-
663
- export function isSupersededClose(code, reason) {
664
- return Number(code) === 1012 && String(reason || "") === "replaced by verified daemon";
665
- }
666
646
 
667
- export function reconnectDelay(attempt, random = Math.random) {
668
- const safeAttempt = Math.max(0, Number.isFinite(Number(attempt)) ? Number(attempt) : 0);
669
- const base = Math.min(3000 * (2 ** Math.min(safeAttempt, 5)), 60_000);
670
- return base + Math.floor(random() * 1000);
671
- }
672
-
673
- function normalizeWorkerUrl(value) {
674
- let url;
675
- try { url = new URL(String(value || "")); } catch { throw new Error("invalid Worker URL"); }
676
- if (url.protocol !== "https:") throw new Error("Worker URL must use HTTPS");
677
- if (url.username || url.password) throw new Error("Worker URL must not contain credentials");
678
- if (url.pathname !== "/" || url.search || url.hash) throw new Error("Worker URL must be an origin without a path, query, or fragment");
679
- return url.origin;
680
- }
681
-
682
- function sanitizeCloseReason(value) {
683
- let text;
684
- try { text = Buffer.isBuffer(value) ? value.toString("utf8") : String(value || ""); } catch { text = ""; }
685
- return text.replace(/[\r\n\t\u0000-\u001f\u007f]/g, " ").trim().slice(0, MAX_CLOSE_REASON_CHARS);
686
- }
687
-
688
- function terminateSocket(socket) {
689
- try {
690
- if (typeof socket?.terminate === "function") socket.terminate();
691
- else socket?.close?.();
692
- } catch {}
693
- }
694
-
695
- function redactUrl(value) {
696
- try {
697
- const url = new URL(String(value));
698
- return `${url.protocol}//${url.host}${url.pathname}`;
699
- } catch {
700
- return "<relay-url>";
647
+ observeConnectStage(stage) {
648
+ if (!Number.isFinite(this.connectAttemptStartedAt)) return;
649
+ this.lastConnectStage = String(stage || "socket_constructing");
701
650
  }
702
- }
703
-
704
- function classifyRelayTransportError(error) {
705
- const directStatus = Number(error?.statusCode ?? error?.response?.statusCode);
706
- if (directStatus === 401 || directStatus === 403) return "authentication_failed";
707
- const match = /^Unexpected server response: (\d{3})$/.exec(String(error?.message || ""));
708
- if (match && [401, 403].includes(Number(match[1]))) return "authentication_failed";
709
- return classifyOperationalError(error);
710
- }
711
-
712
- function boundedPositiveInteger(value, fallback) {
713
- const number = Number(value);
714
- return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
715
- }
716
-
717
- function sanitizeProtocolErrorCode(value) {
718
- const code = String(value || "unknown_error").replace(/[^A-Za-z0-9_-]/g, "_").slice(0, MAX_PROTOCOL_ERROR_CODE_CHARS);
719
- return code || "unknown_error";
720
- }
721
651
 
722
- function formatAttempts(value) {
723
- const attempts = Math.max(1, Math.floor(Number(value) || 1));
724
- return `${attempts} reconnect attempt${attempts === 1 ? "" : "s"}`;
725
- }
726
-
727
- function formatDuration(milliseconds) {
728
- let seconds = Math.max(1, Math.round(Number(milliseconds) / 1000));
729
- const units = [
730
- ["day", 86_400],
731
- ["hour", 3_600],
732
- ["minute", 60],
733
- ["second", 1],
734
- ];
735
- const parts = [];
736
- for (const [label, size] of units) {
737
- if (seconds < size && parts.length === 0) continue;
738
- const amount = Math.floor(seconds / size);
739
- if (amount > 0) {
740
- parts.push(`${amount} ${label}${amount === 1 ? "" : "s"}`);
741
- seconds -= amount * size;
742
- }
743
- if (parts.length === 2) break;
652
+ finishConnectAttempt() {
653
+ if (!Number.isFinite(this.connectAttemptStartedAt)) return;
654
+ this.lastConnectDurationMs = Math.max(0, this.now() - this.connectAttemptStartedAt);
655
+ this.connectAttemptStartedAt = null;
744
656
  }
745
- return parts.join(" ") || "1 second";
746
- }
747
-
748
- function roundSeconds(milliseconds) {
749
- return Math.max(1, Math.round(milliseconds / 1000));
750
657
  }