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,51 +1,83 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { randomBytes } from "node:crypto";
3
3
  import { basename } from "node:path";
4
+ import { performance } from "node:perf_hooks";
4
5
  import { executionEnv } from "./shell.mjs";
6
+ import { attachChildProcessSettlement } from "./child-process-settlement.mjs";
7
+ import { assertOwnedByContext, principalBinding, visibleToContext } from "./authority-context.mjs";
8
+ import { recordMatchesAuthorityRevocation } from "../shared/authority-revocation.mjs";
9
+ import { delegatedProcessCommand } from "./delegated-process-sandbox.mjs";
5
10
  import { validateArgv } from "./process-contract.mjs";
6
11
  import { terminateProcessTree, terminateProcessTreeWithEscalation } from "./process-tree.mjs";
7
12
  import { createToolAuthorizer } from "./policy.mjs";
13
+ import { BridgeError, errorCode } from "./errors.mjs";
8
14
  import { createMonotonicDeadline } from "./monotonic-deadline.mjs";
9
15
  import { clampInteger } from "./numbers.mjs";
10
16
  import { ProcessOutputStream } from "./process-output-stream.mjs";
11
- import {
12
- MAX_PROCESS_SESSIONS, MAX_PROCESS_SESSION_OUTPUT_BYTES, MAX_PROCESS_SESSION_STDIN_BYTES, PROCESS_SESSION_RETENTION_MS,
13
- } from "./execution-limits.mjs";
14
-
17
+ import { boundedErrorMessage, notifySessionWaiters, sessionHasOutputAfter, waitForSessionChange, waitForSpawn } from "./process-session-events.mjs";
18
+ import { terminateProcessSessions } from "./process-session-termination.mjs";
19
+ import { acquireProcessResources, bindProcessResources, releaseProcessResources, releaseProcessResourcesQuietly } from "./resource-process-admission.mjs";
20
+ import { processSessionResourceWaitMs } from "./resource-foreground-wait.mjs";
21
+ import { beginRemoteProcessSessionActivity, endRemoteProcessSessionActivity } from "./process-session-remote-activity.mjs";
22
+ import { EXECUTION_SURFACE, withExecutionSurface } from "./execution-surface.mjs";
23
+ import { MAX_PROCESS_SESSIONS, MAX_PROCESS_SESSION_OUTPUT_BYTES, MAX_PROCESS_SESSION_STDIN_BYTES, PROCESS_SESSION_RETENTION_MS } from "./execution-limits.mjs";
24
+ const PROCESS_SESSION_TERMINATION_SETTLEMENT_MS = 5_000;
15
25
  export class ProcessSessionManager {
16
- constructor({ workspace, policy, authorizeTool = null, runtimeDir, processTracker, resolveCwd, displayPath, throwIfCancelled }) {
26
+ constructor({ workspace, policy, authorizeTool = null, policyForContext = null, runtimeDir, processTracker, resourceCoordinator = null, resourceWaitMs = undefined, terminationSettlementWaitMs = PROCESS_SESSION_TERMINATION_SETTLEMENT_MS, resolveCwd, displayPath, throwIfCancelled, terminateTree = terminateProcessTree, spawnProcess = spawn, childSettlementOptions = {}, remoteActivityGuard = null, now = () => performance.now(), wallNow = Date.now }) {
17
27
  this.workspace = workspace;
18
28
  this.policy = policy;
19
29
  this.authorizeTool = createToolAuthorizer(this.policy, authorizeTool);
30
+ this.policyForContext = typeof policyForContext === "function" ? policyForContext : () => this.policy;
20
31
  this.runtimeDir = runtimeDir;
21
32
  this.processTracker = processTracker;
33
+ this.resourceCoordinator = resourceCoordinator;
34
+ this.resourceWaitMs = resourceWaitMs;
35
+ this.terminationSettlementWaitMs = clampInteger(terminationSettlementWaitMs, PROCESS_SESSION_TERMINATION_SETTLEMENT_MS, 1, 30_000);
22
36
  this.resolveCwd = resolveCwd;
23
37
  this.displayPath = displayPath;
24
38
  this.throwIfCancelled = throwIfCancelled;
39
+ this.terminateTree = typeof terminateTree === "function" ? terminateTree : terminateProcessTree;
40
+ this.spawnProcess = spawnProcess;
41
+ this.childSettlementOptions = childSettlementOptions;
42
+ this.remoteActivityGuard = remoteActivityGuard;
43
+ this.now = typeof now === "function" ? now : () => performance.now();
44
+ this.wallNow = typeof wallNow === "function" ? wallNow : Date.now;
25
45
  this.sessions = new Map();
26
46
  }
27
47
 
28
- status() {
48
+ status(context = {}) {
49
+ const visible = [...this.sessions.values()].filter((session) => visibleToContext(session, context));
29
50
  return {
30
- active: [...this.sessions.values()].filter((session) => session.closedAt === null).length,
31
- retained: this.sessions.size,
51
+ active: visible.filter((session) => session.closedAt === null).length,
52
+ retained: visible.length,
32
53
  maximum: MAX_PROCESS_SESSIONS,
33
54
  };
34
55
  }
35
56
 
36
- clear() {
37
- this.sessions.clear();
57
+ async clearAndWait(signal = "SIGKILL") {
58
+ return terminateProcessSessions({
59
+ sessions: this.sessions, terminateTree: this.terminateTree, signal,
60
+ waitMs: this.terminationSettlementWaitMs, mode: "shutdown",
61
+ });
62
+ }
63
+
64
+ async revokeAuthority(revocation) {
65
+ return terminateProcessSessions({
66
+ sessions: this.sessions, terminateTree: this.terminateTree, signal: "SIGKILL",
67
+ waitMs: this.terminationSettlementWaitMs, mode: "authority_revocation",
68
+ matches: (session) => recordMatchesAuthorityRevocation(session, revocation),
69
+ });
38
70
  }
39
71
 
40
72
  notifyCancellation() {
41
73
  for (const session of this.sessions.values()) notifySessionWaiters(session);
42
74
  }
43
75
 
44
- retainCompletedOutput({ command, cwd, stdout, stderr, exitCode, startedAt, closedAt = Date.now() }) {
76
+ retainCompletedOutput({ command, cwd, stdout, stderr, exitCode, startedAt, closedAt = this.wallNow() }, context = {}) {
45
77
  this.prune();
46
78
  this.evictExitedForCapacity();
47
79
  if (this.sessions.size >= MAX_PROCESS_SESSIONS) return null;
48
- const completedAt = Number.isFinite(Number(closedAt)) ? Number(closedAt) : Date.now();
80
+ const completedAt = Number.isFinite(Number(closedAt)) ? Number(closedAt) : this.wallNow();
49
81
  const session = {
50
82
  id: `proc_${randomBytes(24).toString("base64url")}`,
51
83
  child: null,
@@ -54,33 +86,44 @@ export class ProcessSessionManager {
54
86
  stdout,
55
87
  stderr,
56
88
  startedAt: Number.isFinite(Number(startedAt)) ? Number(startedAt) : completedAt,
57
- lastActivity: completedAt,
89
+ lastActivityMonotonic: this.now(),
58
90
  closedAt: completedAt,
59
91
  exitCode: Number.isInteger(exitCode) ? exitCode : null,
60
92
  signal: null,
61
93
  stdinClosed: true,
62
94
  waiters: new Set(),
63
95
  terminationTimer: null,
96
+ ...principalBinding(context),
64
97
  };
65
98
  this.sessions.set(session.id, session);
66
- return this.summary(session);
99
+ return this.summary(session, context);
67
100
  }
68
101
 
69
102
  async start(args, context = {}) {
70
103
  this.authorizeTool("start_process");
71
104
  const argv = validateArgv(args.argv);
72
- const cwd = await this.resolveCwd(args.cwd || ".");
105
+ const cwd = await this.resolveCwd(args.cwd || ".", context);
73
106
  this.prune();
74
107
  this.evictExitedForCapacity();
75
108
  if (this.sessions.size >= MAX_PROCESS_SESSIONS) throw new Error(`process session limit reached (${MAX_PROCESS_SESSIONS})`);
76
109
  this.throwIfCancelled(context);
77
-
78
- const child = spawn(argv[0], argv.slice(1), {
79
- cwd,
80
- env: executionEnv(this.workspace, { fullEnv: this.policy.minimalEnv === false, runtimeDir: this.runtimeDir }),
81
- detached: process.platform !== "win32",
82
- windowsHide: true,
110
+ const executionEnvironment = withExecutionSurface(executionEnv(this.workspace, { fullEnv: this.policyForContext(context).minimalEnv === false, runtimeDir: this.runtimeDir }), EXECUTION_SURFACE.processSession);
111
+ const admitted = await acquireProcessResources(this.resourceCoordinator, argv[0], argv.slice(1), executionEnvironment, {
112
+ cwd, priority: "interactive", waitMs: processSessionResourceWaitMs(this.resourceWaitMs, { remote: context?.authority?.origin === "relay" }), signal: context.signal,
83
113
  });
114
+ const launch = delegatedProcessCommand({ command: admitted.command, args: admitted.args, workspace: this.workspace, runtimeDir: this.runtimeDir, context });
115
+ const remoteActivityHeld = beginRemoteProcessSessionActivity(context, this.remoteActivityGuard);
116
+ let child;
117
+ try {
118
+ child = this.spawnProcess(launch.command, launch.args, { cwd, env: admitted.environment, detached: process.platform !== "win32", windowsHide: true });
119
+ } catch (error) {
120
+ endRemoteProcessSessionActivity(remoteActivityHeld, this.remoteActivityGuard);
121
+ await releaseProcessResources(admitted.lease);
122
+ throw error;
123
+ }
124
+ let resolveResourceBindingSettled;
125
+ const resourceBindingSettled = new Promise((resolvePromise) => { resolveResourceBindingSettled = resolvePromise; });
126
+ const startedAt = this.wallNow();
84
127
  const session = {
85
128
  id: `proc_${randomBytes(24).toString("base64url")}`,
86
129
  child,
@@ -88,57 +131,94 @@ export class ProcessSessionManager {
88
131
  cwd,
89
132
  stdout: new ProcessOutputStream(MAX_PROCESS_SESSION_OUTPUT_BYTES),
90
133
  stderr: new ProcessOutputStream(MAX_PROCESS_SESSION_OUTPUT_BYTES),
91
- startedAt: Date.now(),
92
- lastActivity: Date.now(),
134
+ startedAt,
135
+ lastActivityMonotonic: this.now(),
93
136
  closedAt: null,
94
137
  exitCode: null,
95
138
  signal: null,
96
139
  stdinClosed: false,
97
140
  waiters: new Set(),
98
141
  terminationTimer: null,
142
+ startupFailed: false,
143
+ resourceLease: admitted.lease,
144
+ resourceBindingSettled,
145
+ ...principalBinding(context),
99
146
  };
100
147
  this.sessions.set(session.id, session);
101
148
  this.trackChild(child, context.callId);
102
149
 
103
150
  child.stdout.on("data", (chunk) => {
104
151
  session.stdout.append(chunk);
105
- session.lastActivity = Date.now();
152
+ this.markActivity(session);
106
153
  notifySessionWaiters(session);
107
154
  });
108
155
  child.stderr.on("data", (chunk) => {
109
156
  session.stderr.append(chunk);
110
- session.lastActivity = Date.now();
157
+ this.markActivity(session);
111
158
  notifySessionWaiters(session);
112
159
  });
113
- child.on("close", (code, signal) => {
114
- session.exitCode = Number.isInteger(code) ? code : null;
115
- session.signal = signal ? String(signal) : null;
116
- session.closedAt = Date.now();
117
- session.lastActivity = Date.now();
118
- session.stdinClosed = true;
119
- this.untrackChild(child);
120
- notifySessionWaiters(session);
160
+ attachChildProcessSettlement(child, {
161
+ ...this.childSettlementOptions,
162
+ onSettle: (code, signal) => {
163
+ session.exitCode = Number.isInteger(code) ? code : null;
164
+ session.signal = signal ? String(signal) : null;
165
+ session.closedAt = this.wallNow();
166
+ this.markActivity(session);
167
+ session.stdinClosed = true;
168
+ this.untrackChild(child);
169
+ endRemoteProcessSessionActivity(remoteActivityHeld, this.remoteActivityGuard);
170
+ void session.resourceBindingSettled.then(() => releaseProcessResourcesQuietly(session.resourceLease));
171
+ if (session.startupFailed) this.sessions.delete(session.id);
172
+ notifySessionWaiters(session);
173
+ },
121
174
  });
122
175
 
123
176
  child.on("error", (error) => {
124
177
  session.stderr.append(Buffer.from(`${boundedErrorMessage(error)}\n`));
125
- session.lastActivity = Date.now();
178
+ this.markActivity(session);
126
179
  notifySessionWaiters(session);
127
180
  });
128
181
  try {
129
182
  await waitForSpawn(child);
183
+ await bindProcessResources(admitted.lease, child);
184
+ resolveResourceBindingSettled();
130
185
  } catch (error) {
186
+ resolveResourceBindingSettled();
187
+ throw this.failedStartError(session, error);
188
+ }
189
+ try { this.throwIfCancelled(context); }
190
+ catch (error) { throw this.failedStartError(session, error); }
191
+ return this.summary(session, context);
192
+ }
193
+
194
+ failedStartError(session, error) {
195
+ session.startupFailed = true; session.stdinClosed = true;
196
+ try { session.child?.stdin?.destroy?.(); }
197
+ catch { /* Startup failure already owns the error path; closing stdin is best-effort cleanup. */ }
198
+ if (session.closedAt !== null) {
131
199
  this.sessions.delete(session.id);
132
- this.untrackChild(child);
133
- throw error;
200
+ return error;
134
201
  }
135
- this.throwIfCancelled(context);
136
- return this.summary(session);
202
+ let terminationRequested = false;
203
+ try { terminationRequested = this.terminateTree(session.child, "SIGKILL") === true; }
204
+ catch { /* The returned settlement metadata records that child termination could not be confirmed. */ }
205
+ this.markActivity(session);
206
+ const code = errorCode(error);
207
+ return new BridgeError(code, "process session start failed after the child was spawned", {
208
+ cause: error instanceof Error ? error : undefined,
209
+ retryable: false,
210
+ details: {
211
+ side_effects_started: true,
212
+ termination_requested: terminationRequested,
213
+ effect_settlement: terminationRequested ? "pending" : "unknown",
214
+ ownership_retained_until_close: true,
215
+ },
216
+ });
137
217
  }
138
218
 
139
219
  async read(args, context = {}) {
140
220
  this.authorizeTool("read_process");
141
- const session = this.get(args.session_id);
221
+ const session = this.get(args.session_id, context);
142
222
  const stdoutOffset = clampInteger(args.stdout_offset, 0, 0, Number.MAX_SAFE_INTEGER);
143
223
  const stderrOffset = clampInteger(args.stderr_offset, 0, 0, Number.MAX_SAFE_INTEGER);
144
224
  const maxBytes = clampInteger(args.max_bytes, 64 * 1024, 1, 256 * 1024);
@@ -156,9 +236,9 @@ export class ProcessSessionManager {
156
236
  }
157
237
  }
158
238
  this.throwIfCancelled(context);
159
- session.lastActivity = Date.now();
239
+ this.markActivity(session);
160
240
  return {
161
- ...this.summary(session),
241
+ ...this.summary(session, context),
162
242
  stdout: session.stdout.read(stdoutOffset, maxBytes),
163
243
  stderr: session.stderr.read(stderrOffset, maxBytes),
164
244
  };
@@ -166,7 +246,7 @@ export class ProcessSessionManager {
166
246
 
167
247
  async write(args, context = {}) {
168
248
  this.authorizeTool("write_process");
169
- const session = this.get(args.session_id);
249
+ const session = this.get(args.session_id, context);
170
250
  if (session.closedAt !== null) throw new Error("process session has already exited");
171
251
  const data = String(args.data ?? "");
172
252
  if (Buffer.byteLength(data) > MAX_PROCESS_SESSION_STDIN_BYTES) throw new Error(`stdin data exceeds maximum size (${MAX_PROCESS_SESSION_STDIN_BYTES} bytes)`);
@@ -181,43 +261,48 @@ export class ProcessSessionManager {
181
261
  session.child.stdin.end();
182
262
  session.stdinClosed = true;
183
263
  }
184
- session.lastActivity = Date.now();
185
- return { ...this.summary(session), bytes_written: Buffer.byteLength(data) };
264
+ this.markActivity(session);
265
+ return { ...this.summary(session, context), bytes_written: Buffer.byteLength(data) };
186
266
  }
187
267
 
188
268
  async kill(args, context = {}) {
189
269
  this.authorizeTool("kill_process");
190
- const session = this.get(args.session_id);
270
+ const session = this.get(args.session_id, context);
191
271
  this.throwIfCancelled(context);
192
272
  const wasRunning = session.closedAt === null;
193
273
  const force = args.force === true;
194
- if (wasRunning && force) terminateProcessTree(session.child, "SIGKILL");
195
- else if (wasRunning && !session.terminationTimer) {
196
- session.terminationTimer = terminateProcessTreeWithEscalation(session.child);
274
+ if (wasRunning && force && !this.terminateTree(session.child, "SIGKILL")) {
275
+ throw new BridgeError("unavailable", "process termination request could not be delivered", { retryable: true });
276
+ } else if (wasRunning && !force && !session.terminationTimer) {
277
+ session.terminationTimer = terminateProcessTreeWithEscalation(session.child, {
278
+ terminate: this.terminateTree,
279
+ onTerminationSettled: () => { session.terminationTimer = null; },
280
+ });
197
281
  }
198
- session.lastActivity = Date.now();
282
+ this.markActivity(session);
199
283
  return {
200
- ...this.summary(session),
284
+ ...this.summary(session, context),
201
285
  termination_requested: wasRunning,
202
286
  force,
203
287
  force_after_ms: wasRunning && !force ? 2000 : null,
204
288
  };
205
289
  }
206
290
 
207
- get(sessionId) {
291
+ get(sessionId, context = {}) {
208
292
  this.prune();
209
293
  const id = String(sessionId || "");
210
294
  if (!/^proc_[A-Za-z0-9_-]{20,}$/.test(id)) throw new Error("invalid process session id");
211
295
  const session = this.sessions.get(id);
212
296
  if (!session) throw new Error("process session not found or expired");
297
+ assertOwnedByContext(session, context, "process session");
213
298
  return session;
214
299
  }
215
300
 
216
- summary(session) {
301
+ summary(session, context = {}) {
217
302
  return {
218
303
  session_id: session.id,
219
304
  command: session.argv0,
220
- cwd: this.displayPath(session.cwd),
305
+ cwd: this.displayPath(session.cwd, context),
221
306
  running: session.closedAt === null,
222
307
  exit_code: session.exitCode,
223
308
  signal: session.signal,
@@ -230,9 +315,9 @@ export class ProcessSessionManager {
230
315
  }
231
316
 
232
317
  prune() {
233
- const cutoff = Date.now() - PROCESS_SESSION_RETENTION_MS;
318
+ const cutoff = this.now() - PROCESS_SESSION_RETENTION_MS;
234
319
  for (const [id, session] of this.sessions) {
235
- if (session.closedAt !== null && session.lastActivity < cutoff) this.sessions.delete(id);
320
+ if (session.closedAt !== null && session.lastActivityMonotonic < cutoff) this.sessions.delete(id);
236
321
  }
237
322
  }
238
323
 
@@ -240,67 +325,14 @@ export class ProcessSessionManager {
240
325
  if (this.sessions.size < MAX_PROCESS_SESSIONS) return;
241
326
  const exited = [...this.sessions.values()]
242
327
  .filter((session) => session.closedAt !== null)
243
- .sort((left, right) => left.lastActivity - right.lastActivity);
328
+ .sort((left, right) => left.lastActivityMonotonic - right.lastActivityMonotonic);
244
329
  for (const session of exited) {
245
330
  if (this.sessions.size < MAX_PROCESS_SESSIONS) break;
246
331
  this.sessions.delete(session.id);
247
332
  }
248
333
  }
249
334
 
250
-
251
- trackChild(child, callId) {
252
- this.processTracker.track(child, callId);
253
- }
254
-
255
- untrackChild(child) {
256
- this.processTracker.untrack(child);
257
- }
258
- }
259
-
260
- function waitForSpawn(child) {
261
- return new Promise((resolvePromise, rejectPromise) => {
262
- const onSpawn = () => { cleanup(); resolvePromise(); };
263
- const onError = (error) => { cleanup(); rejectPromise(error); };
264
- const cleanup = () => {
265
- child.off("spawn", onSpawn);
266
- child.off("error", onError);
267
- };
268
- child.once("spawn", onSpawn);
269
- child.once("error", onError);
270
- });
271
- }
272
-
273
- function sessionHasOutputAfter(session, stdoutOffset, stderrOffset) {
274
- return session.stdout.totalBytes > stdoutOffset || session.stderr.totalBytes > stderrOffset;
275
- }
276
-
277
- function notifySessionWaiters(session) {
278
- for (const waiter of [...session.waiters]) waiter();
279
- }
280
-
281
- function waitForSessionChange(session, waitMs, cancellationCheck) {
282
- return new Promise((resolvePromise, rejectPromise) => {
283
- let timer;
284
- const done = () => {
285
- cleanup();
286
- try {
287
- cancellationCheck();
288
- resolvePromise();
289
- } catch (error) {
290
- rejectPromise(error);
291
- }
292
- };
293
- const cleanup = () => {
294
- if (timer) clearTimeout(timer);
295
- session.waiters.delete(done);
296
- };
297
- session.waiters.add(done);
298
- timer = setTimeout(done, waitMs);
299
- timer.unref?.();
300
- });
301
- }
302
-
303
- function boundedErrorMessage(error) {
304
- const message = error instanceof Error ? error.message : String(error);
305
- return message.replace(/[\r\n]+/g, " ").slice(0, 4096) || "process failed";
335
+ markActivity(session) { session.lastActivityMonotonic = this.now(); }
336
+ trackChild(child, callId) { this.processTracker.track(child, callId); }
337
+ untrackChild(child) { this.processTracker.untrack(child); }
306
338
  }
@@ -1,4 +1,8 @@
1
1
  import { terminateProcessTree, terminateProcessTreeWithEscalation } from "./process-tree.mjs";
2
+ import { BridgeError } from "./errors.mjs";
3
+ import { createMonotonicDeadline } from "./monotonic-deadline.mjs";
4
+
5
+ const DEFAULT_DRAIN_WAIT_MS = 5_000;
2
6
 
3
7
  export class ProcessTracker {
4
8
  constructor(options = {}) {
@@ -6,50 +10,173 @@ export class ProcessTracker {
6
10
  this.terminateWithEscalation = typeof options.terminateWithEscalation === "function"
7
11
  ? options.terminateWithEscalation
8
12
  : terminateProcessTreeWithEscalation;
13
+ this.clearScheduledTermination = typeof options.clearScheduledTermination === "function"
14
+ ? options.clearScheduledTermination
15
+ : clearTimeout;
9
16
  this.active = new Set();
10
17
  this.byCall = new Map();
18
+ this.childCall = new Map();
19
+ this.releasedCalls = new Set();
20
+ this.terminating = new Set();
21
+ this.terminationTimers = new Map();
22
+ this.drainSignal = null;
23
+ this.drainRequested = new Set();
24
+ this.changeWaiters = new Set();
11
25
  }
12
26
 
13
27
  track(child, callId = "") {
14
28
  if (!child) return;
15
29
  this.active.add(child);
16
- if (!callId) return;
17
- const id = String(callId);
18
- const children = this.byCall.get(id) || new Set();
19
- children.add(child);
20
- this.byCall.set(id, children);
30
+ if (callId) {
31
+ const id = String(callId);
32
+ const children = this.byCall.get(id) || new Set();
33
+ children.add(child);
34
+ this.byCall.set(id, children);
35
+ this.childCall.set(child, id);
36
+ }
37
+ if (this.drainSignal) this.requestDrainTermination(child);
38
+ this.notifyChange();
21
39
  }
22
40
 
23
41
  untrack(child) {
24
42
  if (!child) return;
25
43
  this.active.delete(child);
26
- for (const [callId, children] of this.byCall) {
44
+ this.terminating.delete(child);
45
+ this.drainRequested.delete(child);
46
+ const callId = this.childCall.get(child);
47
+ this.childCall.delete(child);
48
+ if (callId) {
49
+ const children = this.byCall.get(callId);
50
+ children?.delete(child);
51
+ if (!children?.size) {
52
+ this.byCall.delete(callId);
53
+ this.releasedCalls.delete(callId);
54
+ }
55
+ this.notifyChange();
56
+ return;
57
+ }
58
+ for (const [id, children] of this.byCall) {
27
59
  children.delete(child);
28
- if (!children.size) this.byCall.delete(callId);
60
+ if (!children.size) {
61
+ this.byCall.delete(id);
62
+ this.releasedCalls.delete(id);
63
+ }
29
64
  }
65
+ this.notifyChange();
30
66
  }
31
67
 
32
68
  releaseCall(callId) {
33
- if (callId) this.byCall.delete(String(callId));
69
+ const id = String(callId || "");
70
+ if (!id) return;
71
+ if (this.byCall.get(id)?.size) this.releasedCalls.add(id);
72
+ else {
73
+ this.byCall.delete(id);
74
+ this.releasedCalls.delete(id);
75
+ }
34
76
  }
35
77
 
36
78
  terminateCall(callId, { force = false } = {}) {
37
79
  const children = [...(this.byCall.get(String(callId || "")) || [])];
38
80
  for (const child of children) {
39
- if (force) this.terminate(child, "SIGKILL");
40
- else this.terminateWithEscalation(child);
81
+ this.terminating.add(child);
82
+ this.requestTermination(child, force);
41
83
  }
42
84
  return children.length;
43
85
  }
44
86
 
45
87
  terminateAll(signal = "SIGTERM", escalate = false) {
46
88
  for (const child of [...this.active]) {
47
- if (escalate && signal !== "SIGKILL") this.terminateWithEscalation(child);
48
- else this.terminate(child, signal);
89
+ this.terminating.add(child);
90
+ if (escalate && signal !== "SIGKILL") this.requestTermination(child, false);
91
+ else {
92
+ this.clearTermination(child);
93
+ this.terminate(child, signal);
94
+ }
95
+ }
96
+ }
97
+
98
+ async drain(signal = "SIGKILL", waitMs = DEFAULT_DRAIN_WAIT_MS) {
99
+ const boundedWaitMs = Number.isFinite(Number(waitMs)) ? Math.max(1, Math.min(30_000, Math.floor(Number(waitMs)))) : DEFAULT_DRAIN_WAIT_MS;
100
+ this.drainSignal = signal;
101
+ const deadline = createMonotonicDeadline(boundedWaitMs);
102
+ while (this.active.size) {
103
+ for (const child of [...this.active]) this.requestDrainTermination(child);
104
+ if (!this.active.size) break;
105
+ if (deadline.expired()) break;
106
+ await this.waitForChange(Math.max(1, deadline.remainingMs()));
107
+ }
108
+ if (this.active.size) {
109
+ for (const child of this.active) this.drainRequested.delete(child);
110
+ throw new BridgeError("unavailable", "process shutdown did not settle before the runtime teardown deadline", {
111
+ retryable: true,
112
+ details: { active_processes: this.active.size },
113
+ });
49
114
  }
50
115
  }
51
116
 
52
117
  snapshot() {
53
- return { active_processes: this.active.size, calls_with_processes: this.byCall.size };
118
+ let activeCalls = 0;
119
+ let drainingCalls = 0;
120
+ let drainingProcesses = 0;
121
+ for (const [callId, children] of this.byCall) {
122
+ if (this.releasedCalls.has(callId)) {
123
+ drainingCalls += 1;
124
+ drainingProcesses += children.size;
125
+ } else activeCalls += 1;
126
+ }
127
+ return {
128
+ active_processes: this.active.size,
129
+ calls_with_processes: activeCalls,
130
+ draining_calls: drainingCalls,
131
+ draining_processes: drainingProcesses,
132
+ terminating_processes: this.terminating.size,
133
+ termination_escalations_pending: this.terminationTimers.size,
134
+ };
135
+ }
136
+
137
+ requestTermination(child, force) {
138
+ if (force) {
139
+ this.clearTermination(child);
140
+ this.terminate(child, "SIGKILL");
141
+ return;
142
+ }
143
+ if (this.terminationTimers.has(child)) return;
144
+ const timer = this.terminateWithEscalation(child, {
145
+ onTerminationSettled: () => { this.terminationTimers.delete(child); },
146
+ });
147
+ if (timer) this.terminationTimers.set(child, timer);
148
+ }
149
+
150
+ clearTermination(child) {
151
+ const timer = this.terminationTimers.get(child);
152
+ if (!timer) return;
153
+ this.clearScheduledTermination(timer);
154
+ this.terminationTimers.delete(child);
155
+ }
156
+
157
+ requestDrainTermination(child) {
158
+ if (!child || this.drainRequested.has(child)) return;
159
+ this.drainRequested.add(child);
160
+ this.terminating.add(child);
161
+ this.clearTermination(child);
162
+ try { this.terminate(child, this.drainSignal || "SIGKILL"); }
163
+ catch { /* Drain completion is proven by close/untrack, never by the kill request alone. */ }
164
+ }
165
+
166
+ waitForChange(waitMs) {
167
+ return new Promise((resolvePromise) => {
168
+ let timer;
169
+ const done = () => {
170
+ if (timer) clearTimeout(timer);
171
+ this.changeWaiters.delete(done);
172
+ resolvePromise();
173
+ };
174
+ this.changeWaiters.add(done);
175
+ timer = setTimeout(done, waitMs);
176
+ });
177
+ }
178
+
179
+ notifyChange() {
180
+ for (const waiter of [...this.changeWaiters]) waiter();
54
181
  }
55
182
  }
@@ -0,0 +1,37 @@
1
+ export interface ChildProcessIdentity {
2
+ pid?: unknown;
3
+ exitCode?: unknown;
4
+ signalCode?: unknown;
5
+ }
6
+
7
+ export interface ProcessGroupEntry {
8
+ pid: number;
9
+ pgid: number;
10
+ startedAt: number;
11
+ }
12
+
13
+ export interface ProcessOwnershipMember {
14
+ pid: number;
15
+ startedAt: number | null;
16
+ }
17
+
18
+ export interface ProcessOwnershipSnapshot {
19
+ platform: string;
20
+ pid: number;
21
+ members: ProcessOwnershipMember[];
22
+ }
23
+
24
+ export interface ProcessSnapshotResult {
25
+ error?: unknown;
26
+ status?: number | null;
27
+ stdout?: string | Buffer;
28
+ }
29
+
30
+ export interface ProcessOwnershipOptions {
31
+ platform?: string;
32
+ listProcessGroups?: (options: ProcessOwnershipOptions, pid: number, timeoutMs: number) => ProcessGroupEntry[] | Promise<ProcessGroupEntry[]>;
33
+ execFileProcess?: (command: string, args: string[], options: Record<string, unknown>) => ProcessSnapshotResult | Promise<ProcessSnapshotResult>;
34
+ ownershipCheckBudgetMs?: unknown;
35
+ processSnapshotTimeoutMs?: unknown;
36
+ monotonicNow?: () => number;
37
+ }