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,20 @@
1
- import { closeSync, constants as fsConstants, existsSync, ftruncateSync, lstatSync, mkdirSync, readSync, realpathSync, rmSync, statSync, writeSync } from "node:fs";
1
+ import { closeSync, constants as fsConstants, existsSync, ftruncateSync, lstatSync, mkdirSync, readSync, realpathSync, statSync, writeSync } from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { runExecutable } from "./shell.mjs";
5
- import { ensureOwnerOnlyDir, expandHome } from "./state.mjs";
5
+ import { expandHome } from "./state.mjs";
6
+ import { ensureOwnerOnlyDir } from "./secure-file.mjs";
6
7
  import { replaceFileAtomicallySync } from "./exclusive-file.mjs";
7
8
  import { openRegularFileSync, readBoundedRegularFileSync } from "./secure-file.mjs";
8
- import { waitForInactiveStatus } from "./service-convergence.mjs";
9
+ import { waitForInactiveStatus, waitForStableActiveStatus, waitForStatus } from "./service-convergence.mjs";
10
+ import { launchdStatusSummary, systemdStatusSummary } from "./service-status.mjs";
11
+ import { systemdRemovalDecision } from "./systemd-removal.mjs";
12
+ import { removeServiceDefinitionIfCurrent, snapshotServiceDefinition } from "./service-definition.mjs";
9
13
  import { writeServiceEnvironment } from "./service-environment.mjs";
14
+ import { beginServiceOwnerUpdate, removeServiceOwner } from "./service-owner.mjs";
10
15
  import {
11
16
  installWindowsTask,
17
+ restartWindowsTask,
12
18
  startWindowsTask,
13
19
  statusWindowsTask,
14
20
  stopWindowsTask,
@@ -18,7 +24,8 @@ export { windowsCommandLineArgument } from "./windows-service.mjs";
18
24
 
19
25
  const LABEL = "dev.machine-bridge-mcp.daemon";
20
26
  const SERVICE_COMMAND_OUTPUT_BYTES = 64 * 1024;
21
- const AUTOSTART_LOG_SCHEMA_VERSION = 3;
27
+ const SERVICE_COMMAND_TIMEOUT_MS = 30_000;
28
+ const AUTOSTART_LOG_SCHEMA_VERSION = 4;
22
29
 
23
30
  function serviceRun(command, args) {
24
31
  return runServiceCommand(command, args);
@@ -28,24 +35,93 @@ export function runServiceCommand(command, args, execute = runExecutable) {
28
35
  return execute(command, args, {
29
36
  capture: true,
30
37
  allowFailure: true,
38
+ timeoutMs: SERVICE_COMMAND_TIMEOUT_MS,
31
39
  maxOutputBytes: SERVICE_COMMAND_OUTPUT_BYTES,
32
40
  });
33
41
  }
34
42
 
35
- export async function installAutostart({ workspace, stateRoot, entryScript, logger = console }) {
43
+ export async function installAutostart({ workspace, stateRoot, entryScript, version, logger = console,
44
+ installProvider = defaultInstallProvider, beginOwnerUpdate = beginServiceOwnerUpdate,
45
+ writeEnvironment = writeServiceEnvironment, deferOwnerCommit = false } = {}) {
36
46
  const spec = serviceSpec({ workspace, stateRoot, entryScript });
37
- const serviceEnvironment = writeServiceEnvironment(spec.stateRoot);
47
+ const ownerUpdate = beginOwnerUpdate({ ...spec, version });
48
+ let serviceEnvironment;
49
+ try { serviceEnvironment = writeEnvironment(spec.stateRoot); }
50
+ catch (error) {
51
+ try { ownerUpdate.rollback(); }
52
+ catch (rollbackError) {
53
+ throw new AggregateError([error, rollbackError],
54
+ "service environment preparation failed and the previous machine service owner could not be restored");
55
+ }
56
+ throw error;
57
+ }
38
58
  let result;
39
- if (process.platform === "darwin") result = await installLaunchd(spec, logger);
40
- else if (process.platform === "win32") result = await installWindowsTask(spec, logger, { run: serviceRun });
41
- else result = await installSystemd(spec, logger);
42
- return { ...result, service_environment: serviceEnvironment };
59
+ try { result = await installProvider(spec, logger); }
60
+ catch (error) {
61
+ throw new Error(
62
+ "autostart installation threw after its machine service owner became pending; service start is blocked until reinstall",
63
+ { cause: error },
64
+ );
65
+ }
66
+ if (result?.ok !== true) {
67
+ return withDeferredOwnerTransaction({
68
+ ...result,
69
+ service_environment: serviceEnvironment,
70
+ service_owner: { status: "pending", version },
71
+ reason: result?.reason || "installation_failed_owner_pending",
72
+ }, ownerUpdate, deferOwnerCommit);
73
+ }
74
+ if (deferOwnerCommit) {
75
+ return withDeferredOwnerTransaction({
76
+ ...result,
77
+ service_environment: serviceEnvironment,
78
+ service_owner: { status: "pending", version },
79
+ }, ownerUpdate, true);
80
+ }
81
+ let owner;
82
+ try { owner = ownerUpdate.commit(); }
83
+ catch (error) {
84
+ throw new Error(
85
+ "autostart definition was installed but its machine service owner could not be committed; the pending owner was retained and service start is blocked until reinstall",
86
+ { cause: error },
87
+ );
88
+ }
89
+ return { ...result, service_environment: serviceEnvironment, service_owner: { status: owner.status, version: owner.version } };
43
90
  }
44
91
 
92
+ function withDeferredOwnerTransaction(result, ownerUpdate, enabled) {
93
+ if (!enabled) return result;
94
+ if (!ownerUpdate?.owner || ownerUpdate.owner.status !== "pending"
95
+ || typeof ownerUpdate.commit !== "function" || typeof ownerUpdate.rollback !== "function") {
96
+ throw new Error("deferred machine service owner transaction is unavailable");
97
+ }
98
+ Object.defineProperty(result, "serviceOwnerTransaction", {
99
+ value: Object.freeze({
100
+ owner: ownerUpdate.owner,
101
+ commit: () => ownerUpdate.commit(),
102
+ rollback: () => ownerUpdate.rollback(),
103
+ }),
104
+ enumerable: false,
105
+ configurable: false,
106
+ writable: false,
107
+ });
108
+ return result;
109
+ }
110
+
111
+ async function defaultInstallProvider(spec, logger) {
112
+ if (process.platform === "darwin") return installLaunchd(spec, logger);
113
+ if (process.platform === "win32") return installWindowsTask(spec, logger, { run: serviceRun });
114
+ return installSystemd(spec, logger);
115
+ }
116
+
117
+
45
118
  export async function uninstallAutostart({ stateRoot, logger = console } = {}) {
46
- if (process.platform === "darwin") return uninstallLaunchd(logger);
47
- if (process.platform === "win32") return uninstallWindowsTask(logger, { run: serviceRun, stateRoot });
48
- return uninstallSystemd(logger);
119
+ let result;
120
+ if (process.platform === "darwin") result = await uninstallLaunchd(logger);
121
+ else if (process.platform === "win32") result = await uninstallWindowsTask(logger, { run: serviceRun, stateRoot });
122
+ else result = await uninstallSystemd(logger);
123
+ if (result?.ok === true) removeServiceOwner();
124
+ return result;
49
125
  }
50
126
 
51
127
  export async function autostartStatus() {
@@ -57,25 +133,19 @@ export async function autostartStatus() {
57
133
  export async function startAutostart({ logger = console } = {}) {
58
134
  if (process.platform === "darwin") return startLaunchd(logger);
59
135
  if (process.platform === "win32") return startWindowsTask(logger, { run: serviceRun });
60
- return normalizeServiceCommandResult("systemd", await serviceRun("systemctl", ["--user", "start", "machine-bridge-mcp.service"]));
136
+ return startSystemd(logger);
61
137
  }
62
138
 
63
- export async function stopAutostart({ logger = console } = {}) {
64
- if (process.platform === "darwin") return stopLaunchd(logger);
65
- if (process.platform === "win32") return stopWindowsTask(logger, { run: serviceRun });
66
- return normalizeServiceCommandResult("systemd", await serviceRun("systemctl", ["--user", "stop", "machine-bridge-mcp.service"]), { allowAlreadyStopped: true });
139
+ export async function restartAutostart({ logger = console } = {}) {
140
+ if (process.platform === "darwin") return restartLaunchd(logger);
141
+ if (process.platform === "win32") return restartWindowsTask(logger, { run: serviceRun });
142
+ return restartSystemd(logger);
67
143
  }
68
144
 
69
- export function normalizeServiceCommandResult(provider, result, { allowAlreadyStopped = false } = {}) {
70
- const detail = `${result?.stdout || ""}
71
- ${result?.stderr || ""}`;
72
- const alreadyStopped = allowAlreadyStopped && /(?:not loaded|not found|does not exist|cannot find|not running|inactive)/i.test(detail);
73
- return {
74
- ...result,
75
- ok: result?.code === 0 || alreadyStopped,
76
- provider,
77
- already_stopped: alreadyStopped,
78
- };
145
+ export async function stopAutostart({ logger = console } = {}) {
146
+ if (process.platform === "darwin") return stopLaunchdService(logger);
147
+ if (process.platform === "win32") return stopWindowsTask(logger, { run: serviceRun });
148
+ return stopSystemdService(logger);
79
149
  }
80
150
 
81
151
  export function trimAutostartLogs(stateRoot, options = {}) {
@@ -84,37 +154,43 @@ export function trimAutostartLogs(stateRoot, options = {}) {
84
154
  const keepBytes = Math.min(maxBytes, Number.isFinite(Number(options.keepBytes)) ? Math.max(1024, Number(options.keepBytes)) : 1024 * 1024);
85
155
  const schemaVersion = String(AUTOSTART_LOG_SCHEMA_VERSION);
86
156
  const logs = path.join(root, "logs");
157
+ ensureOwnerOnlyDir(logs);
87
158
  const schemaFile = path.join(logs, ".log-schema");
88
159
  const reset = readLogSchema(schemaFile) !== schemaVersion;
89
-
90
- for (const name of ["daemon.out.log", "daemon.err.log"]) {
91
- const file = path.join(logs, name);
92
- let fd;
93
- try {
94
- const opened = openRegularFileSync(file, fsConstants.O_RDWR, {
95
- label: "autostart log path",
96
- chmod: 0o600,
97
- });
98
- fd = opened.fd;
160
+ const openedLogs = [];
161
+ try {
162
+ for (const name of ["daemon.out.log", "daemon.err.log"]) {
163
+ const file = path.join(logs, name);
164
+ ensurePrivateLogFile(file);
165
+ openedLogs.push(openRegularFileSync(file, fsConstants.O_RDWR, {
166
+ label: "autostart log path", chmod: 0o600, rejectMultipleLinks: true,
167
+ }));
168
+ }
169
+ for (const opened of openedLogs) {
99
170
  if (reset) {
100
- ftruncateSync(fd, 0);
171
+ ftruncateSync(opened.fd, 0);
101
172
  } else if (opened.info.size > maxBytes) {
102
- const tail = readLogTail(fd, opened.info.size, keepBytes);
103
- ftruncateSync(fd, 0);
104
- if (tail.length) writeSync(fd, tail, 0, tail.length, 0);
173
+ const tail = readLogTail(opened.fd, opened.info.size, keepBytes);
174
+ ftruncateSync(opened.fd, 0);
175
+ if (tail.length) writeSync(opened.fd, tail, 0, tail.length, 0);
105
176
  }
106
- } catch {
107
- // Log maintenance is best effort and must not stop daemon startup.
108
- } finally {
109
- if (fd !== undefined) try { closeSync(fd); } catch {}
177
+ }
178
+ writePrivateServiceFile(schemaFile, `${schemaVersion}\n`);
179
+ } finally {
180
+ for (const opened of openedLogs) {
181
+ try { closeSync(opened.fd); }
182
+ catch { /* Descriptor cleanup cannot improve the already-completed log rotation outcome. */ }
110
183
  }
111
184
  }
112
-
113
- try { writePrivateServiceFile(schemaFile, `${schemaVersion}\n`); } catch {}
114
185
  }
115
186
 
116
187
  function readLogSchema(file) {
117
- try { return readBoundedRegularFileSync(file, 64).toString("utf8").trim(); } catch { return ""; }
188
+ try {
189
+ return readBoundedRegularFileSync(file, 64, "autostart log schema", {
190
+ verifyPathIdentity: true,
191
+ rejectMultipleLinks: true,
192
+ }).toString("utf8").trim();
193
+ } catch (error) { if (error?.code === "ENOENT") return null; throw error; }
118
194
  }
119
195
 
120
196
  function readLogTail(fd, size, limit) {
@@ -172,33 +248,37 @@ export function stableNodeExecutable(options = {}) {
172
248
  if (!sameExecutable) continue;
173
249
  if (platform !== "win32" && (statSync(candidate).mode & 0o111) === 0) continue;
174
250
  return candidate;
175
- } catch {}
251
+ } catch { /* Inaccessible or transient PATH candidates are skipped in favor of the known executable. */ }
176
252
  }
177
253
  return execPath;
178
254
  }
179
255
 
180
256
  export function serviceEnvironmentPath(options = {}) {
181
257
  const platform = String(options.platform || process.platform);
258
+ const pathApi = platform === "win32" ? path.win32 : path;
182
259
  const delimiter = String(options.delimiter || (platform === "win32" ? ";" : ":"));
183
260
  const pathEnv = String(options.pathEnv ?? process.env.PATH ?? "");
184
261
  const node = String(options.node || process.execPath || "");
185
262
  const entryScript = String(options.entryScript || "");
263
+ const entryDirectory = entryScript ? pathApi.dirname(pathApi.resolve(entryScript)) : "";
186
264
  const defaults = platform === "darwin"
187
265
  ? ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"]
188
266
  : platform === "win32"
189
267
  ? []
190
268
  : ["/usr/local/bin", "/usr/bin", "/bin", "/usr/local/sbin", "/usr/sbin", "/sbin"];
269
+ const inheritedEntries = stripNpmLifecyclePathPrefix(pathEnv.split(delimiter));
191
270
  const candidates = [
192
- node ? path.dirname(path.resolve(node)) : "",
193
- entryScript ? path.dirname(path.resolve(entryScript)) : "",
194
- ...pathEnv.split(delimiter),
271
+ node ? pathApi.dirname(pathApi.resolve(node)) : "",
272
+ entryDirectory,
273
+ ...inheritedEntries,
195
274
  ...defaults,
196
275
  ];
197
276
  const seen = new Set();
198
277
  const entries = [];
199
278
  for (const raw of candidates) {
200
- if (!raw || !path.isAbsolute(raw)) continue;
201
- const normalized = path.resolve(raw);
279
+ if (!raw || !pathApi.isAbsolute(raw)) continue;
280
+ const normalized = pathApi.resolve(raw);
281
+ if (isInactiveCandidateRuntimePath(normalized, entryDirectory, platform)) continue;
202
282
  const key = platform === "win32" ? normalized.toLowerCase() : normalized;
203
283
  if (seen.has(key)) continue;
204
284
  seen.add(key);
@@ -207,6 +287,33 @@ export function serviceEnvironmentPath(options = {}) {
207
287
  return entries.join(delimiter);
208
288
  }
209
289
 
290
+ function stripNpmLifecyclePathPrefix(entries) {
291
+ let lastMarker = -1;
292
+ for (let index = 0; index < entries.length; index += 1) {
293
+ const portable = String(entries[index] || "").replaceAll("\\", "/").toLowerCase();
294
+ if (portable.endsWith("/node-gyp-bin") && portable.includes("/@npmcli/run-script/")) lastMarker = index;
295
+ }
296
+ return lastMarker >= 0 ? entries.slice(lastMarker + 1) : entries;
297
+ }
298
+
299
+ function isInactiveCandidateRuntimePath(candidate, entryDirectory, platform) {
300
+ if (!entryDirectory) return false;
301
+ const pathApi = platform === "win32" ? path.win32 : path;
302
+ const normalize = (value) => {
303
+ const resolved = pathApi.resolve(value);
304
+ return platform === "win32" ? resolved.toLowerCase() : resolved;
305
+ };
306
+ const current = normalize(entryDirectory);
307
+ const portable = current.replaceAll("\\", "/");
308
+ const marker = "/release-channels/runtimes/";
309
+ const markerIndex = portable.indexOf(marker);
310
+ if (markerIndex < 0) return false;
311
+ const runtimeContainer = portable.slice(0, markerIndex + marker.length - 1);
312
+ const normalizedCandidate = normalize(candidate).replaceAll("\\", "/");
313
+ return normalizedCandidate !== portable
314
+ && (normalizedCandidate === runtimeContainer || normalizedCandidate.startsWith(`${runtimeContainer}/`));
315
+ }
316
+
210
317
  export function daemonArgs(spec) {
211
318
  return [
212
319
  spec.entryScript,
@@ -223,7 +330,7 @@ function ensurePrivateLogFile(file) {
223
330
  const opened = openRegularFileSync(
224
331
  file,
225
332
  Number(fsConstants.O_WRONLY) | Number(fsConstants.O_CREAT) | Number(fsConstants.O_APPEND),
226
- { label: "autostart log path", mode: 0o600, chmod: 0o600 },
333
+ { label: "autostart log path", mode: 0o600, chmod: 0o600, rejectMultipleLinks: true },
227
334
  );
228
335
  closeSync(opened.fd);
229
336
  }
@@ -260,31 +367,94 @@ async function installLaunchd(spec, logger) {
260
367
 
261
368
  async function startLaunchd(logger) {
262
369
  const plistPath = launchdPlistPath();
263
- const target = launchdDomainTarget();
264
- if (!existsSync(plistPath)) return { ok: false, error: "launchd plist not installed" };
265
- const stopped = await stopLaunchd({ info() {}, warn() {} });
266
- if (!stopped.ok) return { ok: false, provider: "launchd", stop: stopped };
267
- const boot = await serviceRun("launchctl", ["bootstrap", target, plistPath]);
268
- const kick = await serviceRun("launchctl", ["kickstart", "-k", `${target}/${LABEL}`]);
269
- const ok = boot.code === 0 || kick.code === 0;
370
+ const domainTarget = launchdDomainTarget();
371
+ const serviceTarget = launchdServiceTarget();
372
+ if (!existsSync(plistPath)) return { ok: false, provider: "launchd", installed: false, loaded: false, active: false, reason: "not_installed" };
373
+ const before = await statusLaunchd();
374
+ if (before.status_available !== true || typeof before.active !== "boolean") {
375
+ return { ok: false, provider: "launchd", installed: true, loaded: null, active: null, reason: "status_unavailable", status: before };
376
+ }
377
+ if (before.active) {
378
+ const existing = await waitForStableActiveStatus(statusLaunchd);
379
+ if (existing.stable) {
380
+ logger.info?.("launchd service is already running");
381
+ return { ...existing.status, ok: true, active_before: true, already_running: true, reason: "already_running" };
382
+ }
383
+ }
384
+ const boot = before.loaded
385
+ ? { code: 0, stdout: "", stderr: "", already_loaded: true }
386
+ : await serviceRun("launchctl", ["bootstrap", domainTarget, plistPath]);
387
+ const kick = await serviceRun("launchctl", ["kickstart", serviceTarget]);
388
+ const stability = await waitForStableActiveStatus(statusLaunchd);
389
+ const after = stability.status;
390
+ const ok = stability.stable;
270
391
  if (ok) logger.info?.("launchd service started");
271
392
  else logger.warn?.("launchd service failed to start");
272
- return { ok, provider: "launchd", bootstrap: boot, kickstart: kick };
393
+ return {
394
+ ok,
395
+ provider: "launchd",
396
+ installed: true,
397
+ loaded: after?.loaded === true,
398
+ active_before: false,
399
+ active: after?.active === true,
400
+ bootstrap: boot,
401
+ kickstart: kick,
402
+ };
273
403
  }
274
404
 
275
- async function stopLaunchd(logger) {
405
+ async function restartLaunchd(logger) {
276
406
  const plistPath = launchdPlistPath();
277
- const domainTarget = launchdDomainTarget();
278
- const serviceTarget = launchdServiceTarget();
407
+ if (!existsSync(plistPath)) return { ok: false, provider: "launchd", installed: false, reason: "not_installed" };
279
408
  const before = await statusLaunchd();
409
+ if (before.status_available !== true || typeof before.active !== "boolean") {
410
+ return { ok: false, provider: "launchd", installed: true, active: null, restarted: false, reason: "status_unavailable", status: before };
411
+ }
280
412
  if (!before.active) {
413
+ const started = await startLaunchd(logger);
414
+ return { ...started, restarted: false, reason: started.ok ? "started_inactive_service" : started.reason || "start_failed" };
415
+ }
416
+ const target = launchdServiceTarget();
417
+ const previousPid = before.pid;
418
+ const kick = await serviceRun("launchctl", ["kickstart", "-k", target]);
419
+ const changed = await waitForStatus(
420
+ statusLaunchd,
421
+ (status) => status?.active === true && (!previousPid || status.pid !== previousPid),
422
+ );
423
+ const stability = changed?.active === true && (!previousPid || changed.pid !== previousPid)
424
+ ? await waitForStableActiveStatus(statusLaunchd, { identity: (status) => status?.pid || null })
425
+ : { stable: false, status: changed };
426
+ const after = stability.status;
427
+ const ok = kick.code === 0 && stability.stable && (!previousPid || after?.pid !== previousPid);
428
+ if (ok) logger.info?.("launchd service restarted");
429
+ else logger.warn?.("launchd service restart could not be verified");
430
+ return { ok, provider: "launchd", installed: true, active_before: true, active: after?.active === true && stability.stable, restarted: ok, kickstart: kick };
431
+ }
432
+
433
+ export async function stopLaunchdService(logger = console, options = {}) {
434
+ const plistPath = launchdPlistPath();
435
+ const run = typeof options.run === "function" ? options.run : serviceRun;
436
+ const readStatus = typeof options.readStatus === "function" ? options.readStatus : statusLaunchd;
437
+ const waitForUnloaded = typeof options.waitForUnloaded === "function"
438
+ ? options.waitForUnloaded
439
+ : callback => waitForStatus(callback, launchdStatusIsSafelyUnloaded, { attempts: 100, delayMs: 100 });
440
+ const before = await readStatus();
441
+ if (!before.loaded) {
442
+ if (!launchdStatusIsSafelyUnloaded(before)) {
443
+ return {
444
+ ok: false, provider: "launchd", installed: before?.installed === true, loaded: null,
445
+ active_before: null, active: null, restore_required: null, already_stopped: false,
446
+ reason: "status_unavailable", status: before,
447
+ };
448
+ }
281
449
  logger.info?.("launchd service is not loaded");
282
450
  return {
283
451
  ok: true,
284
452
  provider: "launchd",
285
- installed: existsSync(plistPath),
453
+ installed: before?.installed === true,
454
+ loaded: false,
286
455
  active_before: false,
287
456
  active: false,
457
+ restore_required: false,
288
458
  already_stopped: true,
289
459
  code: 0,
290
460
  stdout: "",
@@ -292,44 +462,61 @@ async function stopLaunchd(logger) {
292
462
  };
293
463
  }
294
464
 
295
- const byServiceTarget = await serviceRun("launchctl", ["bootout", serviceTarget]);
465
+ const uid = options.uid === undefined ? process.getuid?.() : options.uid;
466
+ const domainTarget = launchdDomainTarget(uid);
467
+ const serviceTarget = launchdServiceTarget(uid);
468
+ const byServiceTarget = await run("launchctl", ["bootout", serviceTarget]);
296
469
  const byPlist = byServiceTarget.code === 0
297
470
  ? null
298
- : await serviceRun("launchctl", ["bootout", domainTarget, plistPath]);
299
- const after = await waitForInactiveStatus(statusLaunchd);
471
+ : await run("launchctl", ["bootout", domainTarget, plistPath]);
472
+ const after = await waitForUnloaded(readStatus);
300
473
  const rawResult = byPlist || byServiceTarget;
301
- const active = after?.active !== false;
302
- const ok = !active;
474
+ const ok = launchdStatusIsSafelyUnloaded(after);
303
475
  if (ok) logger.info?.("launchd service stopped");
304
- else logger.warn?.("launchd service is still active after the stop request");
476
+ else logger.warn?.("launchd service removal from the launchd domain could not be verified");
305
477
  return {
306
478
  ...rawResult,
307
479
  ok,
308
480
  provider: "launchd",
309
- installed: existsSync(plistPath),
310
- active_before: true,
311
- active,
481
+ installed: after?.installed === true,
482
+ loaded: ok ? false : after?.loaded ?? null,
483
+ active_before: before.active === true,
484
+ active: ok ? false : after?.active ?? null,
485
+ // Once bootout is dispatched, an initially active service may still
486
+ // disappear after this observation window. Preserve rollback intent even
487
+ // when stop settlement itself is inconclusive.
488
+ restore_required: before.active === true,
489
+ mutation_attempted: true,
312
490
  already_stopped: false,
313
491
  code: ok ? 0 : rawResult.code,
492
+ reason: ok ? "stopped" : "stop_not_observed",
493
+ status: after,
314
494
  bootout_service_target: byServiceTarget,
315
495
  ...(byPlist ? { bootout_plist_fallback: byPlist } : {}),
316
496
  };
317
497
  }
318
498
 
499
+ function launchdStatusIsSafelyUnloaded(status) {
500
+ return status?.status_available === true && status?.loaded === false && status?.active === false;
501
+ }
502
+
319
503
  async function uninstallLaunchd(logger) {
320
- const stopped = await stopLaunchd(logger);
321
504
  const plistPath = launchdPlistPath();
505
+ const definitionIdentity = snapshotServiceDefinition(plistPath, "launchd service definition");
506
+ const stopped = await stopLaunchdService(logger);
322
507
  if (!stopped.ok) return { ok: false, provider: "launchd", path: plistPath, stop: stopped };
323
- if (existsSync(plistPath)) rmSync(plistPath, { force: true });
508
+ if (!removeServiceDefinitionIfCurrent(plistPath, definitionIdentity, "launchd service definition")) {
509
+ logger.warn?.("launchd service definition changed during removal; the replacement was retained.");
510
+ return { ok: false, provider: "launchd", path: plistPath, stop: stopped, reason: "definition_changed" };
511
+ }
324
512
  logger.info?.("Autostart removed.");
325
513
  return { ok: true, provider: "launchd", path: plistPath, stop: stopped };
326
514
  }
327
515
 
328
516
  async function statusLaunchd() {
329
517
  const plistPath = launchdPlistPath();
330
- const target = launchdServiceTarget();
331
- const result = await serviceRun("launchctl", ["print", target]);
332
- return { ok: existsSync(plistPath), provider: "launchd", installed: existsSync(plistPath), path: plistPath, active: result.code === 0, detail: result.stdout || result.stderr };
518
+ const result = await serviceRun("launchctl", ["print", launchdServiceTarget()]);
519
+ return launchdStatusSummary({ installed: existsSync(plistPath), definition: LABEL, result });
333
520
  }
334
521
 
335
522
  export function launchdPlist({ args, pathEnv, stdout, stderr }) {
@@ -377,26 +564,132 @@ async function installSystemd(spec, logger) {
377
564
 
378
565
  async function uninstallSystemd(logger) {
379
566
  const servicePath = systemdPath();
567
+ const definitionIdentity = snapshotServiceDefinition(servicePath, "systemd service definition");
568
+ const definitionPresent = definitionIdentity !== null;
380
569
  const disable = await serviceRun("systemctl", ["--user", "disable", "--now", "machine-bridge-mcp.service"]);
381
570
  const activeCheck = await serviceRun("systemctl", ["--user", "is-active", "machine-bridge-mcp.service"]);
382
- const active = activeCheck.code === 0;
383
- if (active) {
384
- logger.warn?.("systemd service is still active; its definition was not removed.");
385
- return { ok: false, provider: "systemd", path: servicePath, disable, active_check: activeCheck, active: true };
571
+ const status = systemdStatusSummary({ installed: definitionPresent, definition: "machine-bridge-mcp.service", result: activeCheck });
572
+ const decision = systemdRemovalDecision({ definitionPresent, disableCode: disable.code, status });
573
+ if (!decision.removable) {
574
+ logger.warn?.("systemd service removal could not be verified; its definition was not removed.");
575
+ return { ok: false, provider: "systemd", path: servicePath, disable, active_check: activeCheck, status, active: status.active, reason: decision.reason };
576
+ }
577
+ if (!removeServiceDefinitionIfCurrent(servicePath, definitionIdentity, "systemd service definition")) {
578
+ logger.warn?.("systemd service definition changed during removal; the replacement was retained.");
579
+ return { ok: false, provider: "systemd", path: servicePath, disable, active_check: activeCheck, status, active: false, reason: "definition_changed" };
386
580
  }
387
- if (existsSync(servicePath)) rmSync(servicePath, { force: true });
388
581
  const reload = await serviceRun("systemctl", ["--user", "daemon-reload"]);
389
- const ok = reload.code === 0 && (disable.code === 0 || /not loaded|not found|does not exist/i.test(`${disable.stdout}
390
- ${disable.stderr}`));
582
+ const ok = reload.code === 0;
391
583
  if (ok) logger.info?.("Autostart removed.");
392
584
  else logger.warn?.("Autostart removal reported an error.");
393
- return { ok, provider: "systemd", path: servicePath, disable, active_check: activeCheck, reload, active: false };
585
+ return { ok, provider: "systemd", path: servicePath, disable, active_check: activeCheck, status, reload, active: false, already_absent: decision.alreadyAbsent, reason: ok ? decision.reason : "reload_failed" };
586
+ }
587
+
588
+ export async function stopSystemdService(logger = console, options = {}) {
589
+ const run = typeof options.run === "function" ? options.run : serviceRun;
590
+ const readStatus = typeof options.readStatus === "function" ? options.readStatus : statusSystemd;
591
+ const waitForInactive = typeof options.waitForInactive === "function"
592
+ ? options.waitForInactive
593
+ : callback => waitForInactiveStatus(callback);
594
+ const before = await readStatus();
595
+ if (systemdStatusIsInactive(before)) {
596
+ logger.info?.("systemd service is not active");
597
+ return {
598
+ ok: true,
599
+ provider: "systemd",
600
+ installed: before?.installed === true,
601
+ active_before: false,
602
+ active: false,
603
+ restore_required: false,
604
+ already_stopped: true,
605
+ state: before?.state || "inactive",
606
+ };
607
+ }
608
+ if (before?.active !== true && ["unknown", "maintenance"].includes(before?.state)) {
609
+ return {
610
+ ok: false,
611
+ provider: "systemd",
612
+ installed: before?.installed === true,
613
+ active_before: null,
614
+ active: null,
615
+ restore_required: null,
616
+ reason: "status_unavailable",
617
+ status: before,
618
+ };
619
+ }
620
+ const command = await run("systemctl", ["--user", "stop", "machine-bridge-mcp.service"]);
621
+ const after = await waitForInactive(readStatus);
622
+ const inactive = systemdStatusIsInactive(after);
623
+ if (inactive) logger.info?.("systemd service stopped");
624
+ else logger.warn?.("systemd service stop could not be verified");
625
+ return {
626
+ ok: inactive,
627
+ provider: "systemd",
628
+ installed: after?.installed === true,
629
+ active_before: before?.active === true,
630
+ active: inactive ? false : after?.active ?? null,
631
+ restore_required: inactive && systemdStatusRequiresRestore(before),
632
+ already_stopped: false,
633
+ command,
634
+ status: after,
635
+ reason: inactive ? "stopped" : "stop_not_observed",
636
+ };
637
+ }
638
+
639
+ function systemdStatusIsInactive(status) {
640
+ if (status?.active !== false) return false;
641
+ if (["inactive", "failed"].includes(status?.state)) return true;
642
+ return status?.installed === false && status?.state === "unknown";
643
+ }
644
+ function systemdStatusRequiresRestore(status) {
645
+ return status?.active === true || ["activating", "reloading"].includes(status?.state);
646
+ }
647
+
648
+ async function startSystemd(logger) {
649
+ const before = await statusSystemd();
650
+ if (typeof before.active !== "boolean") {
651
+ return { ok: false, provider: "systemd", installed: before.installed === true, active: null, reason: "status_unavailable", status: before };
652
+ }
653
+ if (before.installed !== true) {
654
+ return { ok: false, provider: "systemd", installed: false, active: false, reason: "not_installed", status: before };
655
+ }
656
+ if (before.active) {
657
+ const existing = await waitForStableActiveStatus(statusSystemd);
658
+ if (existing.stable) {
659
+ logger.info?.("systemd service is already running");
660
+ return { ...existing.status, ok: true, active_before: true, already_running: true, reason: "already_running" };
661
+ }
662
+ }
663
+ const command = await serviceRun("systemctl", ["--user", "start", "machine-bridge-mcp.service"]);
664
+ const stability = await waitForStableActiveStatus(statusSystemd);
665
+ const after = stability.status;
666
+ const ok = command.code === 0 && stability.stable;
667
+ if (ok) logger.info?.("systemd service started");
668
+ else logger.warn?.("systemd service failed to start");
669
+ return { ok, provider: "systemd", installed: after.installed, active_before: false, active: after.active, command };
670
+ }
671
+
672
+ async function restartSystemd(logger) {
673
+ const before = await statusSystemd();
674
+ if (typeof before.active !== "boolean") {
675
+ return { ok: false, provider: "systemd", installed: before.installed === true, active: null, restarted: false, reason: "status_unavailable", status: before };
676
+ }
677
+ if (before.installed !== true) {
678
+ return { ok: false, provider: "systemd", installed: false, active: false, restarted: false, reason: "not_installed", status: before };
679
+ }
680
+ const command = await serviceRun("systemctl", ["--user", "restart", "machine-bridge-mcp.service"]);
681
+ const stability = await waitForStableActiveStatus(statusSystemd);
682
+ const after = stability.status;
683
+ const ok = command.code === 0 && stability.stable;
684
+ if (ok) logger.info?.("systemd service restarted");
685
+ else logger.warn?.("systemd service restart could not be verified");
686
+ return { ok, provider: "systemd", installed: after.installed, active: after.active, restarted: ok, command };
394
687
  }
395
688
 
396
689
  async function statusSystemd() {
397
690
  const servicePath = systemdPath();
398
- const result = await serviceRun("systemctl", ["--user", "status", "machine-bridge-mcp.service", "--no-pager"]);
399
- return { ok: existsSync(servicePath), provider: "systemd", installed: existsSync(servicePath), path: servicePath, active: result.code === 0, detail: result.stdout || result.stderr };
691
+ const result = await serviceRun("systemctl", ["--user", "is-active", "machine-bridge-mcp.service"]);
692
+ return systemdStatusSummary({ installed: existsSync(servicePath), definition: "machine-bridge-mcp.service", result });
400
693
  }
401
694
 
402
695
  export function systemdUnit(spec) {