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,14 +1,25 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
- import { chmodSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { chmodSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { basename, join, resolve } from "node:path";
5
5
  import { performance } from "node:perf_hooks";
6
6
  import { executionEnv } from "./shell.mjs";
7
+ import { attachChildProcessSettlement, childExitedBeforeTimeout } from "./child-process-settlement.mjs";
7
8
  import { terminateProcessTreeWithEscalation } from "./process-tree.mjs";
8
- import { createExclusiveFileSync, removeOwnedJsonFileSync, replaceFileAtomicallySync } from "./exclusive-file.mjs";
9
+ import { removeOwnedJsonFileSync, replaceFileAtomicallySync } from "./exclusive-file.mjs";
9
10
  import { createMonotonicDeadline } from "./monotonic-deadline.mjs";
10
- import { currentProcessStartTimeMs } from "./process-identity.mjs";
11
+ import { currentProcessStartTimeMs, processState } from "./process-identity.mjs";
11
12
  import { readBoundedRegularFileSync } from "./secure-file.mjs";
13
+ import { managedJobCancellationRequested } from "./managed-job-cancellation.mjs";
14
+ import { MacosIdleSleepAssertion } from "./macos-idle-sleep-assertion.mjs";
15
+ import relayContract from "../shared/relay-contract.json" with { type: "json" };
16
+ import { MANAGED_JOB_ID } from "./managed-job-directory.mjs";
17
+ import { ACTIVE_JOB_STATES, isTerminalManagedJobStatus, managedJobFinalStatus, persistManagedJobTerminal } from "./managed-job-terminal.mjs";
18
+ import { sanitizeLogText } from "./log.mjs";
19
+ import { confirmRunnerClaim } from "./managed-job-runner-claim.mjs";
20
+ import { ResourceCoordinator } from "./resource-admission.mjs";
21
+ import { acquireProcessResources, bindProcessResources, releaseProcessResources, releaseProcessResourcesQuietly } from "./resource-process-admission.mjs";
22
+ import { delegatedProcessCommand } from "./delegated-process-sandbox.mjs";
12
23
 
13
24
  const RESOURCE_TOKEN = /\{\{resource:([a-z][a-z0-9._-]{0,63})\}\}/g;
14
25
  const TEMP_TOKEN = /\{\{temp:([a-z][a-z0-9._-]{0,63})\}\}/g;
@@ -17,16 +28,22 @@ const MAX_OUTPUT_BYTES = 64 * 1024;
17
28
  const MAX_JOB_CAPTURE_BYTES = 256 * 1024;
18
29
  const MAX_RESULT_BYTES = 4 * 1024 * 1024;
19
30
  const MAX_STATUS_BYTES = 256 * 1024;
20
- const JOB_ID = /^job_[A-Za-z0-9_-]{24,}$/;
31
+ const RECOVERY_LOCK_HANDOFF_WAIT_MS = 30_000;
32
+ const FATAL_IDENTITY_FIELDS = [
33
+ "job_id", "name", "plan_sha256", "created_at", "approval", "owner_kind", "owner_account_id",
34
+ "owner_account_version", "owner_client_id", "owner_family_id",
35
+ ];
21
36
 
22
37
  const options = parseArgs(process.argv.slice(2));
23
38
  const jobDirInput = typeof options.jobDir === "string" ? options.jobDir.trim() : "";
24
39
  if (!jobDirInput) throw new Error("--job-dir is required");
25
40
  const jobDir = resolve(jobDirInput);
26
- if (!JOB_ID.test(basename(jobDir))) throw new Error("--job-dir must name a managed job directory");
41
+ if (!MANAGED_JOB_ID.test(basename(jobDir))) throw new Error("--job-dir must name a managed job directory");
27
42
  const recover = options.recover === true;
28
43
  const recoveryLockToken = typeof process.env.MBM_RECOVERY_LOCK_TOKEN === "string" ? process.env.MBM_RECOVERY_LOCK_TOKEN : "";
44
+ const launchToken = typeof process.env.MBM_RUNNER_LAUNCH_TOKEN === "string" ? process.env.MBM_RUNNER_LAUNCH_TOKEN : "";
29
45
  delete process.env.MBM_RECOVERY_LOCK_TOKEN;
46
+ delete process.env.MBM_RUNNER_LAUNCH_TOKEN;
30
47
  const planFile = join(jobDir, "plan.json");
31
48
  const statusFile = join(jobDir, "status.json");
32
49
  const resultFile = join(jobDir, "result.json");
@@ -36,6 +53,7 @@ const resourcesDir = join(runtimeDir, "resources");
36
53
  const temporaryFilesDir = join(runtimeDir, "files");
37
54
  const runnerPidFile = join(jobDir, "runner.pid");
38
55
  const RUNNER_PROCESS_STARTED_AT = new Date(currentProcessStartTimeMs()).toISOString();
56
+ const resourceCoordinator = new ResourceCoordinator();
39
57
 
40
58
  class JobCancelledError extends Error {
41
59
  constructor() {
@@ -47,28 +65,41 @@ class JobCancelledError extends Error {
47
65
 
48
66
  let activeChild = null;
49
67
  let activeChildCancellationAware = false;
50
- let cancellationEscalation = null;
68
+ let activeChildTermination = null;
51
69
  let cancelRequested = false;
70
+ let runnerClaimConfirmed = false;
71
+ let jobIdleSleepAssertion = null;
52
72
  for (const signal of ["SIGTERM", "SIGINT"]) {
53
73
  process.on(signal, () => requestCancellation());
54
74
  }
55
75
 
56
76
  const initial = readJson(statusFile, MAX_STATUS_BYTES);
57
77
  assertLaunchState(initial);
58
- createExclusiveFileSync(runnerPidFile, `${JSON.stringify({ pid: process.pid, processStartedAt: RUNNER_PROCESS_STARTED_AT })}\n`, { mode: 0o600 });
59
78
  try {
79
+ await confirmRunnerClaim({
80
+ file: runnerPidFile, pid: process.pid, processStartedAt: RUNNER_PROCESS_STARTED_AT, launchToken,
81
+ });
82
+ if (initial.owner_kind === "account") {
83
+ jobIdleSleepAssertion = new MacosIdleSleepAssertion({ logger: managedJobIdleSleepLogger() });
84
+ jobIdleSleepAssertion.acquire();
85
+ }
60
86
  if (recover) await releaseRecoveryClaim();
87
+ runnerClaimConfirmed = true;
61
88
  const plan = readJson(planFile, 1024 * 1024);
62
89
  assertPlanIntegrity(plan, initial);
63
90
  await main(plan, initial);
64
91
  } catch (error) {
65
92
  recordFatalRunnerError(error);
93
+ process.exitCode = 1;
94
+ } finally {
95
+ jobIdleSleepAssertion?.release();
66
96
  }
67
97
 
98
+
68
99
  async function releaseRecoveryClaim() {
69
100
  if (!/^[a-f0-9]{32}$/.test(recoveryLockToken)) throw new Error("recovery runner is missing its ownership token");
70
101
  const file = join(jobDir, "recovery.lock");
71
- const deadline = createMonotonicDeadline(5000);
102
+ const deadline = createMonotonicDeadline(RECOVERY_LOCK_HANDOFF_WAIT_MS);
72
103
  while (!deadline.expired()) {
73
104
  if (removeOwnedJsonFileSync(file, { pid: process.pid, token: recoveryLockToken })) return;
74
105
  await new Promise((resolvePromise) => { setTimeout(resolvePromise, 10); });
@@ -104,7 +135,7 @@ async function main(plan, initial) {
104
135
  for (let index = 0; index < plan.steps.length; index += 1) {
105
136
  if (isCancellationRequested()) throw new JobCancelledError();
106
137
  updateStatus(status, { status: "running", current_phase: "steps", current_step: index });
107
- const result = await runStep(plan.steps[index], index, "steps", plan, resourceContext, true, captureBudget);
138
+ const result = await runStep(plan.steps[index], index, "steps", plan, resourceContext, true, captureBudget, status);
108
139
  mainResults.push(result);
109
140
  if (result.timed_out && !plan.steps[index].allow_failure) throw new Error(`step ${index + 1} timed out`);
110
141
  if (result.code !== 0 && !plan.steps[index].allow_failure) throw new Error(`step ${index + 1} exited ${result.code}`);
@@ -117,7 +148,7 @@ async function main(plan, initial) {
117
148
  try {
118
149
  for (let index = 0; index < plan.finally_steps.length; index += 1) {
119
150
  updateStatus(status, { status: "cleaning", current_phase: recover ? "recovery-cleanup" : "finally_steps", current_step: index });
120
- const result = await runStep(plan.finally_steps[index], index, "finally_steps", plan, resourceContext, false, captureBudget);
151
+ const result = await runStep(plan.finally_steps[index], index, "finally_steps", plan, resourceContext, false, captureBudget, status);
121
152
  cleanupResults.push(result);
122
153
  if (result.timed_out && !plan.finally_steps[index].allow_failure && !cleanupError) cleanupError = new Error(`cleanup step ${index + 1} timed out`);
123
154
  if (result.code !== 0 && !plan.finally_steps[index].allow_failure && !cleanupError) cleanupError = new Error(`cleanup step ${index + 1} exited ${result.code}`);
@@ -125,15 +156,23 @@ async function main(plan, initial) {
125
156
  } catch (error) {
126
157
  cleanupError ||= error;
127
158
  }
128
- rmSync(runtimeDir, { recursive: true, force: true });
129
159
  }
130
160
 
131
- const cancelled = mainError instanceof JobCancelledError || existsSync(cancelFile);
132
- let finalStatus;
133
- if (recover) finalStatus = cleanupError ? "recovery_failed" : "recovered";
134
- else if (cancelled) finalStatus = cleanupError ? "cancelled_cleanup_failed" : "cancelled";
135
- else if (mainError) finalStatus = cleanupError ? "failed_cleanup_failed" : "failed";
136
- else finalStatus = cleanupError ? "succeeded_cleanup_failed" : "succeeded";
161
+ let cancellationMarker = false;
162
+ try { cancellationMarker = managedJobCancellationRequested(cancelFile); }
163
+ catch (error) {
164
+ mainError = mainError
165
+ ? new AggregateError([mainError, error], "managed job failed and cancellation state could not be verified")
166
+ : error;
167
+ }
168
+ const cancelled = mainError instanceof JobCancelledError || cancellationMarker;
169
+ const finalStatus = managedJobFinalStatus({ recover, cancelled, mainError, cleanupError });
170
+ if (cleanupError && classifyError(cleanupError) === "resource_error") {
171
+ const stage = cleanupError?.details?.admission_reason || cleanupError?.details?.reason || cleanupError?.code
172
+ || (/resource lease/i.test(String(cleanupError?.message || "")) ? "resource_lease" : "resource_operation");
173
+ try { process.stderr.write(`managed job cleanup infrastructure failure: stage=${sanitizeLogText(stage, 64)}\n`); }
174
+ catch { /* Last-resort diagnostics must not replace the managed job's primary failure. */ }
175
+ }
137
176
 
138
177
  const result = {
139
178
  job_id: status.job_id,
@@ -148,24 +187,23 @@ async function main(plan, initial) {
148
187
  capture_remaining_bytes: captureBudget.remaining,
149
188
  finished_at: new Date().toISOString(),
150
189
  };
151
- writeJson(resultFile, result, MAX_RESULT_BYTES);
152
- updateStatus(status, {
153
- status: finalStatus,
154
- current_phase: null,
155
- current_step: null,
156
- finished_at: result.finished_at,
157
- error_class: result.error_class || result.cleanup_error_class,
190
+ const terminal = persistManagedJobTerminal({
191
+ statusFile, resultFile,
192
+ artifacts: [runtimeDir, planFile, runnerPidFile, cancelFile],
193
+ status: { ...status, runner_pid: process.pid, runner_process_started_at: RUNNER_PROCESS_STARTED_AT },
194
+ result, writeJson,
195
+ removeFile: (file) => rmSync(file, { recursive: true, force: true }),
196
+ maxStatusBytes: MAX_STATUS_BYTES, maxResultBytes: MAX_RESULT_BYTES,
158
197
  });
159
- try { rmSync(planFile, { force: true }); } catch {}
160
- try { rmSync(runnerPidFile, { force: true }); } catch {}
161
- try { rmSync(cancelFile, { force: true }); } catch {}
198
+ Object.assign(status, terminal.status);
199
+ reportTerminalPersistenceFailure(terminal);
162
200
  }
163
201
 
164
202
  function assertLaunchState(status) {
165
203
  if (!status || typeof status !== "object" || Array.isArray(status)) throw new Error("job status is unavailable or invalid");
166
204
  const expected = recover ? "interrupted" : "queued";
167
205
  if (status.status !== expected) throw new Error(`runner cannot start job in status: ${status.status}`);
168
- if (status.approval === "pending-local-operator" || !status.approval) throw new Error("runner cannot start an unapproved managed job");
206
+ if (status.approval !== "mcp") throw new Error("runner cannot start a managed job without direct execution authority");
169
207
  }
170
208
 
171
209
  function assertPlanIntegrity(plan, status) {
@@ -176,10 +214,24 @@ function assertPlanIntegrity(plan, status) {
176
214
  }
177
215
 
178
216
  function recordFatalRunnerError(error) {
179
- rmSync(runtimeDir, { recursive: true, force: true });
180
217
  const now = new Date().toISOString();
181
- let status = {};
182
- try { status = readJson(statusFile, MAX_STATUS_BYTES); } catch {}
218
+ try {
219
+ process.stderr.write(`managed job runner fatal: error_class=${classifyError(error)} message=${sanitizeLogText(error?.message || error, 512)}\n`);
220
+ } catch { /* Last-resort diagnostics must not prevent terminal-state recovery. */ }
221
+ if (!runnerClaimConfirmed) {
222
+ reportFatalRecordSkipped("runner_claim_unconfirmed");
223
+ return;
224
+ }
225
+ let status;
226
+ try { status = readJson(statusFile, MAX_STATUS_BYTES); }
227
+ catch (stateError) {
228
+ reportFatalRecordSkipped(classifyError(stateError));
229
+ return;
230
+ }
231
+ if (!fatalRunnerStatusIsCurrent(status, initial)) {
232
+ reportFatalRecordSkipped("state_integrity");
233
+ return;
234
+ }
183
235
  const finalStatus = recover ? "recovery_failed" : "runner_failed";
184
236
  const result = {
185
237
  job_id: status.job_id ?? null,
@@ -192,27 +244,62 @@ function recordFatalRunnerError(error) {
192
244
  cleanup_error_class: recover ? classifyError(error) : null,
193
245
  finished_at: now,
194
246
  };
195
- try { writeJson(resultFile, result, MAX_RESULT_BYTES); } catch {}
196
- try {
197
- writeJson(statusFile, {
247
+ const terminal = persistManagedJobTerminal({
248
+ statusFile, resultFile,
249
+ artifacts: [runtimeDir, planFile, runnerPidFile, cancelFile],
250
+ status: {
198
251
  ...status,
199
- status: finalStatus,
200
- current_phase: null,
201
- current_step: null,
202
252
  runner_pid: process.pid,
203
253
  runner_process_started_at: RUNNER_PROCESS_STARTED_AT,
204
- updated_at: now,
205
- finished_at: now,
206
- error_class: result.error_class,
207
254
  cleanup_guarantee: "best-effort-finally-and-recovery",
208
- }, MAX_STATUS_BYTES);
209
- } catch {}
210
- try { rmSync(planFile, { force: true }); } catch {}
211
- try { rmSync(runnerPidFile, { force: true }); } catch {}
212
- try { rmSync(cancelFile, { force: true }); } catch {}
255
+ },
256
+ result, writeJson,
257
+ removeFile: (file) => rmSync(file, { recursive: true, force: true }),
258
+ maxStatusBytes: MAX_STATUS_BYTES, maxResultBytes: MAX_RESULT_BYTES,
259
+ });
260
+ reportTerminalPersistenceFailure(terminal);
261
+ }
262
+
263
+ function fatalRunnerStatusIsCurrent(status, accepted) {
264
+ if (!status || typeof status !== "object" || Array.isArray(status)
265
+ || !accepted || typeof accepted !== "object" || Array.isArray(accepted)) return false;
266
+ if (isTerminalManagedJobStatus(status.status) || !ACTIVE_JOB_STATES.has(status.status)) return false;
267
+ if (status.job_id !== basename(jobDir) || FATAL_IDENTITY_FIELDS.some((field) => status[field] !== accepted[field])) return false;
268
+ const runnerPid = Number(status.runner_pid || 0);
269
+ const acceptedPid = Number(accepted.runner_pid || 0);
270
+ if (runnerPid && runnerPid !== process.pid && runnerPid !== acceptedPid) return false;
271
+ const runnerStartedAt = String(status.runner_process_started_at || "");
272
+ const acceptedStartedAt = String(accepted.runner_process_started_at || "");
273
+ return !runnerStartedAt || runnerStartedAt === RUNNER_PROCESS_STARTED_AT || runnerStartedAt === acceptedStartedAt;
274
+ }
275
+
276
+ function reportFatalRecordSkipped(reason) {
277
+ try { process.stderr.write(`managed job fatal terminal record skipped: reason=${sanitizeLogText(reason, 64)}\n`); }
278
+ catch { /* A closed diagnostic stream is irrelevant to the already-decided state transition. */ }
279
+ }
280
+
281
+ function reportTerminalPersistenceFailure(terminal) {
282
+ if (terminal.statusPersisted && terminal.artifactsScrubbed && !terminal.statusErrorClass) return;
283
+ const fields = [
284
+ `status_persisted=${terminal.statusPersisted}`,
285
+ `result_persisted=${terminal.resultPersisted}`,
286
+ `artifacts_scrubbed=${terminal.artifactsScrubbed}`,
287
+ `status_error=${terminal.statusErrorClass || "none"}`,
288
+ `result_error=${terminal.resultErrorClass || "none"}`,
289
+ `cleanup_error=${terminal.cleanupErrorClass || "none"}`,
290
+ ];
291
+ try { process.stderr.write(`managed job terminal persistence incomplete: ${fields.join(" ")}\n`); }
292
+ catch { /* Persistence outcome is authoritative even if its fallback diagnostic cannot be written. */ }
293
+ }
294
+
295
+ function managedJobIdleSleepLogger() {
296
+ return { event(_level, _name, fields) {
297
+ try { process.stderr.write(`managed job idle-sleep assertion unavailable: error_class=${sanitizeLogText(fields?.error_class || "unknown", 64)}\n`); }
298
+ catch { /* Auxiliary power diagnostics must not replace managed-job settlement. */ }
299
+ } };
213
300
  }
214
301
 
215
- async function runStep(step, index, phase, plan, resourceContext, cancellationAware, captureBudget) {
302
+ async function runStep(step, index, phase, plan, resourceContext, cancellationAware, captureBudget, status) {
216
303
  const argv = step.argv.map((value) => substitute(value, plan, resourceContext));
217
304
  const envOverrides = Object.fromEntries(Object.entries(step.env || {}).map(([key, value]) => [key, substitute(value, plan, resourceContext)]));
218
305
  const envResourceValues = Object.fromEntries(Object.entries(step.env_resources || {}).map(([key, name]) => [key, resourceEnvValue(name, resourceContext.bytes)]));
@@ -235,6 +322,19 @@ async function runStep(step, index, phase, plan, resourceContext, cancellationAw
235
322
  cancellationAware,
236
323
  captureOutput: step.capture_output !== "discard",
237
324
  captureBudget,
325
+ resourcePriority: plan?.execution_priority === "interactive" ? "interactive" : "background",
326
+ delegatedProcess: plan?.delegated_process === true,
327
+ workspace: plan.workspace,
328
+ onAdmissionStart: () => updateStatus(status, {
329
+ status: phase === "steps" ? "running" : "cleaning",
330
+ current_phase: "resource_admission",
331
+ current_step: index,
332
+ }),
333
+ onAdmissionComplete: () => updateStatus(status, {
334
+ status: phase === "steps" ? "running" : "cleaning",
335
+ current_phase: phase === "finally_steps" && recover ? "recovery-cleanup" : phase,
336
+ current_step: index,
337
+ }),
238
338
  });
239
339
  return {
240
340
  index,
@@ -245,6 +345,7 @@ async function runStep(step, index, phase, plan, resourceContext, cancellationAw
245
345
  signal: raw.signal,
246
346
  timed_out: raw.timedOut,
247
347
  duration_ms: performance.now() - started,
348
+ resource_admission_ms: raw.resourceAdmissionMs,
248
349
  stdout: step.capture_output === "discard" ? "" : redactOutput(raw.stdout, resourceContext),
249
350
  stderr: step.capture_output === "discard" ? "" : redactOutput(raw.stderr, resourceContext),
250
351
  output_discarded: step.capture_output === "discard",
@@ -255,31 +356,87 @@ async function runStep(step, index, phase, plan, resourceContext, cancellationAw
255
356
  };
256
357
  }
257
358
 
258
- function spawnStep(argv, { cwd, env, input, timeoutMs, cancellationAware, captureOutput, captureBudget }) {
359
+ async function spawnStep(argv, {
360
+ cwd, env, input, timeoutMs, cancellationAware, captureOutput, captureBudget,
361
+ resourcePriority = "background", delegatedProcess = false, workspace = "",
362
+ onAdmissionStart = null, onAdmissionComplete = null,
363
+ }) {
364
+ if (cancellationAware && isCancellationRequested()) throw new JobCancelledError();
365
+ const admissionStarted = performance.now();
366
+ onAdmissionStart?.();
367
+ let admitted;
368
+ let resourceAdmissionMs = 0;
369
+ try {
370
+ admitted = await acquireProcessResources(resourceCoordinator, argv[0], argv.slice(1), env, {
371
+ cwd, priority: resourcePriority, waitMs: relayContract.maximumManagedJobResourceAdmissionWaitMs,
372
+ cancelCheck: () => { if (cancellationAware && isCancellationRequested()) throw new JobCancelledError(); },
373
+ });
374
+ onAdmissionComplete?.();
375
+ resourceAdmissionMs = performance.now() - admissionStarted;
376
+ } catch (error) {
377
+ if (admitted?.lease) {
378
+ try { await releaseProcessResources(admitted.lease); }
379
+ catch (cleanupError) {
380
+ throw new AggregateError([error, cleanupError], "managed job admission status update and resource lease cleanup both failed");
381
+ }
382
+ }
383
+ throw error;
384
+ }
259
385
  return new Promise((resolvePromise, rejectPromise) => {
260
- if (cancellationAware && isCancellationRequested()) {
261
- rejectPromise(new JobCancelledError());
386
+ let child;
387
+ try {
388
+ const launch = delegatedProcess
389
+ ? delegatedProcessCommand({
390
+ command: admitted.command,
391
+ args: admitted.args,
392
+ workspace,
393
+ runtimeDir,
394
+ forceDelegated: true,
395
+ })
396
+ : { command: admitted.command, args: admitted.args };
397
+ child = spawn(launch.command, launch.args, {
398
+ cwd, env: admitted.environment, detached: process.platform !== "win32", windowsHide: true,
399
+ stdio: ["pipe", "pipe", "pipe"],
400
+ });
401
+ } catch (error) {
402
+ void releaseProcessResourcesQuietly(admitted.lease).then(() => rejectPromise(error));
262
403
  return;
263
404
  }
264
- const child = spawn(argv[0], argv.slice(1), {
265
- cwd,
266
- env,
267
- detached: process.platform !== "win32",
268
- windowsHide: true,
269
- stdio: ["pipe", "pipe", "pipe"],
270
- });
271
405
  activeChild = child;
272
406
  activeChildCancellationAware = cancellationAware;
407
+ let terminationSettlement = null;
408
+ const terminateAndSettle = () => {
409
+ if (terminationSettlement) return terminationSettlement;
410
+ terminationSettlement = new Promise((resolveTermination) => {
411
+ try {
412
+ terminateProcessTreeWithEscalation(child, {
413
+ onTerminationSettled: resolveTermination,
414
+ });
415
+ } catch {
416
+ resolveTermination();
417
+ }
418
+ });
419
+ return terminationSettlement;
420
+ };
421
+ activeChildTermination = terminateAndSettle;
422
+ let resourceBindError = null; let childError = null;
423
+ const resourceBinding = bindProcessResources(admitted.lease, child).catch((error) => {
424
+ resourceBindError = error;
425
+ void terminateAndSettle();
426
+ });
273
427
  let stdout = Buffer.alloc(0);
274
428
  let stderr = Buffer.alloc(0);
275
429
  let stdoutTruncated = 0;
276
430
  let stderrTruncated = 0;
277
431
  let timedOut = false;
278
432
  let closed = false;
279
- let killTimer = null;
280
433
  const timer = setTimeout(() => {
434
+ if (childExitedBeforeTimeout({ exitCode: child.exitCode, signalCode: child.signalCode, processState: processState(child.pid) })) {
435
+ settlement.onExit(child.exitCode, child.signalCode);
436
+ return;
437
+ }
281
438
  timedOut = true;
282
- killTimer = terminateProcessTreeWithEscalation(child);
439
+ void terminateAndSettle();
283
440
  }, timeoutMs);
284
441
  timer.unref?.();
285
442
  const cancellationPoll = setInterval(() => {
@@ -300,38 +457,37 @@ function spawnStep(argv, { cwd, env, input, timeoutMs, cancellationAware, captur
300
457
  stderr = next.buffer;
301
458
  stderrTruncated += next.truncated;
302
459
  });
303
- child.on("error", (error) => finish(() => rejectPromise(error)));
304
- child.on("close", (code, signal) => finish(() => {
305
- if (cancellationAware && isCancellationRequested()) {
306
- rejectPromise(new JobCancelledError());
307
- return;
308
- }
309
- resolvePromise({
310
- code: Number.isInteger(code) ? code : 1,
311
- signal: signal ? String(signal) : null,
312
- timedOut,
313
- stdout,
314
- stderr,
315
- stdoutTruncated,
316
- stderrTruncated,
317
- });
318
- }));
460
+ const settlement = attachChildProcessSettlement(child, {
461
+ onSettle(code, signal) {
462
+ finish(() => {
463
+ if (resourceBindError) { rejectPromise(resourceBindError); return; }
464
+ if (childError) { rejectPromise(childError); return; }
465
+ if (cancellationAware && isCancellationRequested()) { rejectPromise(new JobCancelledError()); return; }
466
+ resolvePromise({
467
+ code: Number.isInteger(code) ? code : 1,
468
+ signal: signal ? String(signal) : null,
469
+ timedOut, resourceAdmissionMs, stdout, stderr, stdoutTruncated, stderrTruncated,
470
+ });
471
+ });
472
+ },
473
+ });
474
+ child.on("error", (error) => { childError ||= error; });
319
475
  if (input && input.length) child.stdin.end(input);
320
476
  else child.stdin.end();
321
477
 
322
478
  function finish(callback) {
323
479
  if (closed) return;
324
480
  closed = true;
481
+ settlement.cancel();
325
482
  clearTimeout(timer);
326
- if (killTimer && !timedOut) clearTimeout(killTimer);
327
483
  clearInterval(cancellationPoll);
328
484
  activeChild = null;
329
485
  activeChildCancellationAware = false;
330
- if (cancellationEscalation && !isCancellationRequested()) {
331
- clearTimeout(cancellationEscalation);
332
- cancellationEscalation = null;
333
- }
334
- callback();
486
+ activeChildTermination = null;
487
+ void resourceBinding
488
+ .then(() => terminationSettlement || undefined)
489
+ .then(() => releaseProcessResources(admitted.lease))
490
+ .then(callback, rejectPromise);
335
491
  }
336
492
  });
337
493
  }
@@ -362,7 +518,7 @@ function materializeResources(resources) {
362
518
  if (text.length > 0) patterns.push(text);
363
519
  const trimmed = text.replace(/[\r\n]+$/, "");
364
520
  if (trimmed.length > 0 && trimmed !== text) patterns.push(trimmed);
365
- } catch {}
521
+ } catch { /* Binary resources still use byte-level base64/hex redaction below. */ }
366
522
  if (data.length > 0 && data.length <= 64 * 1024) {
367
523
  patterns.push(data.toString("base64"), data.toString("hex"));
368
524
  }
@@ -471,16 +627,12 @@ function updateStatus(status, changes) {
471
627
 
472
628
  function requestCancellation() {
473
629
  cancelRequested = true;
474
- const child = activeChild;
475
- if (!child || !activeChildCancellationAware) return;
476
- if (cancellationEscalation) return;
477
- cancellationEscalation = terminateProcessTreeWithEscalation(child, {
478
- onEscalated: () => { cancellationEscalation = null; },
479
- });
630
+ if (!activeChild || !activeChildCancellationAware) return;
631
+ void activeChildTermination?.();
480
632
  }
481
633
 
482
634
  function isCancellationRequested() {
483
- return cancelRequested || existsSync(cancelFile);
635
+ return cancelRequested || managedJobCancellationRequested(cancelFile);
484
636
  }
485
637
 
486
638
  function appendLimited(current, chunk, limit, budget) {
@@ -504,7 +656,10 @@ function writeJson(file, value, maxBytes) {
504
656
  }
505
657
 
506
658
  function readJson(file, maxBytes) {
507
- return JSON.parse(readBoundedFile(file, maxBytes).toString("utf8"));
659
+ return JSON.parse(readBoundedRegularFileSync(file, maxBytes, "managed job runner state", {
660
+ verifyPathIdentity: true,
661
+ rejectMultipleLinks: true,
662
+ }).toString("utf8"));
508
663
  }
509
664
 
510
665
  function readBoundedFile(file, maxBytes) {
@@ -31,6 +31,12 @@ export class LifecycleController {
31
31
  this.transition("failed");
32
32
  }
33
33
 
34
+ markStopFailed(error) {
35
+ if (this.state !== "stopping") throw new BridgeError("conflict", `${this.name} cannot fail stop from state ${this.state}`);
36
+ this.failureCode = String(error?.code || error?.name || "execution_failed").slice(0, 64);
37
+ this.transition("stop_failed");
38
+ }
39
+
34
40
  beginStop() {
35
41
  if (this.state === "stopped" || this.state === "stopping") return false;
36
42
  this.transition("stopping");
package/src/local/log.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import process from "node:process";
2
2
  import os from "node:os";
3
3
  import { errorCode } from "./errors.mjs";
4
+ import { sanitizePortableLogText } from "../shared/log-redaction.mjs";
4
5
 
5
6
  const COLORS = {
6
7
  reset: "\x1b[0m",
@@ -18,20 +19,6 @@ const MAX_LOG_OBJECT_KEYS = 48;
18
19
  const LEVEL_RANK = Object.freeze({ debug: 10, info: 20, success: 20, warn: 30, error: 40 });
19
20
  const SENSITIVE_KEY = /(authorization|cookie|password|passwd|secret|token|api[_-]?key|private[_-]?key|credential)/i;
20
21
  const LOCAL_PATH_KEY = /(path|paths|cwd|workspace|directory|(?:^|[_-])dir(?:$|[_-])|root|home)/i;
21
- const SECRET_VALUE = /\b(?:account_admin|account_password|daemon_secret|token_version|mcp_at|mcp_code)_[A-Za-z0-9_-]+\b/g;
22
- const BEARER_VALUE = /\bBearer\s+[A-Za-z0-9._~+\/-]+=*\b/gi;
23
- const EMAIL_VALUE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
24
- const AWS_ACCESS_KEY = /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g;
25
- const GITHUB_TOKEN = /\bgh[pousr]_[A-Za-z0-9_]{30,}\b/g;
26
- const GITLAB_TOKEN = /\bglpat-[A-Za-z0-9_-]{20,}\b/g;
27
- const NPM_TOKEN = /\bnpm_[A-Za-z0-9]{30,}\b/g;
28
- const SLACK_TOKEN = /\bxox[aboprs]-[A-Za-z0-9-]{10,}\b/g;
29
- const GOOGLE_API_KEY = /\bAIza[A-Za-z0-9_-]{30,}\b/g;
30
- const PAYMENT_API_KEY = /\b(?:sk|rk|pk)_live_[A-Za-z0-9]{16,}\b/g;
31
- const JWT_VALUE = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g;
32
- const URL_CREDENTIALS = /https?:\/\/[^\s/@:"'<>]+:[^\s/@"'<>]+@[^\s/"'<>]+/gi;
33
- const API_SECRET = /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g;
34
- const PRIVATE_KEY_HEADER = /-----BEGIN\s+(?:(?:OPENSSH|RSA|EC|DSA)\s+|ENCRYPTED\s+)?PRIVATE\s+KEY-----/g;
35
22
  const HOME_PATHS = [...new Set([process.env.HOME, process.env.USERPROFILE, safeHomeDirectory()].filter(value => typeof value === "string" && value.length > 1))]
36
23
  .sort((left, right) => right.length - left.length);
37
24
 
@@ -46,24 +33,36 @@ export function createLogger(options = {}) {
46
33
 
47
34
  const write = (stream, level, label, color, message, fields) => {
48
35
  if (LEVEL_RANK[level] < LEVEL_RANK[minimumLevel]) return;
36
+ const sanitizedMessage = sanitizeLogText(message, MAX_LOG_MESSAGE_CHARS);
37
+ if (options.format === "json") {
38
+ const entry = sanitizeLogValue({
39
+ ...(fields && typeof fields === "object" ? fields : {}),
40
+ timestamp: new Date().toISOString(),
41
+ level: level === "success" ? "info" : level,
42
+ component,
43
+ message: sanitizedMessage,
44
+ });
45
+ stream.write(`${JSON.stringify(entry)}\n`);
46
+ return;
47
+ }
49
48
  const prefix = useColor ? `${color}${label}${COLORS.reset}` : label;
50
49
  const suffix = formatFields(fields);
51
- stream.write(`${prefix} ${component}: ${sanitizeLogText(message, MAX_LOG_MESSAGE_CHARS)}${suffix}\n`);
50
+ stream.write(`${prefix} ${component}: ${sanitizedMessage}${suffix}\n`);
52
51
  };
53
52
 
54
53
  const event = (level, name, fields = {}, message = "") => {
55
54
  const normalizedLevel = normalizeEventLevel(level);
56
55
  const eventName = sanitizeEventName(name);
57
- const payload = { event: eventName, ...fields };
56
+ const payload = { ...fields, event: eventName };
58
57
  const humanMessage = message || humanizeEventName(eventName);
59
58
  if (options.format === "json") {
60
59
  if (LEVEL_RANK[normalizedLevel] < LEVEL_RANK[minimumLevel]) return;
61
60
  const entry = sanitizeLogValue({
61
+ ...payload,
62
62
  timestamp: new Date().toISOString(),
63
63
  level: normalizedLevel === "success" ? "info" : normalizedLevel,
64
64
  component,
65
65
  message: sanitizeLogText(humanMessage, MAX_LOG_MESSAGE_CHARS),
66
- ...payload,
67
66
  });
68
67
  const target = normalizedLevel === "info" || normalizedLevel === "success" ? stdout : stderr;
69
68
  target.write(`${JSON.stringify(entry)}\n`);
@@ -83,6 +82,7 @@ export function createLogger(options = {}) {
83
82
  return {
84
83
  verbose: minimumLevel === "debug",
85
84
  level: minimumLevel,
85
+ format: options.format === "json" ? "json" : "text",
86
86
  child(childComponent) {
87
87
  return createLogger({ ...options, component: childComponent });
88
88
  },
@@ -137,7 +137,7 @@ function sanitizeLogValue(value, key = "", seen = new WeakSet(), depth = 0) {
137
137
  if (seen.has(value)) return "<circular>";
138
138
  seen.add(value);
139
139
  if (Array.isArray(value)) return value.slice(0, MAX_LOG_ARRAY_ITEMS).map(item => sanitizeLogValue(item, "", seen, depth + 1));
140
- const out = {};
140
+ const out = Object.create(null);
141
141
  for (const [childKey, childValue] of Object.entries(value).slice(0, MAX_LOG_OBJECT_KEYS)) {
142
142
  out[childKey] = sanitizeLogValue(childValue, childKey, seen, depth + 1);
143
143
  }
@@ -145,33 +145,7 @@ function sanitizeLogValue(value, key = "", seen = new WeakSet(), depth = 0) {
145
145
  }
146
146
 
147
147
  export function sanitizeLogText(value, maxChars = MAX_LOG_MESSAGE_CHARS) {
148
- let raw;
149
- try { raw = String(value ?? ""); } catch { raw = "<unprintable>"; }
150
- let sanitized = raw
151
- .replace(SECRET_VALUE, "<redacted-secret>")
152
- .replace(BEARER_VALUE, "Bearer <redacted>")
153
- .replace(AWS_ACCESS_KEY, "<redacted-cloud-key>")
154
- .replace(GITHUB_TOKEN, "<redacted-access-token>")
155
- .replace(GITLAB_TOKEN, "<redacted-access-token>")
156
- .replace(NPM_TOKEN, "<redacted-access-token>")
157
- .replace(SLACK_TOKEN, "<redacted-access-token>")
158
- .replace(GOOGLE_API_KEY, "<redacted-cloud-key>")
159
- .replace(PAYMENT_API_KEY, "<redacted-api-secret>")
160
- .replace(JWT_VALUE, "<redacted-bearer-token>")
161
- .replace(URL_CREDENTIALS, "<redacted-credential-url>")
162
- .replace(API_SECRET, "<redacted-api-secret>")
163
- .replace(PRIVATE_KEY_HEADER, "<redacted-private-key-header>")
164
- .replace(EMAIL_VALUE, "<redacted-email>");
165
- for (const home of HOME_PATHS) sanitized = sanitized.split(home).join("<home>");
166
- sanitized = sanitized
167
- .replace(/\/(?:Users|home)\/[^/\s"'<>]+(?=\/|$)/g, "<home>")
168
- .replace(/\b[A-Za-z]:\\Users\\[^\\\s"'<>]+(?=\\|$)/g, "<home>")
169
- .replace(/[\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]/g, "")
170
- .replace(/[\r\n\t]/g, match => match === "\t" ? "\\t" : "\\n")
171
- .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "?");
172
- if (!Number.isFinite(Number(maxChars)) || Number(maxChars) <= 0) return "";
173
- const limit = Math.max(16, Number(maxChars));
174
- return sanitized.length > limit ? `${sanitized.slice(0, limit - 1)}…` : sanitized;
148
+ return sanitizePortableLogText(value, { maxChars, homePaths: HOME_PATHS });
175
149
  }
176
150
 
177
151
  function safeHomeDirectory() {