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,26 +1,63 @@
1
+ import type { AuthorityRevocation } from "../shared/authority-revocation.mjs";
2
+ import type { DaemonChannel } from "./daemon-channel.ts";
3
+
4
+ export type PendingCallOutcome =
5
+ | { ok: true; value: unknown }
6
+ | { ok: false; error: Error };
7
+
8
+ export type PendingCallSettlement = {
9
+ resolve: (value: unknown) => void;
10
+ reject: (error: Error) => void;
11
+ };
12
+
13
+ export class PendingCallRegistrationError extends Error {
14
+ readonly code: "conflict" | "limit_exceeded";
15
+ readonly retryable: boolean;
16
+ constructor(code: "conflict" | "limit_exceeded", message: string, retryable = false) {
17
+ super(message);
18
+ this.name = "PendingCallRegistrationError";
19
+ this.code = code;
20
+ this.retryable = retryable;
21
+ }
22
+ }
23
+
1
24
  export interface PendingCallRecord {
2
25
  id: string;
3
- socket?: WebSocket;
26
+ socket?: DaemonChannel;
4
27
  daemonInstanceId?: string;
5
28
  reconnectTimeout?: ReturnType<typeof setTimeout>;
29
+ reconnectDeadlineAt?: number;
30
+ onReconnectTimeout?: (record: PendingCallRecord) => Error;
6
31
  clientRequestKey?: string;
32
+ owner_kind?: "account";
33
+ owner_account_id?: string;
34
+ owner_account_version?: number;
35
+ owner_client_id?: string;
36
+ owner_family_id?: string;
7
37
  tool: string;
38
+ recovery?: Record<string, unknown>;
8
39
  startedAt: number;
9
- timeout: ReturnType<typeof setTimeout>;
10
- resolve: (value: unknown) => void;
11
- reject: (error: Error) => void;
40
+ timeout?: ReturnType<typeof setTimeout>;
41
+ deadlineAt: number;
42
+ remainingTimeoutMs: number;
43
+ onTimeout: (record: PendingCallRecord) => Error;
44
+ redeliverAfterProvenMissing?: (record: PendingCallRecord, channel: DaemonChannel) => boolean;
45
+ settlement: PendingCallSettlement;
12
46
  signal?: AbortSignal;
13
47
  abortHandler?: () => void;
14
48
  }
15
49
 
16
50
  export interface RegisterPendingCall {
17
51
  id: string;
18
- socket: WebSocket;
52
+ socket: DaemonChannel;
19
53
  daemonInstanceId?: string;
20
54
  clientRequestKey?: string;
55
+ authority?: AuthorityRevocation;
21
56
  tool: string;
57
+ recovery?: Record<string, unknown>;
22
58
  timeoutMs: number;
23
59
  onTimeout: (record: PendingCallRecord) => Error;
60
+ redeliverAfterProvenMissing?: (record: PendingCallRecord, channel: DaemonChannel) => boolean;
24
61
  signal?: AbortSignal;
25
62
  onAbort?: (record: PendingCallRecord) => Error;
26
63
  }
@@ -0,0 +1,75 @@
1
+ import type { PendingCallRecord } from "./pending-call-contract.ts";
2
+
3
+ type TimerHandle = ReturnType<typeof setTimeout>;
4
+ export type PendingCallDeadlineOptions = {
5
+ now?: () => number;
6
+ scheduler?: {
7
+ setTimeout: (callback: () => void, delay: number) => TimerHandle;
8
+ clearTimeout: (handle: TimerHandle) => void;
9
+ };
10
+ };
11
+
12
+ export class PendingCallDeadlines {
13
+ private readonly clock: () => number;
14
+ private readonly scheduler: Required<PendingCallDeadlineOptions>["scheduler"];
15
+
16
+ constructor(options: PendingCallDeadlineOptions = {}) {
17
+ this.clock = options.now ?? (() => performance.now());
18
+ this.scheduler = options.scheduler ?? { setTimeout, clearTimeout };
19
+ }
20
+
21
+ now(): number {
22
+ return this.clock();
23
+ }
24
+
25
+ nextDelayMs(record: PendingCallRecord): number {
26
+ const deadline = record.socket ? record.deadlineAt : record.reconnectDeadlineAt;
27
+ return Number.isFinite(deadline) ? Math.max(0, Number(deadline) - this.clock()) : Number.POSITIVE_INFINITY;
28
+ }
29
+
30
+ isDue(record: PendingCallRecord, now = this.clock()): boolean {
31
+ const deadline = record.socket ? record.deadlineAt : record.reconnectDeadlineAt;
32
+ return Number.isFinite(deadline) && Number(deadline) <= now;
33
+ }
34
+
35
+ armOperation(record: PendingCallRecord, delayMs: number, expire: (id: string) => void): void {
36
+ if (record.timeout) this.scheduler.clearTimeout(record.timeout);
37
+ const delay = boundedPendingDelayMs(delayMs);
38
+ record.remainingTimeoutMs = delay;
39
+ record.deadlineAt = this.clock() + delay;
40
+ record.timeout = this.scheduler.setTimeout(() => expire(record.id), delay);
41
+ }
42
+
43
+ pauseOperation(record: PendingCallRecord): void {
44
+ record.remainingTimeoutMs = Math.max(1, Math.ceil(record.deadlineAt - this.clock()));
45
+ if (record.timeout) this.scheduler.clearTimeout(record.timeout);
46
+ record.timeout = undefined;
47
+ }
48
+
49
+ armReconnect(record: PendingCallRecord, delayMs: number, expire: (id: string) => void): void {
50
+ if (record.reconnectTimeout) this.scheduler.clearTimeout(record.reconnectTimeout);
51
+ const delay = boundedPendingDelayMs(delayMs);
52
+ record.reconnectDeadlineAt = this.clock() + delay;
53
+ record.reconnectTimeout = this.scheduler.setTimeout(() => expire(record.id), delay);
54
+ }
55
+
56
+ clearReconnect(record: PendingCallRecord): void {
57
+ if (record.reconnectTimeout) this.scheduler.clearTimeout(record.reconnectTimeout);
58
+ record.reconnectTimeout = undefined;
59
+ record.reconnectDeadlineAt = undefined;
60
+ }
61
+
62
+ clear(record: PendingCallRecord): void {
63
+ if (record.timeout) this.scheduler.clearTimeout(record.timeout);
64
+ if (record.reconnectTimeout) this.scheduler.clearTimeout(record.reconnectTimeout);
65
+ record.reconnectDeadlineAt = undefined;
66
+ }
67
+ }
68
+
69
+ export function boundedPendingDelayMs(value: unknown, maximum = 2_147_483_647): number {
70
+ if (!Number.isSafeInteger(maximum) || maximum < 1) throw new TypeError("pending-call delay maximum must be a positive safe integer");
71
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > maximum) {
72
+ throw new RangeError(`pending-call delay must be an integer from 1 to ${maximum}`);
73
+ }
74
+ return value;
75
+ }
@@ -1,173 +1,179 @@
1
- import type { PendingCallRecord, RegisterPendingCall } from "./pending-call-contract.ts";
2
-
3
- export class PendingCallRegistrationError extends Error {
4
- readonly code: "conflict" | "limit_exceeded";
5
- readonly retryable: boolean;
6
-
7
- constructor(code: "conflict" | "limit_exceeded", message: string, retryable = false) {
8
- super(message);
9
- this.name = "PendingCallRegistrationError";
10
- this.code = code;
11
- this.retryable = retryable;
12
- }
13
- }
14
-
1
+ import { toolCallAdmission, toolCallCapacityConfig, type ToolCallCapacityConfig } from "../shared/tool-call-capacity.mjs";
2
+ import relayContract from "../shared/relay-contract.json" with { type: "json" };
3
+ import { PendingCallRegistrationError, type PendingCallOutcome, type PendingCallRecord, type PendingCallSettlement, type RegisterPendingCall } from "./pending-call-contract.ts";
4
+ import { boundedPendingDelayMs, PendingCallDeadlines, type PendingCallDeadlineOptions } from "./pending-call-deadlines.ts";
5
+ import { pendingRegistrySnapshot } from "./pending-call-capacity.ts";
6
+ import { recordMatchesAuthorityRevocation, type AuthorityRevocation } from "../shared/authority-revocation.mjs";
7
+ import type { DaemonChannel } from "./daemon-channel.ts";
8
+ type PendingCallRegistryOptions = PendingCallDeadlineOptions & {
9
+ reservedCapacity?: number;
10
+ reservedTools?: Iterable<string>;
11
+ };
15
12
  export class PendingCallRegistry {
16
- private readonly maximum: number;
13
+ private readonly capacity: ToolCallCapacityConfig;
17
14
  private readonly byId = new Map<string, PendingCallRecord>();
18
15
  private readonly byRequestKey = new Map<string, string>();
19
-
20
- constructor(maximum: number) {
21
- this.maximum = maximum;
16
+ private readonly deadlines: PendingCallDeadlines;
17
+ constructor(maximum: number, options: PendingCallRegistryOptions = {}) {
18
+ this.capacity = toolCallCapacityConfig(maximum, options.reservedCapacity, options.reservedTools);
19
+ this.deadlines = new PendingCallDeadlines(options);
22
20
  }
23
-
24
- get size(): number {
25
- return this.byId.size;
21
+ get size(): number { return this.byId.size; } hasRequestKey(requestKey?: string): boolean { return Boolean(requestKey && this.byRequestKey.has(requestKey)); }
22
+ resultOwnership(id: string, socket: DaemonChannel): "owned" | "missing" | "stale" {
23
+ const record = this.byId.get(id); return !record ? "missing" : record.socket === socket ? "owned" : "stale";
26
24
  }
27
-
28
- hasRequestKey(requestKey?: string): boolean {
29
- return Boolean(requestKey && this.byRequestKey.has(requestKey));
25
+ nextDeadlineDelayMs(): number { return Math.min(Number.POSITIVE_INFINITY, ...[...this.byId.values()].map((record) => this.deadlines.nextDelayMs(record))); }
26
+ async expireDue(now = this.deadlines.now()): Promise<number> {
27
+ const due = [...this.byId.values()].filter((record) => this.deadlines.isDue(record, now));
28
+ let expired = 0;
29
+ for (const record of due) expired += Number(await this.expireRecord(record));
30
+ return expired;
30
31
  }
31
32
 
32
33
  register(input: RegisterPendingCall): Promise<unknown> {
33
- if (this.byId.size >= this.maximum) throw new PendingCallRegistrationError("limit_exceeded", "too many concurrent daemon tool calls", true);
34
- if (this.byId.has(input.id)) throw new PendingCallRegistrationError("conflict", "duplicate internal daemon call id");
35
- if (input.clientRequestKey && this.byRequestKey.has(input.clientRequestKey)) {
36
- throw new PendingCallRegistrationError("conflict", "duplicate in-flight JSON-RPC request id within this MCP session");
37
- }
38
- const startedAt = performance.now();
39
- return new Promise((resolve, reject) => {
40
- const timeout = setTimeout(() => {
41
- const record = this.take(input.id);
42
- if (!record) return;
43
- let error: unknown;
44
- try { error = input.onTimeout(record); }
45
- catch { error = new Error("pending daemon call timed out"); }
46
- reject(error instanceof Error ? error : new Error("pending daemon call timed out"));
47
- }, input.timeoutMs);
48
- const abortHandler = () => {
49
- const record = this.take(input.id);
50
- if (!record) return;
51
- let error: unknown;
52
- try { error = input.onAbort?.(record); }
53
- catch { error = new Error("pending daemon call was cancelled"); }
54
- reject(error instanceof Error ? error : new Error("pending daemon call was cancelled"));
55
- };
56
- const record: PendingCallRecord = {
57
- id: input.id,
58
- socket: input.socket,
59
- daemonInstanceId: input.daemonInstanceId,
60
- clientRequestKey: input.clientRequestKey,
61
- tool: String(input.tool || "unknown"),
62
- startedAt,
63
- timeout,
64
- resolve,
65
- reject,
66
- signal: input.signal,
67
- abortHandler: input.signal ? abortHandler : undefined,
68
- };
69
- this.byId.set(input.id, record);
70
- if (input.clientRequestKey) this.byRequestKey.set(input.clientRequestKey, input.id);
71
- if (input.signal?.aborted) abortHandler();
72
- else input.signal?.addEventListener("abort", abortHandler, { once: true });
73
- });
34
+ this.assertCanRegister(input);
35
+ let resolveResult!: (value: unknown) => void;
36
+ let rejectResult!: (error: Error) => void;
37
+ const result = new Promise<unknown>((resolve, reject) => { resolveResult = resolve; rejectResult = reject; });
38
+ this.add(input, { resolve: resolveResult, reject: rejectResult });
39
+ return result;
74
40
  }
75
-
76
- resolve(id: string, socket: WebSocket, value: unknown): boolean {
77
- const record = this.byId.get(id);
78
- if (!record || record.socket !== socket) return false;
79
- this.take(id)?.resolve(value);
80
- return true;
41
+ resolve(id: string, socket: DaemonChannel, value: unknown): Promise<boolean> {
42
+ const record = this.byId.get(id); return !record || record.socket !== socket ? Promise.resolve(false) : this.finish(id, { ok: true, value });
43
+ }
44
+ reject(id: string, error: Error, socket?: DaemonChannel): Promise<boolean> {
45
+ const record = this.byId.get(id); return !record || (socket && record.socket !== socket) ? Promise.resolve(false) : this.finish(id, { ok: false, error });
81
46
  }
82
47
 
83
- reject(id: string, error: Error, socket?: WebSocket): boolean {
84
- const record = this.byId.get(id);
85
- if (!record || (socket && record.socket !== socket)) return false;
86
- this.take(id)?.reject(error);
87
- return true;
48
+ async cancelRequest(requestKey: string, onCancel: (record: PendingCallRecord) => Error): Promise<boolean> {
49
+ const id = this.byRequestKey.get(requestKey); return id ? this.fail(id, onCancel, "pending daemon call was cancelled") : false;
88
50
  }
89
51
 
90
- cancelRequest(requestKey: string, onCancel: (record: PendingCallRecord) => Error): boolean {
91
- const id = this.byRequestKey.get(requestKey);
92
- if (!id) return false;
93
- const record = this.take(id);
94
- if (!record) return false;
95
- record.reject(onCancel(record));
96
- return true;
52
+ async cancelAuthority(revocation: AuthorityRevocation, onCancel: (record: PendingCallRecord) => Error): Promise<number> {
53
+ const ids = [...this.byId.values()].filter((record) => recordMatchesAuthorityRevocation(record, revocation)).map((record) => record.id);
54
+ let cancelled = 0;
55
+ for (const id of ids) cancelled += Number(await this.fail(id, onCancel, "pending daemon authority was revoked"));
56
+ return cancelled;
97
57
  }
98
58
 
99
- rejectSocket(socket: WebSocket, createError: (record: PendingCallRecord) => Error): number {
59
+ async rejectSocket(socket: DaemonChannel, createError: (record: PendingCallRecord) => Error): Promise<number> {
100
60
  const ids = [...this.byId.values()].filter((record) => record.socket === socket).map((record) => record.id);
61
+ return this.rejectSocketIds(ids, socket, createError, "pending daemon call failed");
62
+ }
63
+
64
+ async rejectSocketIds(ids: Iterable<string>, socket: DaemonChannel, createError: (record: PendingCallRecord) => Error, fallback = "pending daemon call was not received", beforeReject?: (record: PendingCallRecord) => boolean): Promise<number> {
65
+ let rejected = 0;
101
66
  for (const id of ids) {
102
- const record = this.take(id);
103
- if (record) record.reject(createError(record));
67
+ const record = this.byId.get(id); if (record?.socket === socket && beforeReject?.(record) !== true) rejected += Number(await this.fail(id, createError, fallback));
104
68
  }
105
- return ids.length;
69
+ return rejected;
106
70
  }
107
71
 
108
- detachSocket(
109
- socket: WebSocket,
110
- graceMs: number,
111
- createError: (record: PendingCallRecord) => Error,
112
- ): number {
72
+ detachSocket(socket: DaemonChannel, graceMs: number, createError: (record: PendingCallRecord) => Error): number {
113
73
  const records = [...this.byId.values()].filter((record) => record.socket === socket);
114
- const delay = Math.max(1, Math.floor(Number(graceMs) || 1));
74
+ const maximumGrace = boundedPendingDelayMs(graceMs, relayContract.reconnectGraceMs);
115
75
  for (const record of records) {
116
76
  record.socket = undefined;
117
- if (record.reconnectTimeout) clearTimeout(record.reconnectTimeout);
118
- record.reconnectTimeout = setTimeout(() => {
119
- const current = this.byId.get(record.id);
120
- if (!current || current.socket) return;
121
- const expired = this.take(record.id);
122
- if (expired) expired.reject(createError(expired));
123
- }, delay);
77
+ record.onReconnectTimeout = createError;
78
+ this.deadlines.pauseOperation(record);
79
+ const delay = Math.min(maximumGrace, record.remainingTimeoutMs);
80
+ this.deadlines.armReconnect(record, delay, (id) => { void this.expireReconnect(id); });
124
81
  }
125
82
  return records.length;
126
83
  }
127
84
 
128
- rebindInstance(daemonInstanceId: string, socket: WebSocket): string[] {
85
+ rebindInstance(daemonInstanceId: string, socket: DaemonChannel): string[] {
129
86
  if (!daemonInstanceId) return [];
130
87
  const rebound: string[] = [];
131
88
  for (const record of this.byId.values()) {
132
- if (record.socket || record.daemonInstanceId !== daemonInstanceId) continue;
133
- if (record.reconnectTimeout) clearTimeout(record.reconnectTimeout);
134
- record.reconnectTimeout = undefined;
89
+ if (record.daemonInstanceId !== daemonInstanceId || record.socket === socket) continue;
90
+ if (record.socket) this.deadlines.pauseOperation(record);
91
+ else this.deadlines.clearReconnect(record);
92
+ const remainingTimeoutMs = Math.max(1, Math.ceil(record.deadlineAt - this.deadlines.now()));
93
+ record.onReconnectTimeout = undefined;
135
94
  record.socket = socket;
95
+ this.deadlines.armOperation(record, remainingTimeoutMs, (id) => { void this.expireOperation(id); });
136
96
  rebound.push(record.id);
137
97
  }
138
98
  return rebound;
139
99
  }
140
100
 
141
- snapshot(): { active: number; detached: number; request_keys: number; maximum: number; oldest_ms: number; by_tool: Record<string, number> } {
142
- const now = performance.now();
143
- const byTool: Record<string, number> = {};
144
- let detached = 0;
145
- let oldestMs = 0;
146
- for (const record of this.byId.values()) {
147
- byTool[record.tool] = (byTool[record.tool] || 0) + 1;
148
- if (!record.socket) detached += 1;
149
- oldestMs = Math.max(oldestMs, now - record.startedAt);
101
+ snapshot() {
102
+ return pendingRegistrySnapshot(this.byId.values(), this.byRequestKey.size, this.deadlines.now(), this.capacity);
103
+ }
104
+
105
+ private assertCanRegister(input: RegisterPendingCall): void {
106
+ const snapshot = this.snapshot();
107
+ const decision = toolCallAdmission({ active: snapshot.active, byTool: snapshot.by_tool }, this.capacity, input.tool);
108
+ if (!decision.allowed) {
109
+ const message = decision.reason === "ordinary_capacity"
110
+ ? `ordinary daemon-call capacity reached (${decision.ordinaryMaximum}); control-plane capacity is reserved for diagnosis and recovery`
111
+ : "too many concurrent daemon tool calls";
112
+ throw new PendingCallRegistrationError("limit_exceeded", message, true);
150
113
  }
151
- return {
152
- active: this.byId.size,
153
- detached,
154
- request_keys: this.byRequestKey.size,
155
- maximum: this.maximum,
156
- oldest_ms: oldestMs,
157
- by_tool: byTool,
114
+ if (this.byId.has(input.id)) throw new PendingCallRegistrationError("conflict", "duplicate internal daemon call id");
115
+ if (input.clientRequestKey && this.byRequestKey.has(input.clientRequestKey)) {
116
+ throw new PendingCallRegistrationError("conflict", "duplicate in-flight response-stream request key");
117
+ }
118
+ }
119
+
120
+ private add(input: RegisterPendingCall, settlement: PendingCallSettlement): void {
121
+ const startedAt = this.deadlines.now();
122
+ const timeoutMs = boundedPendingDelayMs(input.timeoutMs, relayContract.maximumRelayToolTimeoutMs);
123
+ const abortHandler = input.signal ? () => { void this.expire(input.id, input.onAbort, "pending daemon call was cancelled"); } : undefined;
124
+ const record: PendingCallRecord = {
125
+ id: input.id, socket: input.socket, daemonInstanceId: input.daemonInstanceId, clientRequestKey: input.clientRequestKey,
126
+ ...(input.authority ? {
127
+ owner_kind: "account" as const, owner_account_id: input.authority.accountId,
128
+ owner_account_version: input.authority.accountVersion, owner_client_id: input.authority.clientId,
129
+ owner_family_id: input.authority.familyId,
130
+ } : {}),
131
+ tool: String(input.tool || "unknown"), ...(input.recovery ? { recovery: input.recovery } : {}),
132
+ startedAt, deadlineAt: startedAt + timeoutMs, remainingTimeoutMs: timeoutMs,
133
+ onTimeout: input.onTimeout, redeliverAfterProvenMissing: input.redeliverAfterProvenMissing, settlement, signal: input.signal, abortHandler,
158
134
  };
135
+ this.byId.set(input.id, record);
136
+ if (input.clientRequestKey) this.byRequestKey.set(input.clientRequestKey, input.id);
137
+ this.deadlines.armOperation(record, timeoutMs, (id) => { void this.expireOperation(id); });
138
+ if (input.signal?.aborted) void this.expire(input.id, input.onAbort, "pending daemon call was cancelled");
139
+ else if (input.signal && abortHandler) input.signal.addEventListener("abort", abortHandler, { once: true });
159
140
  }
160
141
 
142
+ private expireOperation(id: string): Promise<boolean> { return this.expireRecord(this.byId.get(id)); }
143
+ private expireReconnect(id: string): Promise<boolean> {
144
+ const record = this.byId.get(id); return record && !record.socket ? this.expireRecord(record) : Promise.resolve(false);
145
+ }
146
+ private expireRecord(record?: PendingCallRecord): Promise<boolean> {
147
+ if (!record) return Promise.resolve(false);
148
+ return record.socket
149
+ ? this.fail(record.id, record.onTimeout, "pending daemon call timed out")
150
+ : this.fail(record.id, record.onReconnectTimeout, "pending daemon reconnect expired");
151
+ }
152
+ private expire(id: string, createError: ((record: PendingCallRecord) => Error) | undefined, fallback: string): Promise<boolean> {
153
+ const record = this.byId.get(id);
154
+ const applicable = record && (fallback.includes("reconnect") ? !record.socket : true);
155
+ return applicable ? this.fail(id, createError, fallback) : Promise.resolve(false);
156
+ }
157
+ private async fail(id: string, createError: ((record: PendingCallRecord) => Error) | undefined, fallback: string): Promise<boolean> {
158
+ const record = this.byId.get(id);
159
+ if (!record) return false;
160
+ let error: Error;
161
+ try { error = createError?.(record) ?? new Error(fallback); } catch { error = new Error(fallback); }
162
+ return this.finish(id, { ok: false, error });
163
+ }
164
+ private async finish(id: string, outcome: PendingCallOutcome): Promise<boolean> {
165
+ const record = this.take(id);
166
+ if (!record) return false;
167
+ outcome.ok ? record.settlement.resolve(outcome.value) : record.settlement.reject(outcome.error);
168
+ return true;
169
+ }
161
170
  private take(id: string): PendingCallRecord | undefined {
162
171
  const record = this.byId.get(id);
163
172
  if (!record) return undefined;
164
- clearTimeout(record.timeout);
165
- if (record.reconnectTimeout) clearTimeout(record.reconnectTimeout);
173
+ this.deadlines.clear(record);
166
174
  if (record.signal && record.abortHandler) record.signal.removeEventListener("abort", record.abortHandler);
167
175
  this.byId.delete(id);
168
- if (record.clientRequestKey && this.byRequestKey.get(record.clientRequestKey) === id) {
169
- this.byRequestKey.delete(record.clientRequestKey);
170
- }
176
+ if (record.clientRequestKey && this.byRequestKey.get(record.clientRequestKey) === id) this.byRequestKey.delete(record.clientRequestKey);
171
177
  return record;
172
178
  }
173
179
  }
@@ -0,0 +1,37 @@
1
+ export interface AlarmStorage {
2
+ getAlarm(): Promise<number | null>;
3
+ setAlarm(scheduledTime: number | Date): Promise<void>;
4
+ deleteAlarm(): Promise<void>;
5
+ }
6
+
7
+ export async function writeEarliestRuntimeAlarm(input: {
8
+ storage: AlarmStorage;
9
+ nextDeadline: number;
10
+ now: number;
11
+ onError: (error: unknown) => void;
12
+ onMutation?: (action: "set" | "delete" | "noop") => void;
13
+ currentAlarm?: number | null;
14
+ }): Promise<void> {
15
+ try {
16
+ const current = input.currentAlarm === undefined ? await input.storage.getAlarm() : input.currentAlarm;
17
+ if (!Number.isFinite(input.nextDeadline)) {
18
+ if (current === null) {
19
+ input.onMutation?.("noop");
20
+ return;
21
+ }
22
+ await input.storage.deleteAlarm();
23
+ input.onMutation?.("delete");
24
+ return;
25
+ }
26
+ const target = Math.max(input.now, input.nextDeadline);
27
+ if (current !== null && current > input.now && current <= target) {
28
+ input.onMutation?.("noop");
29
+ return;
30
+ }
31
+ await input.storage.setAlarm(target);
32
+ input.onMutation?.("set");
33
+ } catch (error) {
34
+ try { input.onError(error); }
35
+ catch { /* Alarm persistence already failed; observer failure cannot repair that storage outcome. */ }
36
+ }
37
+ }
@@ -0,0 +1,136 @@
1
+ import { DAEMON_HELLO_TIMEOUT_MS, daemonLivenessDeadlineMs, daemonReadyDeadlineMs } from "./daemon-liveness.ts";
2
+ import type { DaemonChannel } from "./daemon-channel.ts";
3
+ import type { DaemonRegistry } from "./daemon-registry.ts";
4
+ import type { PendingCallRegistry } from "./pending-calls.ts";
5
+ import { writeEarliestRuntimeAlarm, type AlarmStorage } from "./runtime-alarm-storage.ts";
6
+
7
+ type InvalidateDaemonSocket = (
8
+ socket: WebSocket,
9
+ message: string,
10
+ closeReason: string,
11
+ errorCode?: string,
12
+ ) => Promise<void>;
13
+ interface RuntimeAlarmContext {
14
+ storage: AlarmStorage;
15
+ pending: PendingCallRegistry;
16
+ daemonRegistry: DaemonRegistry;
17
+ invalidateDaemonSocket: InvalidateDaemonSocket;
18
+ invalidateDaemonChannel: (channel: DaemonChannel, message: string, errorCode?: string) => Promise<void>;
19
+ onScheduleError: (error: unknown) => void;
20
+ onAlarmMutation?: (action: "set" | "delete" | "noop") => void;
21
+ }
22
+ export async function processRuntimeAlarm(context: RuntimeAlarmContext, now = Date.now()): Promise<void> {
23
+ await context.pending.expireDue();
24
+ let nextDeadline = Number.POSITIVE_INFINITY;
25
+ for (const socket of context.daemonRegistry.candidateSockets()) {
26
+ const attachment = context.daemonRegistry.attachment(socket);
27
+ const connectedAt = Date.parse(attachment?.connectedAt ?? "");
28
+ const deadline = connectedAt + DAEMON_HELLO_TIMEOUT_MS;
29
+ if (!Number.isFinite(connectedAt) || deadline <= now) {
30
+ await context.invalidateDaemonSocket(
31
+ socket,
32
+ "daemon did not complete authentication",
33
+ "daemon hello timeout",
34
+ "daemon_hello_timeout",
35
+ );
36
+ continue;
37
+ }
38
+ nextDeadline = Math.min(nextDeadline, deadline);
39
+ }
40
+ for (const socket of context.daemonRegistry.probingSockets()) {
41
+ const attachment = context.daemonRegistry.attachment(socket);
42
+ const readyDeadline = daemonReadyDeadlineMs(attachment);
43
+ const liveDeadline = daemonLivenessDeadlineMs(attachment);
44
+ if (!Number.isFinite(readyDeadline) || !Number.isFinite(liveDeadline) || Math.min(readyDeadline, liveDeadline) <= now) {
45
+ await context.invalidateDaemonSocket(
46
+ socket,
47
+ "daemon did not complete end-to-end readiness verification",
48
+ "daemon ready timeout",
49
+ "daemon_ready_timeout",
50
+ );
51
+ continue;
52
+ }
53
+ nextDeadline = Math.min(nextDeadline, readyDeadline, liveDeadline);
54
+ }
55
+ for (const socket of context.daemonRegistry.readyRoleSockets()) {
56
+ const deadline = daemonLivenessDeadlineMs(context.daemonRegistry.readyAttachment(socket));
57
+ if (!Number.isFinite(deadline) || deadline <= now) {
58
+ await context.invalidateDaemonSocket(socket, "daemon became unresponsive", "daemon liveness timeout");
59
+ continue;
60
+ }
61
+ nextDeadline = Math.min(nextDeadline, deadline);
62
+ }
63
+ if (context.daemonRegistry.http) {
64
+ context.daemonRegistry.httpCandidates(now);
65
+ for (const channel of context.daemonRegistry.http.staleOwned(now)) {
66
+ await context.invalidateDaemonChannel(channel, "HTTPS fallback became unresponsive", "daemon_liveness_timeout");
67
+ }
68
+ nextDeadline = Math.min(nextDeadline, context.daemonRegistry.http.nextDeadline(now));
69
+ }
70
+ nextDeadline = Math.min(nextDeadline, pendingAlarmDeadline(context, now));
71
+ await writeEarliestRuntimeAlarm({
72
+ storage: context.storage,
73
+ nextDeadline,
74
+ now,
75
+ onError: context.onScheduleError,
76
+ onMutation: context.onAlarmMutation,
77
+ });
78
+ }
79
+ export async function scheduleRuntimeAlarm(context: RuntimeAlarmContext, now = Date.now()): Promise<void> {
80
+ try {
81
+ let nextDeadline = Number.POSITIVE_INFINITY;
82
+ for (const socket of context.daemonRegistry.candidateSockets()) {
83
+ const connectedAt = Date.parse(context.daemonRegistry.attachment(socket)?.connectedAt ?? "");
84
+ if (!Number.isFinite(connectedAt)) {
85
+ await context.invalidateDaemonSocket(
86
+ socket,
87
+ "daemon candidate timestamp is invalid",
88
+ "invalid daemon candidate timestamp",
89
+ "daemon_hello_timeout",
90
+ );
91
+ continue;
92
+ }
93
+ nextDeadline = Math.min(nextDeadline, connectedAt + DAEMON_HELLO_TIMEOUT_MS);
94
+ }
95
+ for (const socket of context.daemonRegistry.probingSockets()) {
96
+ const attachment = context.daemonRegistry.attachment(socket);
97
+ const readyDeadline = daemonReadyDeadlineMs(attachment);
98
+ const liveDeadline = daemonLivenessDeadlineMs(attachment);
99
+ if (!Number.isFinite(readyDeadline) || !Number.isFinite(liveDeadline)) {
100
+ await context.invalidateDaemonSocket(
101
+ socket,
102
+ "daemon readiness state is invalid",
103
+ "daemon ready timeout",
104
+ "daemon_ready_timeout",
105
+ );
106
+ continue;
107
+ }
108
+ nextDeadline = Math.min(nextDeadline, readyDeadline, liveDeadline);
109
+ }
110
+ for (const socket of context.daemonRegistry.readyRoleSockets()) {
111
+ const deadline = daemonLivenessDeadlineMs(context.daemonRegistry.readyAttachment(socket));
112
+ if (!Number.isFinite(deadline)) {
113
+ await context.invalidateDaemonSocket(socket, "daemon became unresponsive", "invalid daemon liveness timestamp");
114
+ continue;
115
+ }
116
+ nextDeadline = Math.min(nextDeadline, deadline);
117
+ }
118
+ if (context.daemonRegistry.http) {
119
+ context.daemonRegistry.httpCandidates(now);
120
+ nextDeadline = Math.min(nextDeadline, context.daemonRegistry.http.nextDeadline(now));
121
+ }
122
+ const currentAlarm = await context.storage.getAlarm();
123
+ nextDeadline = Math.min(nextDeadline, pendingAlarmDeadline(context, now));
124
+ await writeEarliestRuntimeAlarm({
125
+ storage: context.storage, nextDeadline, now, currentAlarm,
126
+ onError: context.onScheduleError, onMutation: context.onAlarmMutation,
127
+ });
128
+ } catch (error) {
129
+ try { context.onScheduleError(error); }
130
+ catch { /* The error observer must not recursively fail the Durable Object alarm callback. */ }
131
+ }
132
+ }
133
+ function pendingAlarmDeadline(context: RuntimeAlarmContext, now: number): number {
134
+ const delay = context.pending.nextDeadlineDelayMs();
135
+ return Number.isFinite(delay) ? now + Math.max(1, Math.ceil(delay)) : Number.POSITIVE_INFINITY;
136
+ }
@@ -0,0 +1,36 @@
1
+ export function workerGlobalActivityVisible(authorization: { account_role_is_owner?: unknown }): boolean {
2
+ return authorization.account_role_is_owner === true;
3
+ }
4
+
5
+ export function hiddenWorkerPending(value: Record<string, unknown>): Record<string, unknown> {
6
+ return {
7
+ maximum: value.maximum ?? 0,
8
+ ordinary_capacity: value.ordinary_capacity ?? 0,
9
+ reserved_capacity: value.reserved_capacity ?? 0,
10
+ activity_hidden_by_authority: true,
11
+ };
12
+ }
13
+
14
+ export function hiddenWorkerActivity(): Record<string, unknown> {
15
+ return { activity_hidden_by_authority: true };
16
+ }
17
+
18
+ export function projectDaemonStatus(value: Record<string, unknown>, fullToolNamesVisible: boolean): Record<string, unknown> {
19
+ if (fullToolNamesVisible) return value;
20
+ return {
21
+ connected: value.connected ?? false,
22
+ count: value.count ?? 0,
23
+ tool_count: value.tool_count ?? 0,
24
+ connected_at: value.connected_at ?? null,
25
+ last_seen_at: value.last_seen_at ?? null,
26
+ readiness_verified: value.readiness_verified ?? false,
27
+ readiness_timeout_ms: value.readiness_timeout_ms ?? null,
28
+ liveness_timeout_ms: value.liveness_timeout_ms ?? null,
29
+ policy: value.policy ?? null,
30
+ policy_scope: value.policy_scope ?? "daemon_capability_ceiling_not_account_authority",
31
+ tools: [],
32
+ tools_scope: value.tools_scope ?? "daemon_advertised_before_account_role_filtering",
33
+ tools_hidden_by_authority: true,
34
+ relay_transport: value.relay_transport ?? null,
35
+ };
36
+ }