@moretea-labs/forge 1.7.2 → 1.8.1

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 (900) hide show
  1. package/AGENTS.md +121 -62
  2. package/CHANGELOG.md +40 -0
  3. package/CLAUDE.md +57 -62
  4. package/CONTRIBUTING.md +3 -2
  5. package/README.md +2 -2
  6. package/SKILL.md +0 -1
  7. package/THIRD_PARTY_NOTICES.md +5 -2
  8. package/adapters/browser/index.ts +2 -0
  9. package/adapters/chatgpt/browser-delivery-host.ts +77 -0
  10. package/adapters/chatgpt/browser-delivery-runtime.ts +1256 -0
  11. package/adapters/chatgpt/controller-binding-store.ts +134 -0
  12. package/adapters/chatgpt/controller-round-host.ts +68 -0
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +99 -0
  14. package/adapters/chatgpt/provider-delivery.ts +340 -0
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +218 -0
  16. package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
  17. package/adapters/computer/chatgpt-extension-target.ts +463 -0
  18. package/adapters/computer/chatgpt-target-router.ts +103 -0
  19. package/adapters/computer/chrome-extension/background.js +385 -0
  20. package/adapters/computer/chrome-extension/content.js +378 -0
  21. package/adapters/computer/chrome-extension/core.js +24 -0
  22. package/adapters/computer/chrome-extension/manifest.json +30 -0
  23. package/adapters/computer/desktop-operator-contract.ts +7 -0
  24. package/adapters/computer/desktop-operator-discovery.ts +181 -0
  25. package/adapters/computer/desktop-operator-negotiation.ts +174 -0
  26. package/adapters/computer/desktop-operator-provider.ts +312 -0
  27. package/adapters/computer/index.ts +1 -0
  28. package/adapters/computer/interaction-target-authority.ts +1068 -0
  29. package/adapters/controller-process/binding-store.ts +34 -0
  30. package/adapters/controller-process/controller-host.ts +26 -0
  31. package/adapters/mcp/access-mode.ts +132 -0
  32. package/adapters/mcp/audit.ts +32 -0
  33. package/adapters/mcp/auth.ts +406 -0
  34. package/adapters/mcp/instructions.ts +37 -0
  35. package/adapters/mcp/legacy-context.ts +18 -0
  36. package/adapters/mcp/multi-repository.ts +421 -0
  37. package/adapters/mcp/oauth.ts +464 -0
  38. package/adapters/mcp/paths.ts +143 -0
  39. package/adapters/mcp/policy.ts +232 -0
  40. package/adapters/mcp/proxy-env.ts +62 -0
  41. package/adapters/mcp/readiness-projection.ts +24 -0
  42. package/adapters/mcp/redaction.ts +21 -0
  43. package/adapters/mcp/repo.ts +24 -0
  44. package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
  45. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
  46. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  47. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  48. package/adapters/mcp/runtime-gateway/context-adapter.ts +1167 -0
  49. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
  50. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +206 -0
  51. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  52. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +130 -0
  53. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  54. package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
  55. package/adapters/mcp/runtime-gateway/execution-tools.ts +173 -0
  56. package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
  57. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +412 -0
  58. package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
  59. package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
  60. package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
  61. package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
  62. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +380 -0
  63. package/adapters/mcp/runtime-gateway/process-tools.ts +457 -0
  64. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +88 -0
  65. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +285 -0
  66. package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
  67. package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
  68. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +79 -0
  69. package/adapters/mcp/runtime-gateway/result-adapter.ts +114 -0
  70. package/adapters/mcp/runtime-gateway/router.ts +242 -0
  71. package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
  72. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1200 -0
  73. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  74. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +574 -0
  75. package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
  76. package/adapters/mcp/runtime-gateway/runtime-tools.ts +78 -0
  77. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +187 -0
  78. package/adapters/mcp/runtime-gateway/shared-adapter.ts +45 -0
  79. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +962 -0
  80. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
  81. package/adapters/mcp/runtime-gateway/work-adapter.ts +325 -0
  82. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  83. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  84. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
  85. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  86. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  87. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  88. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  89. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  90. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
  91. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
  92. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  93. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
  94. package/adapters/mcp/server.ts +1092 -0
  95. package/adapters/mcp/setup.ts +789 -0
  96. package/adapters/mcp/tool-capability-cache.ts +87 -0
  97. package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
  98. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5139 -0
  99. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +145 -0
  100. package/adapters/mcp/tool-mapping/repository-tools.ts +1682 -0
  101. package/adapters/mcp/tool-mapping/tools.ts +9 -0
  102. package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
  103. package/adapters/mcp/toolset-names.ts +79 -0
  104. package/adapters/mcp/toolset-selection.ts +41 -0
  105. package/adapters/mcp/toolset.ts +337 -0
  106. package/adapters/mcp/transports/http-observation.ts +365 -0
  107. package/adapters/mcp/transports/http.ts +933 -0
  108. package/adapters/mcp/transports/oauth-http.ts +322 -0
  109. package/adapters/mcp/transports/session-registry.ts +519 -0
  110. package/adapters/mcp/transports/stdio.ts +8 -0
  111. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +223 -0
  112. package/adapters/mcp/types.ts +41 -0
  113. package/assets/AGENTS.md +2 -16
  114. package/assets/CLAUDE.md +2 -16
  115. package/assets/hooks/AGENTS.md +3 -14
  116. package/assets/hooks/CLAUDE.md +3 -14
  117. package/assets/hooks/hook-input.sh +9 -2
  118. package/assets/hooks/lib/workflow-state.sh +28 -10
  119. package/assets/hooks/prompt-guard.sh +63 -37
  120. package/assets/hooks/security-sentinel.sh +2 -1
  121. package/assets/hooks/session-start-context.sh +7 -5
  122. package/assets/hooks/stop-orchestrator.sh +13 -3
  123. package/assets/initializer-question-pack.v4.json +11 -7
  124. package/assets/partials/05-workflow.partial.md +18 -73
  125. package/assets/partials/07-footer.partial.md +1 -1
  126. package/assets/partials-agents/02-operating-mode.partial.md +1 -2
  127. package/assets/partials-agents/04-task-protocol.partial.md +14 -59
  128. package/assets/partials-agents/06-quality-safety.partial.md +11 -16
  129. package/assets/partials-agents/08-deep-docs.partial.md +1 -2
  130. package/assets/plan-map.json +11 -11
  131. package/assets/plugin-registry.v1.json +11 -3
  132. package/assets/plugin-trust-policy.v1.json +6 -0
  133. package/assets/recovery/ForgeRecovery.ps1 +100 -0
  134. package/assets/recovery/forge-wsl-rescue +271 -0
  135. package/assets/reference-configs/agentic-development-flow.md +25 -0
  136. package/assets/reference-configs/development-protocol.md +11 -15
  137. package/assets/reference-configs/document-generation.md +1 -1
  138. package/assets/reference-configs/global-working-rules.md +28 -1
  139. package/assets/reference-configs/harness-overview.md +22 -112
  140. package/assets/reference-configs/hook-operations.md +1 -1
  141. package/assets/reference-configs/sprint-contracts.md +2 -2
  142. package/assets/skill-version.json +2 -2
  143. package/assets/templates/helpers/architecture-event.ts +3 -34
  144. package/assets/templates/helpers/architecture-queue.sh +61 -10
  145. package/assets/templates/helpers/archive-workflow.sh +90 -180
  146. package/assets/templates/helpers/capability-config.ts +8 -11
  147. package/assets/templates/helpers/capability-resolver.ts +37 -19
  148. package/assets/templates/helpers/capture-plan.sh +2 -2
  149. package/assets/templates/helpers/check-architecture-sync.sh +1 -9
  150. package/assets/templates/helpers/check-task-sync.sh +82 -11
  151. package/assets/templates/helpers/check-task-workflow.sh +1 -3
  152. package/assets/templates/helpers/context-contract-sync.sh +1 -12
  153. package/assets/templates/helpers/contract-worktree.sh +26 -24
  154. package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
  155. package/assets/templates/helpers/inspect-project-state.ts +1 -1
  156. package/assets/templates/helpers/migrate-project-template.sh +1 -1
  157. package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
  158. package/assets/templates/helpers/new-plan.sh +2 -2
  159. package/assets/templates/helpers/plan-to-todo.sh +3 -31
  160. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  161. package/assets/templates/helpers/switch-plan.sh +1 -1
  162. package/assets/templates/helpers/workflow-contract.ts +1 -0
  163. package/assets/workflow-contract.v1.json +63 -171
  164. package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
  165. package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
  166. package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
  167. package/docs/architecture/CURRENT.md +233 -28
  168. package/docs/architecture/EVOLUTION.md +13 -0
  169. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
  170. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  171. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  172. package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
  173. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
  174. package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
  175. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +37 -0
  176. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +311 -0
  177. package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
  178. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  179. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  180. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
  181. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  182. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
  183. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
  184. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  185. package/docs/architecture/domains/verification.md +8 -0
  186. package/docs/architecture/domains/workflow-engine.md +7 -1
  187. package/docs/architecture/index.md +3 -3
  188. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
  189. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
  190. package/docs/forge-chatgpt-mcp-setup.md +5 -3
  191. package/docs/forge-plugin-management.md +20 -4
  192. package/docs/forge-runtime-self-healing-loop.md +2 -0
  193. package/docs/operations/benchmark-cross-version-freeze.md +38 -0
  194. package/docs/operations/controller-browser-plugin.md +3 -1
  195. package/docs/operations/controller-performance-and-502.md +17 -0
  196. package/docs/operations/controller-reliability-runbook.md +2 -2
  197. package/docs/operations/features.md +1 -1
  198. package/docs/operations/features.zh-CN.md +1 -1
  199. package/docs/operations/homebrew.md +2 -2
  200. package/docs/operations/mcp-tool-exposure.md +2 -0
  201. package/docs/operations/platform-support.md +16 -8
  202. package/docs/operations/platform-support.zh-CN.md +2 -2
  203. package/docs/operations/releasing.md +2 -0
  204. package/docs/operations/standalone-disaster-recovery.md +38 -5
  205. package/docs/operations/uu-remote-rescue.md +41 -0
  206. package/docs/operations/windows-wsl-host-rescue.md +133 -0
  207. package/docs/public-usage-guide.md +1 -1
  208. package/docs/public-usage-guide.zh-CN.md +1 -1
  209. package/docs/tutorials/01-install-and-start.md +1 -1
  210. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  211. package/docs/tutorials/02-connect-chatgpt.md +4 -4
  212. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
  213. package/docs/wiki/Implementation.md +1 -1
  214. package/package.json +46 -26
  215. package/packages/kernel/cognition/api/index.ts +6 -0
  216. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  217. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  218. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  219. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  220. package/packages/kernel/cognition/domain/learning.ts +106 -0
  221. package/packages/kernel/cognition/domain/memory.ts +254 -0
  222. package/packages/kernel/controller/api/index.ts +3 -0
  223. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  224. package/packages/kernel/controller/application/controller-round-service.ts +41 -0
  225. package/packages/kernel/controller/application/controller-service.ts +30 -0
  226. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +529 -0
  227. package/packages/kernel/controller/domain/controller-round.ts +110 -0
  228. package/packages/kernel/controller/domain/execution-quality.ts +135 -0
  229. package/packages/kernel/controller/domain/types.ts +56 -0
  230. package/packages/kernel/controller/index.ts +1 -0
  231. package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
  232. package/packages/kernel/controller/infrastructure/controller-round-store.ts +1970 -0
  233. package/packages/kernel/controller/infrastructure/controller-session-store.ts +804 -0
  234. package/packages/kernel/controller/ports/controller-host.ts +18 -0
  235. package/packages/kernel/goal/api/index.ts +1 -0
  236. package/packages/kernel/goal/domain/types.ts +40 -0
  237. package/packages/kernel/goal/index.ts +1 -0
  238. package/packages/kernel/identity/api/index.ts +15 -0
  239. package/packages/kernel/identity/application/identity-service.ts +67 -0
  240. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  241. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  242. package/packages/kernel/identity/domain/grant.ts +72 -0
  243. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  244. package/packages/kernel/identity/domain/scope.ts +63 -0
  245. package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
  246. package/packages/kernel/identity/domain/types.ts +44 -0
  247. package/packages/kernel/identity/domain/user-request.ts +93 -0
  248. package/packages/kernel/identity/domain/workspace.ts +56 -0
  249. package/packages/kernel/identity/index.ts +1 -0
  250. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
  251. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  252. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  253. package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
  254. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  255. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  256. package/packages/kernel/memory/api/index.ts +5 -0
  257. package/packages/kernel/memory/application/experience-service.ts +118 -0
  258. package/packages/kernel/memory/application/outcome-service.ts +77 -0
  259. package/packages/kernel/memory/domain/experience.ts +103 -0
  260. package/packages/kernel/memory/domain/operational-prior.ts +92 -0
  261. package/packages/kernel/memory/index.ts +1 -0
  262. package/packages/kernel/scheduler/api/index.ts +4 -0
  263. package/packages/kernel/scheduler/application/eligibility.ts +252 -0
  264. package/packages/kernel/scheduler/application/schedule-service.ts +27 -0
  265. package/packages/kernel/scheduler/application/settlement.ts +182 -0
  266. package/packages/kernel/scheduler/domain/schedule.ts +236 -0
  267. package/packages/kernel/scheduler/index.ts +1 -0
  268. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +805 -0
  269. package/packages/kernel/work/api/index.ts +16 -0
  270. package/packages/kernel/work/application/work-service.ts +123 -0
  271. package/packages/kernel/work/domain/check-receipt.ts +43 -0
  272. package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
  273. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  274. package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
  275. package/packages/kernel/work/domain/engineering-design.ts +286 -0
  276. package/packages/kernel/work/domain/engineering-profile.ts +238 -0
  277. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  278. package/packages/kernel/work/domain/execution-concurrency.ts +247 -0
  279. package/packages/kernel/work/domain/execution-snapshot.ts +2 -0
  280. package/packages/kernel/work/domain/implementation-review.ts +193 -0
  281. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  282. package/packages/kernel/work/domain/project-engineering-contract.ts +224 -0
  283. package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
  284. package/{src/runtime/control-plane/facade/work-state-machine.ts → packages/kernel/work/domain/state-machine.ts} +37 -66
  285. package/packages/kernel/work/domain/types.ts +534 -0
  286. package/packages/kernel/work/index.ts +1 -0
  287. package/packages/kernel/work/infrastructure/work-contract-store.ts +1967 -0
  288. package/packages/kernel/work/ports/work-contract-store.ts +21 -0
  289. package/packages/plugin-runtime/browser/index.ts +3 -0
  290. package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
  291. package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
  292. package/packages/plugin-runtime/browser/session-authority.ts +62 -0
  293. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  294. package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
  295. package/packages/plugin-runtime/computer/index.ts +8 -0
  296. package/packages/plugin-runtime/computer/provider-error.ts +26 -0
  297. package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
  298. package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
  299. package/packages/plugin-runtime/computer/provider.ts +19 -0
  300. package/packages/plugin-runtime/computer/target-authority.ts +215 -0
  301. package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
  302. package/packages/plugin-runtime/external/index.ts +1 -0
  303. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +581 -0
  304. package/packages/plugin-runtime/index.ts +3 -0
  305. package/packages/protocols/browser/index.ts +1 -0
  306. package/packages/protocols/browser/session.ts +98 -0
  307. package/packages/protocols/computer/contract.ts +225 -0
  308. package/packages/protocols/computer/index.ts +1 -0
  309. package/packages/protocols/failure/contract.ts +158 -0
  310. package/packages/protocols/failure/index.ts +1 -0
  311. package/packages/protocols/handoff/index.ts +1 -0
  312. package/packages/protocols/handoff/status.ts +22 -0
  313. package/packages/protocols/mcp/execution-context.ts +11 -0
  314. package/packages/protocols/mcp/index.ts +2 -0
  315. package/packages/protocols/mcp/tool-contract.ts +42 -0
  316. package/packages/workflow-runtime/api/index.ts +2 -0
  317. package/packages/workflow-runtime/application/interpreter.ts +342 -0
  318. package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
  319. package/packages/workflow-runtime/index.ts +1 -0
  320. package/scripts/activate-source-baseline.ts +7 -9
  321. package/scripts/assemble-template.ts +1 -1
  322. package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
  323. package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
  324. package/scripts/canary-controller-semantic-wait.ts +183 -0
  325. package/scripts/canary-global-hook.sh +2 -2
  326. package/scripts/check-mcp-compatibility.ts +85 -7
  327. package/scripts/check-platform-support.mjs +28 -2
  328. package/scripts/check-public-docs.sh +1 -1
  329. package/scripts/check-repository-hygiene.mjs +92 -0
  330. package/scripts/check-runtime-architecture.mjs +1643 -127
  331. package/scripts/check-source-duplication.mjs +9 -1
  332. package/scripts/controller-ngrok-rotation.sh +5 -4
  333. package/scripts/create-project-dirs.sh +10 -74
  334. package/scripts/forge-local-recovery-helper.mjs +282 -0
  335. package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
  336. package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
  337. package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
  338. package/scripts/hook-shim.sh +2 -2
  339. package/scripts/init-project.sh +12 -70
  340. package/scripts/install-desktop-operator-registration.ts +4 -2
  341. package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
  342. package/scripts/install-standalone-recovery.ts +122 -10
  343. package/scripts/install-uu-remote-rescue-registration.ts +74 -0
  344. package/scripts/install-windows-host-recovery-registration.ts +22 -0
  345. package/scripts/lib/controller-home-env.sh +6 -3
  346. package/scripts/lib/project-init-lib.sh +42 -582
  347. package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
  348. package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
  349. package/scripts/open-source-audit-allowlist.txt +11 -1
  350. package/scripts/portability-bindings.mjs +70 -0
  351. package/scripts/run-bounded-command.mjs +60 -0
  352. package/scripts/run-bun-test-file.ts +22 -6
  353. package/scripts/run-governed-gate.ts +4 -2
  354. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  355. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  356. package/scripts/smoke-mcp-http-runtime.ts +6 -1
  357. package/scripts/smoke-runtime-recovery.ts +13 -11
  358. package/scripts/smoke-schedule-engine.ts +24 -16
  359. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  360. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  361. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
  362. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  363. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  364. package/scripts/smoke-workflow-supervisor.ts +103 -0
  365. package/scripts/test-governance.ts +51 -2
  366. package/scripts/verify-forge-runtime.sh +2 -1
  367. package/src/cli/agent-jobs/executor-health.ts +11 -34
  368. package/src/cli/agent-jobs/integration.ts +18 -49
  369. package/src/cli/agent-jobs/job-manager.ts +17 -4
  370. package/src/cli/chatgpt-browser/bind-server.ts +2 -1
  371. package/src/cli/chatgpt-browser/binding.ts +20 -10
  372. package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
  373. package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
  374. package/src/cli/chatgpt-browser/engine.ts +10 -11
  375. package/src/cli/chatgpt-browser/native-provider.ts +24 -83
  376. package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
  377. package/src/cli/chatgpt-browser/types.ts +1 -0
  378. package/src/cli/commands/bootstrap-control.ts +334 -0
  379. package/src/cli/commands/brain-assistant.ts +52 -0
  380. package/src/cli/commands/brain.ts +191 -40
  381. package/src/cli/commands/chatgpt.ts +32 -67
  382. package/src/cli/commands/computer.ts +437 -0
  383. package/src/cli/commands/doctor.ts +18 -1
  384. package/src/cli/commands/init-hook.ts +71 -61
  385. package/src/cli/commands/init.ts +3 -1
  386. package/src/cli/commands/mcp.ts +16 -10
  387. package/src/cli/commands/plugin.ts +38 -12
  388. package/src/cli/commands/recovery.ts +507 -80
  389. package/src/cli/commands/runtime.ts +12 -7
  390. package/src/cli/commands/setup-profile.ts +97 -39
  391. package/src/cli/commands/status.ts +2 -1
  392. package/src/cli/controller/check-runner.ts +500 -100
  393. package/src/cli/controller/completion-backlog.ts +3 -3
  394. package/src/cli/controller/completion-orchestrator.ts +4 -43
  395. package/src/cli/controller/completion-target.ts +3 -72
  396. package/src/cli/controller/context/source-materializer.ts +81 -100
  397. package/src/cli/controller/context/types.ts +7 -1
  398. package/src/cli/controller/context-pack.ts +28 -6
  399. package/src/cli/controller/execution-completion.ts +4 -7
  400. package/src/cli/controller/execution-policy.ts +36 -153
  401. package/src/cli/controller/governance.ts +2 -3
  402. package/src/cli/controller/issue-store.ts +4 -99
  403. package/src/cli/controller/launch-agents.ts +59 -18
  404. package/src/cli/controller/operational-plan.ts +24 -51
  405. package/src/cli/controller/runtime-config.ts +5 -1
  406. package/src/cli/controller/stuck-state-migration.ts +2 -3
  407. package/src/cli/controller/systemd-user.ts +148 -0
  408. package/src/cli/controller/types.ts +10 -70
  409. package/src/cli/controller/worklog.ts +67 -13
  410. package/src/cli/editing/edit-operation-contract.ts +92 -0
  411. package/src/cli/editing/edit-session.ts +290 -20
  412. package/src/cli/github/plugin.ts +17 -12
  413. package/src/cli/hook/route-registry.ts +3 -5
  414. package/src/cli/hook/runtime.ts +49 -3
  415. package/src/cli/index.ts +3 -0
  416. package/src/cli/local-bridge/connector-freshness.ts +2 -2
  417. package/src/cli/local-bridge/console-automations.ts +20 -15
  418. package/src/cli/local-bridge/console-view-models.ts +3 -12
  419. package/src/cli/local-bridge/facade-api.ts +105 -205
  420. package/src/cli/local-bridge/job-store.ts +1 -1
  421. package/src/cli/local-bridge/server.ts +48 -174
  422. package/src/cli/local-bridge/types.ts +0 -1
  423. package/src/cli/mcp/access-mode.ts +2 -132
  424. package/src/cli/mcp/access-tools.ts +2 -387
  425. package/src/cli/mcp/audit.ts +2 -32
  426. package/src/cli/mcp/auth.ts +2 -404
  427. package/src/cli/mcp/instructions.ts +2 -34
  428. package/src/cli/mcp/legacy-context.ts +2 -16
  429. package/src/cli/mcp/legacy-tool-service.ts +2 -5308
  430. package/src/cli/mcp/multi-repository.ts +2 -436
  431. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  432. package/src/cli/mcp/oauth.ts +2 -375
  433. package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
  434. package/src/cli/mcp/paths.ts +2 -140
  435. package/src/cli/mcp/policy.ts +2 -232
  436. package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
  437. package/src/cli/mcp/proxy-env.ts +2 -62
  438. package/src/cli/mcp/readiness-projection.ts +2 -24
  439. package/src/cli/mcp/redaction.ts +2 -21
  440. package/src/cli/mcp/repo.ts +2 -24
  441. package/src/cli/mcp/repository-tools.ts +2 -1613
  442. package/src/cli/mcp/server.ts +2 -951
  443. package/src/cli/mcp/setup.ts +2 -737
  444. package/src/cli/mcp/tool-capability-cache.ts +2 -87
  445. package/src/cli/mcp/tool-contract.ts +2 -14
  446. package/src/cli/mcp/tools.ts +2 -9
  447. package/src/cli/mcp/toolset-names.ts +2 -70
  448. package/src/cli/mcp/toolset-selection.ts +2 -41
  449. package/src/cli/mcp/toolset.ts +2 -336
  450. package/src/cli/mcp/transports/AGENTS.md +1 -10
  451. package/src/cli/mcp/transports/CLAUDE.md +1 -10
  452. package/src/cli/mcp/transports/http.ts +2 -1414
  453. package/src/cli/mcp/transports/session-registry.ts +2 -490
  454. package/src/cli/mcp/transports/stdio.ts +2 -8
  455. package/src/cli/mcp/types.ts +2 -41
  456. package/src/cli/mcp/umbrella-tools.ts +2 -117
  457. package/src/cli/repositories/command-executor.ts +91 -39
  458. package/src/cli/repositories/command-scope.ts +139 -4
  459. package/src/cli/repositories/controller-home.ts +167 -1
  460. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  461. package/src/cli/repositories/git-command-executor.ts +2 -2
  462. package/src/cli/repositories/identity.ts +13 -0
  463. package/src/cli/repositories/registry.ts +145 -60
  464. package/src/cli/repositories/repository-snapshot.ts +53 -7
  465. package/src/cli/repositories/runtime-storage.ts +27 -2
  466. package/src/cli/repositories/safe-patch.ts +34 -38
  467. package/src/cli/repositories/selected-path-actions.ts +22 -2
  468. package/src/cli/repositories/structured-git.ts +167 -27
  469. package/src/cli/repository/session-cache.ts +92 -6
  470. package/src/cli/runtime/helper-runner.ts +7 -0
  471. package/src/cli/runtime-invocation.ts +8 -1
  472. package/src/cli/tools/codegraph.ts +44 -25
  473. package/src/core/adoption/manifest-templates.ts +1 -1
  474. package/src/core/adoption/plan.ts +3 -26
  475. package/src/core/adoption/workflow-contract-plan.ts +17 -9
  476. package/src/effects/process-runner.ts +8 -14
  477. package/src/runtime/context/assistant-context.ts +167 -0
  478. package/src/runtime/context/assistant-work-context.ts +209 -0
  479. package/src/runtime/context/automatic-learning.ts +565 -0
  480. package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
  481. package/src/runtime/context/codegraph-read-provider.ts +55 -22
  482. package/src/runtime/context/codegraph-sidecar.cjs +8 -1
  483. package/src/runtime/context/cognitive-attention.ts +173 -0
  484. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  485. package/src/runtime/context/context-closure.ts +305 -0
  486. package/src/runtime/context/context-pack-process.ts +103 -0
  487. package/src/runtime/context/context-plane.ts +203 -0
  488. package/src/runtime/context/direct-controller-learning.ts +298 -0
  489. package/src/runtime/context/project-engineering-contract.ts +60 -0
  490. package/src/runtime/context/semantic-navigation-contract.ts +9 -0
  491. package/src/runtime/context/semantic-navigation.ts +6 -34
  492. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  493. package/src/runtime/context/typescript-navigation.ts +89 -3
  494. package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
  495. package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
  496. package/src/runtime/control-plane/bootstrap/index.ts +5 -0
  497. package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
  498. package/src/runtime/control-plane/bootstrap/store.ts +21 -0
  499. package/src/runtime/control-plane/bootstrap/types.ts +87 -0
  500. package/src/runtime/control-plane/cleanup-artifact-retention.ts +531 -0
  501. package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
  502. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +282 -0
  503. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +380 -0
  504. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +113 -16
  505. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  506. package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
  507. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  508. package/src/runtime/control-plane/execution/retained-work-resume.ts +439 -102
  509. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  510. package/src/runtime/control-plane/execution/validation.ts +34 -11
  511. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +87 -0
  512. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  513. package/src/runtime/control-plane/execution/work-completion-authority.ts +31 -44
  514. package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
  515. package/src/runtime/control-plane/execution/work-evidence-policy.ts +46 -53
  516. package/src/runtime/control-plane/execution/work-execution-support.ts +85 -164
  517. package/src/runtime/control-plane/execution/work-handle-authority.ts +349 -73
  518. package/src/runtime/control-plane/execution/work-handle-store.ts +55 -2
  519. package/src/runtime/control-plane/execution/work-head-settlement.ts +108 -0
  520. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +173 -28
  521. package/src/runtime/control-plane/execution/work-operation-service.ts +92 -40
  522. package/src/runtime/control-plane/execution/work-preparation-service.ts +145 -269
  523. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  524. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  525. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +543 -225
  526. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +59 -45
  527. package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
  528. package/src/runtime/control-plane/execution/work-verification-service.ts +662 -52
  529. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +205 -16
  530. package/src/runtime/control-plane/facade/capability-registry.ts +380 -44
  531. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  532. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  533. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  534. package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
  535. package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -22
  536. package/src/runtime/control-plane/facade/goal-workloop.ts +565 -1202
  537. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +145 -0
  538. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +266 -52
  539. package/src/runtime/control-plane/facade/index.ts +4 -3
  540. package/src/runtime/control-plane/facade/operation-digest.ts +34 -73
  541. package/src/runtime/control-plane/facade/plan-contract-store.ts +671 -245
  542. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  543. package/src/runtime/control-plane/facade/repository-work-admission.ts +31 -23
  544. package/src/runtime/control-plane/facade/requirement-authority.ts +121 -0
  545. package/src/runtime/control-plane/facade/requirement-board.ts +9 -4
  546. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +85 -0
  547. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  548. package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
  549. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -15
  550. package/src/runtime/control-plane/facade/types.ts +218 -620
  551. package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
  552. package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
  553. package/src/runtime/control-plane/generated-cache-retention.ts +336 -19
  554. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  555. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +13 -2
  556. package/src/runtime/control-plane/global-scheduler/maintenance.ts +274 -84
  557. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
  558. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
  559. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -2
  560. package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
  561. package/src/runtime/control-plane/global-scheduler/scheduler.ts +227 -109
  562. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  563. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
  564. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  565. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  566. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +160 -0
  567. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
  568. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +139 -670
  569. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  570. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +25 -6
  571. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -194
  572. package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
  573. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  574. package/src/runtime/control-plane/persistence/experience-store.ts +302 -0
  575. package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
  576. package/src/runtime/control-plane/persistence/requirement-store.ts +230 -80
  577. package/src/runtime/control-plane/persistence/sqlite-store.ts +654 -78
  578. package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
  579. package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
  580. package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
  581. package/src/runtime/control-plane/release-retention.ts +154 -26
  582. package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
  583. package/src/runtime/control-plane/runtime-cleanup.ts +844 -38
  584. package/src/runtime/control-plane/runtime-generation.ts +6 -0
  585. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  586. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +479 -0
  587. package/src/runtime/control-plane/workspace/workspace-store.ts +249 -0
  588. package/src/runtime/diagnostics/incident-repair.ts +573 -0
  589. package/src/runtime/diagnostics/mcp-timing.ts +239 -6
  590. package/src/runtime/diagnostics/performance.ts +6 -14
  591. package/src/runtime/diagnostics/read-only-tool.ts +1 -1
  592. package/src/runtime/evidence/artifact-store.ts +216 -35
  593. package/src/runtime/evidence/evidence-store.ts +112 -8
  594. package/src/runtime/evidence/operational-shadow.ts +38 -0
  595. package/src/runtime/evidence/output-handle-index.ts +225 -0
  596. package/src/runtime/evidence/process-check-receipt.ts +5 -43
  597. package/src/runtime/evidence/result-store.ts +158 -44
  598. package/src/runtime/evidence/sensitive-output.ts +4 -4
  599. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
  600. package/src/runtime/execution/jobs/store.ts +124 -6
  601. package/src/runtime/execution/managed-workspace.ts +112 -5
  602. package/src/runtime/execution/process-runtime/canary.ts +19 -5
  603. package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
  604. package/src/runtime/execution/process-runtime/check-facade.ts +24 -10
  605. package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
  606. package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
  607. package/src/runtime/execution/process-runtime/check-result.ts +177 -2
  608. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +58 -9
  609. package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
  610. package/src/runtime/execution/process-runtime/command-facade.ts +117 -114
  611. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  612. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  613. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  614. package/src/runtime/execution/process-runtime/index.ts +4 -0
  615. package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
  616. package/src/runtime/execution/process-runtime/lightweight-managed.ts +283 -73
  617. package/src/runtime/execution/process-runtime/persisted-check.ts +130 -23
  618. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  619. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  620. package/src/runtime/execution/process-runtime/resource-claims.ts +125 -64
  621. package/src/runtime/execution/process-runtime/runtime.ts +167 -17
  622. package/src/runtime/execution/process-runtime/store.ts +30 -2
  623. package/src/runtime/execution/process-runtime/types.ts +27 -0
  624. package/src/runtime/execution/workers/executor.ts +18 -19
  625. package/src/runtime/execution/workers/worker-entry.ts +1 -1
  626. package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
  627. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
  628. package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
  629. package/src/runtime/gateway/mcp/process-tools.ts +2 -289
  630. package/src/runtime/gateway/mcp/router.ts +2 -1125
  631. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
  632. package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
  633. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
  634. package/src/runtime/maintenance/cleanup.ts +38 -24
  635. package/src/runtime/platform/browser-product-discovery.ts +174 -0
  636. package/src/runtime/platform/computer-platform.ts +14 -0
  637. package/src/runtime/platform/executable-discovery.ts +54 -0
  638. package/src/runtime/platform/fetch-http-server.ts +90 -0
  639. package/src/runtime/platform/service-inventory.ts +130 -0
  640. package/src/runtime/platform/service-manager.ts +146 -0
  641. package/src/runtime/platform/windows-host-discovery.ts +118 -0
  642. package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
  643. package/src/runtime/plugins/action-application.ts +192 -0
  644. package/src/runtime/plugins/app-store-connect-adapter.ts +47 -35
  645. package/src/runtime/plugins/browser-adapter.ts +898 -135
  646. package/src/runtime/plugins/browser-automation-service.ts +33 -34
  647. package/src/runtime/plugins/browser-macos-bridge.ts +386 -46
  648. package/src/runtime/plugins/browser-manifest-surface.ts +75 -17
  649. package/src/runtime/plugins/browser-native-session-service.ts +64 -14
  650. package/src/runtime/plugins/browser-provider-registry.ts +2 -109
  651. package/src/runtime/plugins/browser-registration.ts +32 -0
  652. package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
  653. package/src/runtime/plugins/browser-runtime.ts +55 -10
  654. package/src/runtime/plugins/browser-session-authority.ts +101 -291
  655. package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
  656. package/src/runtime/plugins/browser-session-store.ts +243 -80
  657. package/src/runtime/plugins/browser-session-types.ts +2 -98
  658. package/src/runtime/plugins/capability-authorization-grants.ts +272 -59
  659. package/src/runtime/plugins/catalog-trust.ts +82 -0
  660. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
  661. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
  662. package/src/runtime/plugins/computer-registration.ts +1050 -0
  663. package/src/runtime/plugins/config-store.ts +119 -0
  664. package/src/runtime/plugins/desktop-operator-external-policy.ts +964 -0
  665. package/src/runtime/plugins/desktop-operator-registration.ts +123 -12
  666. package/src/runtime/plugins/errors.ts +8 -1
  667. package/src/runtime/plugins/external-adapter.ts +82 -323
  668. package/src/runtime/plugins/external-provider-policy.ts +48 -0
  669. package/src/runtime/plugins/external-registration.ts +30 -2
  670. package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
  671. package/src/runtime/plugins/external-unix-socket.ts +64 -193
  672. package/src/runtime/plugins/first-party-external-registration.ts +163 -0
  673. package/src/runtime/plugins/first-party-registry.ts +3 -1
  674. package/src/runtime/plugins/github-adapter.ts +1 -2
  675. package/src/runtime/plugins/gmail-adapter.ts +29 -8
  676. package/src/runtime/plugins/google-calendar-adapter.ts +8 -8
  677. package/src/runtime/plugins/google-shared.ts +45 -53
  678. package/src/runtime/plugins/google-tasks-adapter.ts +8 -8
  679. package/src/runtime/plugins/ios-adapter.ts +2 -3
  680. package/src/runtime/plugins/lightweight-action.ts +208 -2
  681. package/src/runtime/plugins/local-system-adapter.ts +192 -2
  682. package/src/runtime/plugins/macos-capability-broker.ts +15 -105
  683. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  684. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  685. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  686. package/src/runtime/plugins/resend-adapter.ts +15 -24
  687. package/src/runtime/plugins/store.ts +1003 -387
  688. package/src/runtime/plugins/types.ts +28 -1
  689. package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
  690. package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
  691. package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
  692. package/src/runtime/plugins/xiaohongshu-publish.ts +102 -430
  693. package/src/runtime/projections/controller-context.ts +12 -6
  694. package/src/runtime/projections/git-status-sampler.ts +73 -22
  695. package/src/runtime/projections/invalidation.ts +7 -3
  696. package/src/runtime/projections/materialized-view.ts +93 -43
  697. package/src/runtime/recovery/actions.ts +3 -3
  698. package/src/runtime/recovery/capability-action-application.ts +229 -0
  699. package/src/runtime/recovery/index.ts +3 -0
  700. package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
  701. package/src/runtime/recovery/local-jobs-repair.ts +14 -14
  702. package/src/runtime/recovery/maintenance-executor.ts +701 -168
  703. package/src/runtime/recovery/quarantine-retention.ts +256 -0
  704. package/src/runtime/recovery/store.ts +29 -11
  705. package/src/runtime/release/identity.ts +6 -0
  706. package/src/runtime/release/release-coordinator.ts +296 -0
  707. package/src/runtime/release/release-session.ts +716 -0
  708. package/src/runtime/release/release-state-migration.ts +25 -0
  709. package/src/runtime/resources/leases/store.ts +56 -25
  710. package/src/runtime/root/active-execution-power-assertion.ts +139 -0
  711. package/src/runtime/root/assistant-learning-loop.ts +45 -0
  712. package/src/runtime/root/browser-session-composition.ts +59 -0
  713. package/src/runtime/root/computer-composition.ts +376 -0
  714. package/src/runtime/root/computer-target-composition.ts +16 -0
  715. package/src/runtime/root/computer-target-persistence.ts +39 -0
  716. package/src/runtime/root/controller-progression-composition.ts +192 -0
  717. package/src/runtime/root/controller-round-composition.ts +126 -0
  718. package/src/runtime/root/controller-services.ts +2 -0
  719. package/src/runtime/root/deployment-topology.ts +106 -0
  720. package/src/runtime/root/entry.ts +8 -6
  721. package/src/runtime/root/gateway-adapter.ts +3 -3
  722. package/src/runtime/root/known-good-recovery.ts +184 -0
  723. package/src/runtime/root/local-bridge.ts +5 -3
  724. package/src/runtime/root/mcp-transport.ts +116 -23
  725. package/src/runtime/root/ownership.ts +261 -9
  726. package/src/runtime/root/package-connector-service.ts +212 -74
  727. package/src/runtime/root/package-runtime-release.ts +199 -33
  728. package/src/runtime/root/package-runtime-service.ts +77 -108
  729. package/src/runtime/root/release-execution-canary.ts +103 -0
  730. package/src/runtime/root/release-loader.ts +150 -0
  731. package/src/runtime/root/release-manifest.ts +185 -7
  732. package/src/runtime/root/release-materialize.ts +474 -31
  733. package/src/runtime/root/release-store.ts +425 -37
  734. package/src/runtime/root/runtime-lane.ts +191 -0
  735. package/src/runtime/root/runtime.ts +379 -32
  736. package/src/runtime/root/scheduled-controller-composition.ts +76 -0
  737. package/src/runtime/root/scheduler.ts +12 -4
  738. package/src/runtime/root/service-runner.ts +3 -3
  739. package/src/runtime/root/service.ts +73 -45
  740. package/src/runtime/root/status.ts +49 -0
  741. package/src/runtime/root/types.ts +55 -3
  742. package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
  743. package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
  744. package/src/runtime/root/write-fence.ts +50 -2
  745. package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
  746. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  747. package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
  748. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  749. package/src/runtime/shared/local-bridge-surface.ts +1 -1
  750. package/src/runtime/shared/process-environment.ts +36 -10
  751. package/src/runtime/shared/response-budget.ts +1 -3
  752. package/src/runtime/shared/storage-capacity.ts +68 -0
  753. package/src/runtime/standalone-recovery/controller-home-migration.ts +1108 -0
  754. package/src/runtime/standalone-recovery/core.ts +4609 -495
  755. package/src/runtime/standalone-recovery/entry.ts +642 -81
  756. package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
  757. package/src/runtime/standalone-recovery/installer.ts +399 -177
  758. package/src/runtime/standalone-recovery/mcp-server.ts +79 -0
  759. package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
  760. package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
  761. package/src/runtime/standalone-recovery/performance.ts +173 -0
  762. package/src/runtime/standalone-recovery/release.ts +58 -5
  763. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  764. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  765. package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
  766. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  767. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -4
  768. package/src/runtime/workflow/schedules/engine.ts +298 -484
  769. package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
  770. package/src/runtime/workflow/schedules/settlement.ts +2 -191
  771. package/src/runtime/workflow/schedules/store.ts +2 -607
  772. package/src/runtime/workflow/schedules/types.ts +2 -127
  773. package/src/runtime/workflow/schedules/work-continuation.ts +220 -16
  774. package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
  775. package/src/runtime/workflows/runtime.ts +482 -0
  776. package/src/testing/test-governance.ts +95 -30
  777. package/supervisor/chatgpt-conversation.ts +23 -0
  778. package/supervisor/client.ts +201 -0
  779. package/supervisor/control-plane.ts +868 -0
  780. package/supervisor/entry.ts +15 -0
  781. package/supervisor/forge-validators.ts +80 -0
  782. package/supervisor/index.ts +5 -0
  783. package/supervisor/native-browser-adapter.ts +786 -0
  784. package/supervisor/native-messaging/host.ts +39 -0
  785. package/supervisor/native-messaging/manifest.ts +26 -0
  786. package/supervisor/native-messaging/protocol.ts +27 -0
  787. package/supervisor/paths.ts +18 -0
  788. package/supervisor/protocol.ts +202 -0
  789. package/supervisor/server.ts +404 -0
  790. package/supervisor/store.ts +1451 -0
  791. package/supervisor/tsconfig.json +8 -0
  792. package/supervisor/types.ts +255 -0
  793. package/assets/templates/helpers/archive-architecture-request.sh +0 -244
  794. package/docs/architecture/history/README.md +0 -7
  795. package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
  796. package/docs/architecture/history/global-hook-runtime.md +0 -166
  797. package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
  798. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  799. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
  800. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
  801. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
  802. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
  803. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  804. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  805. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  806. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  807. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
  808. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  809. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  810. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  811. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  812. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  813. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  814. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  815. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  816. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  817. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
  818. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
  819. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
  820. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  821. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  822. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  823. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  824. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
  825. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
  826. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
  827. package/docs/architecture/requests/archive/2026/root.md +0 -68
  828. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
  829. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
  830. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
  831. package/scripts/archive-architecture-request.sh +0 -4
  832. package/scripts/benchmark-background-check-overlap.ts +0 -228
  833. package/scripts/benchmark-controller-hotpath.ts +0 -212
  834. package/scripts/benchmark-controller-round2.ts +0 -139
  835. package/scripts/benchmark-ios-agent-device.ts +0 -514
  836. package/scripts/benchmark-mcp-host.sh +0 -117
  837. package/scripts/benchmark-mcp-transport.ts +0 -170
  838. package/scripts/benchmark-quality-harness.ts +0 -146
  839. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  840. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  841. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  842. package/scripts/benchmark-thin-harness.ts +0 -251
  843. package/scripts/benchmark-typescript-navigation.ts +0 -186
  844. package/scripts/benchmark-work-efficiency.ts +0 -334
  845. package/scripts/build-controller-ui.ts +0 -28
  846. package/scripts/check-npm-release.sh +0 -32
  847. package/scripts/check-public-release-surface.sh +0 -19
  848. package/scripts/check-release-published.sh +0 -57
  849. package/scripts/check-release-readiness.sh +0 -37
  850. package/scripts/check-release-version.mjs +0 -74
  851. package/scripts/check-tarball-install-smoke.sh +0 -67
  852. package/scripts/package-source-archive.sh +0 -147
  853. package/scripts/public-release-files.txt +0 -39
  854. package/scripts/public-release.ts +0 -174
  855. package/scripts/publish-release-tarball.sh +0 -27
  856. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  857. package/scripts/run-skill-evals.ts +0 -1022
  858. package/scripts/stage-runtime-release.ts +0 -39
  859. package/src/cli/controller/progress.ts +0 -656
  860. package/src/cli/controller/work-mode.ts +0 -181
  861. package/src/cli/local-bridge/console-assets.ts +0 -17
  862. package/src/cli/local-bridge/dashboard.ts +0 -17
  863. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  864. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  865. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  866. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  867. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  868. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  869. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  870. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  871. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  872. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  873. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  874. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  875. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  876. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  877. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  878. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  879. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  880. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  881. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  882. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  883. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  884. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  885. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  886. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  887. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  888. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  889. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  890. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  891. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -385
  892. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -1913
  893. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  894. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -449
  895. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -1118
  896. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -93
  897. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  898. package/src/runtime/control-plane/routing/index.ts +0 -1
  899. package/src/runtime/control-plane/routing/route-policy.ts +0 -382
  900. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
@@ -1,20 +1,19 @@
1
1
  import { createHash, randomUUID } from 'crypto';
2
2
  import {
3
3
  chmodSync,
4
- closeSync,
5
4
  copyFileSync,
6
5
  existsSync,
7
6
  lstatSync,
8
7
  mkdirSync,
9
- openSync,
10
8
  readFileSync,
11
9
  readdirSync,
12
10
  renameSync,
13
11
  rmSync,
14
12
  writeFileSync,
15
13
  } from 'fs';
16
- import { dirname, join, resolve } from 'path';
14
+ import { dirname, join, relative, resolve } from 'path';
17
15
  import { runProcess, type ProcessRunResult } from '../../effects/process-runner';
16
+ import { resolveBunExecutable } from '../shared/process-environment';
18
17
  import {
19
18
  installLaunchAgent,
20
19
  launchAgentPath,
@@ -23,10 +22,19 @@ import {
23
22
  type SafeHandoffResult,
24
23
  } from '../../cli/controller/launch-agents';
25
24
  import { isProcessAlive } from '../shared/process-tree';
25
+ import {
26
+ installSystemdUserUnit,
27
+ systemdUserAvailable,
28
+ systemdUserServicePid,
29
+ systemdUserUnitName,
30
+ systemdUserUnitPath,
31
+ type SystemdUserUnitInput,
32
+ } from '../../cli/controller/systemd-user';
26
33
  import { FORGE_VERSION } from '../../version';
27
- import { initializeStandaloneRecovery, loadRecoveryConfig, type PrimaryConnectorServiceConfig, type PrimaryRuntimeServiceConfig, type PublicTunnelServiceConfig, type RecoveryConfig } from './core';
28
- import { RECOVERY_GATEWAY_LABEL, RECOVERY_WATCHDOG_LABEL } from './service-labels';
29
- export { RECOVERY_GATEWAY_LABEL, RECOVERY_WATCHDOG_LABEL } from './service-labels';
34
+ import { initializeStandaloneRecovery, loadRecoveryConfig, normalizeRecoveryInstallProfile, recoveryInstallProfileRoles, type LaunchdPrimaryConnectorServiceConfig, type LaunchdPublicTunnelServiceConfig, type PrimaryConnectorServiceConfig, type PrimaryRuntimeServiceConfig, type PublicTunnelServiceConfig, type RecoveryConfig, type RecoveryInstallProfile, type RecoveryTunnelServiceConfig } from './core';
35
+ import { RECOVERY_DAEMON_LABEL, RECOVERY_GATEWAY_LABEL, RECOVERY_WATCHDOG_LABEL } from './service-labels';
36
+ import { acquireRecoveryOperationLock } from './operation-lock';
37
+ export { RECOVERY_DAEMON_LABEL, RECOVERY_GATEWAY_LABEL, RECOVERY_WATCHDOG_LABEL } from './service-labels';
30
38
  import {
31
39
  RECOVERY_RELEASE_BINARIES,
32
40
  RECOVERY_RELEASE_ROLE_CANARY_ARG,
@@ -40,6 +48,7 @@ import {
40
48
  recoveryPreviousPath,
41
49
  recoveryReleasesRoot,
42
50
  recoveryRoot,
51
+ setRecoveryGatewayDraining,
43
52
  writeRecoveryReleaseManifest,
44
53
  type RecoveryReleaseDescriptor,
45
54
  type RecoveryReleaseManifest,
@@ -71,7 +80,7 @@ export function retireStaleRecoveryLaunchAgents(
71
80
  ): string[] {
72
81
  const generatedRoot = join(recoveryRoot(controllerHome), 'launchd');
73
82
  if (!existsSync(generatedRoot)) return [];
74
- const activeLabels = new Set([RECOVERY_GATEWAY_LABEL, RECOVERY_WATCHDOG_LABEL]);
83
+ const activeLabels = new Set([RECOVERY_DAEMON_LABEL]);
75
84
  const retired: string[] = [];
76
85
  for (const name of readdirSync(generatedRoot)) {
77
86
  if (!name.endsWith('.plist')) continue;
@@ -124,20 +133,28 @@ export interface RecoveryActivationVerification {
124
133
  ok: boolean;
125
134
  expectedReleaseRevision: string;
126
135
  failures: string[];
136
+ daemonPid?: number;
127
137
  gatewayPid?: number;
128
138
  watchdogPid?: number;
129
139
  healthStatus?: number;
130
140
  }
131
141
 
142
+
143
+ export interface RecoveryServiceHandoffResult {
144
+ platform: 'launchd' | 'systemd-user';
145
+ serviceRegistered: boolean;
146
+ pidReady: boolean;
147
+ portReady: boolean;
148
+ servicePath?: string;
149
+ pid?: number;
150
+ launchd?: SafeHandoffResult;
151
+ }
132
152
  export interface RecoveryActivationResult {
133
153
  release: RecoveryReleaseDescriptor;
134
154
  previous?: RecoveryReleaseDescriptor;
135
155
  migratedLegacy?: RecoveryReleaseDescriptor;
136
156
  noOp?: boolean;
137
- handoff?: {
138
- gateway: SafeHandoffResult;
139
- watchdog: SafeHandoffResult;
140
- };
157
+ handoff?: Partial<Record<RecoveryRuntimeRole, RecoveryServiceHandoffResult>>;
141
158
  verification: RecoveryActivationVerification;
142
159
  rollback?: {
143
160
  release: RecoveryReleaseDescriptor;
@@ -147,22 +164,28 @@ export interface RecoveryActivationResult {
147
164
 
148
165
  export interface RecoveryInstallResult {
149
166
  controllerHome: string;
167
+ profile: RecoveryInstallProfile;
150
168
  staged: StagedRecoveryRelease;
151
169
  activated?: RecoveryActivationResult;
152
170
  config: RecoveryConfig;
153
171
  }
154
172
 
155
173
  export interface RecoveryInstallerDependencies {
174
+ platform?: NodeJS.Platform;
175
+ systemdEnv?: NodeJS.ProcessEnv;
176
+ systemdAvailable?: typeof systemdUserAvailable;
177
+ installSystemdUnit?: typeof installSystemdUserUnit;
156
178
  now?: () => number;
157
179
  uuid?: () => string;
158
180
  compileBinary?: (input: { sourceRoot: string; outputPath: string }) => ProcessRunResult;
159
- runCanary?: (input: { binaryPath: string; controllerHome: string; role?: RecoveryRuntimeRole }) => ProcessRunResult;
181
+ runCanary?: (input: { binaryPath: string; controllerHome: string; productVersion: string; role?: RecoveryRuntimeRole }) => ProcessRunResult;
160
182
  installAgent?: typeof installLaunchAgent;
161
183
  handoff?: typeof safeLaunchdHandoff;
162
184
  verify?: (input: {
163
185
  controllerHome: string;
164
186
  config: RecoveryConfig;
165
187
  expectedRelease: RecoveryReleaseDescriptor;
188
+ profile?: RecoveryInstallProfile;
166
189
  timeoutMs?: number;
167
190
  }) => Promise<RecoveryActivationVerification>;
168
191
  currentPid?: (controllerHome: string, role: RecoveryRuntimeRole) => number | undefined;
@@ -175,6 +198,28 @@ function sha256(path: string): string {
175
198
  return createHash('sha256').update(readFileSync(path)).digest('hex');
176
199
  }
177
200
 
201
+ function recoverySourceProductVersion(sourceRoot: string): string {
202
+ const packagePath = join(resolve(sourceRoot), 'package.json');
203
+ try {
204
+ const parsed = JSON.parse(readFileSync(packagePath, 'utf8')) as { version?: unknown };
205
+ const version = typeof parsed.version === 'string' ? parsed.version.trim() : '';
206
+ if (version && version !== '0.0.0-unknown') return version;
207
+ } catch {
208
+ // Fall through to one explicit release-build error.
209
+ }
210
+ throw new Error(`RECOVERY_RELEASE_PRODUCT_VERSION_INVALID: ${packagePath}`);
211
+ }
212
+
213
+ function recoveryReleaseProductVersion(release: RecoveryReleaseDescriptor): string {
214
+ const bound = release.productVersion?.trim();
215
+ if (bound) return bound;
216
+ // Compatibility for Recovery releases created before productVersion became
217
+ // manifest-bound. Prefer their exact recorded source package when it still
218
+ // exists; only the legacy fallback may inherit the current process version.
219
+ try { return recoverySourceProductVersion(release.sourceRoot); } catch { /* legacy/retired source */ }
220
+ return FORGE_VERSION;
221
+ }
222
+
178
223
  function gitText(root: string, args: string[]): string {
179
224
  const result = runProcess('git', ['-C', root, ...args], { timeoutMs: 15_000, maxOutputBytes: 128 * 1024 });
180
225
  if (!result.ok) throw new Error(`RECOVERY_RELEASE_GIT_FAILED: ${result.stderr || result.stdout || result.error}`.slice(0, 2_000));
@@ -190,9 +235,20 @@ export function recoverySourceIdentity(sourceRoot: string): RecoverySourceIdenti
190
235
  return { sourceCommit, releaseRevision: sourceCommit, cleanWorkspace: true, sourceRoot: root };
191
236
  }
192
237
 
238
+ export function resolveRecoveryCompilerExecutable(
239
+ execPath: string = process.execPath,
240
+ env: NodeJS.ProcessEnv = process.env,
241
+ accountHome?: string,
242
+ ): string {
243
+ const configured = env.FORGE_BUN_BIN?.trim();
244
+ if (configured) return configured;
245
+ return accountHome === undefined
246
+ ? resolveBunExecutable(execPath, env)
247
+ : resolveBunExecutable(execPath, env, accountHome);
248
+ }
249
+
193
250
  function defaultCompileBinary(input: { sourceRoot: string; outputPath: string }): ProcessRunResult {
194
- const configured = process.env.FORGE_BUN_BIN?.trim();
195
- const bun = configured || (process.versions.bun && /(?:^|\/)bun(?:$|-)/.test(process.execPath) ? process.execPath : 'bun');
251
+ const bun = resolveRecoveryCompilerExecutable();
196
252
  return runProcess(bun, [
197
253
  'build',
198
254
  join(input.sourceRoot, 'src/runtime/standalone-recovery/entry.ts'),
@@ -202,14 +258,14 @@ function defaultCompileBinary(input: { sourceRoot: string; outputPath: string })
202
258
  ], { cwd: input.sourceRoot, timeoutMs: 180_000, maxOutputBytes: 512 * 1024 });
203
259
  }
204
260
 
205
- function defaultRunCanary(input: { binaryPath: string; controllerHome: string; role?: RecoveryRuntimeRole }): ProcessRunResult {
261
+ function defaultRunCanary(input: { binaryPath: string; controllerHome: string; productVersion: string; role?: RecoveryRuntimeRole }): ProcessRunResult {
206
262
  const args = input.role
207
263
  ? [input.role, RECOVERY_RELEASE_ROLE_CANARY_ARG, '--controller-home', input.controllerHome]
208
264
  : ['status', '--controller-home', input.controllerHome];
209
265
  return runProcess(input.binaryPath, args, {
210
266
  timeoutMs: 30_000,
211
267
  maxOutputBytes: 128 * 1024,
212
- env: { PATH: '/usr/bin:/bin:/usr/sbin:/sbin', FORGE_BUILD_VERSION: FORGE_VERSION },
268
+ env: { PATH: '/usr/bin:/bin:/usr/sbin:/sbin', FORGE_BUILD_VERSION: input.productVersion },
213
269
  replaceEnv: true,
214
270
  });
215
271
  }
@@ -225,6 +281,7 @@ export function stageRecoveryRelease(input: {
225
281
  sourceRoot: string;
226
282
  }, dependencies: RecoveryInstallerDependencies = {}): StagedRecoveryRelease {
227
283
  const identity = recoverySourceIdentity(input.sourceRoot);
284
+ const productVersion = recoverySourceProductVersion(identity.sourceRoot);
228
285
  const now = dependencies.now ?? Date.now;
229
286
  const uuid = dependencies.uuid ?? randomUUID;
230
287
  const releasesRoot = recoveryReleasesRoot(input.controllerHome);
@@ -247,19 +304,20 @@ export function stageRecoveryRelease(input: {
247
304
  schemaVersion: 1,
248
305
  ...identity,
249
306
  builtAt: new Date(now()).toISOString(),
307
+ productVersion,
250
308
  artifacts: completeArtifacts(staging),
251
309
  });
252
310
  const runCanary = dependencies.runCanary ?? defaultRunCanary;
253
311
  const canaries: Array<{ label: string; binaryPath: string; role?: RecoveryRuntimeRole }> = [
254
312
  { label: 'primary', binaryPath: primary },
255
- { label: 'gateway', binaryPath: join(staging, 'forge-recovery-gateway'), role: 'gateway' },
256
- { label: 'watchdog', binaryPath: join(staging, 'forge-recovery-watchdog'), role: 'watchdog' },
313
+ { label: 'daemon', binaryPath: primary, role: 'daemon' },
257
314
  ];
258
315
  const canaryDetails: string[] = [];
259
316
  for (const canary of canaries) {
260
317
  const canaryResult = runCanary({
261
318
  binaryPath: canary.binaryPath,
262
319
  controllerHome: resolve(input.controllerHome),
320
+ productVersion,
263
321
  role: canary.role,
264
322
  });
265
323
  if (!canaryResult.ok) {
@@ -331,16 +389,49 @@ export function captureLegacyRecoveryRelease(
331
389
  return release;
332
390
  }
333
391
 
392
+ export function recoverySystemdUserUnitInput(
393
+ controllerHome: string,
394
+ role: RecoveryRuntimeRole,
395
+ env: NodeJS.ProcessEnv = process.env,
396
+ productVersion: string = FORGE_VERSION,
397
+ ): SystemdUserUnitInput {
398
+ const binary = role === 'daemon' ? 'forge-recovery' : role === 'gateway' ? 'forge-recovery-gateway' : 'forge-recovery-watchdog';
399
+ return {
400
+ description: role === 'daemon' ? 'Forge Standalone Recovery' : role === 'gateway' ? 'Forge Standalone Recovery Gateway (legacy)' : 'Forge Standalone Recovery Watchdog (legacy)',
401
+ executable: join(recoveryCurrentPath(controllerHome), binary),
402
+ args: [role, '--controller-home', resolve(controllerHome)],
403
+ environment: {
404
+ PATH: env.PATH?.trim() || '/usr/bin:/bin:/usr/sbin:/sbin',
405
+ FORGE_BUILD_VERSION: productVersion,
406
+ // Recovery can be running from its own compiled binary. Never let that
407
+ // binary become the interpreter for the primary OAuth Connector after a
408
+ // Controller Home cutover; preserve the explicit package executable.
409
+ FORGE_CONNECTOR_EXECUTABLE: env.FORGE_CONNECTOR_EXECUTABLE?.trim() || process.execPath,
410
+ },
411
+ restart: 'always',
412
+ restartSec: 5,
413
+ };
414
+ }
415
+
416
+ function recoverySystemdLabel(role: RecoveryRuntimeRole): string {
417
+ return role === 'daemon' ? RECOVERY_DAEMON_LABEL : role === 'gateway' ? RECOVERY_GATEWAY_LABEL : RECOVERY_WATCHDOG_LABEL;
418
+ }
419
+
420
+ function recoveryRoleServesGateway(role: RecoveryRuntimeRole): boolean {
421
+ return role === 'daemon' || role === 'gateway';
422
+ }
423
+
334
424
  function recoveryPlist(input: {
335
425
  label: string;
336
426
  executable: string;
337
427
  command: RecoveryRuntimeRole;
338
428
  controllerHome: string;
339
429
  logPath: string;
430
+ productVersion: string;
340
431
  }): string {
341
432
  const xml = (value: string) => value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
342
433
  const argumentsList = [
343
- '/usr/bin/env', '-i', 'PATH=/usr/bin:/bin:/usr/sbin:/sbin', `FORGE_BUILD_VERSION=${FORGE_VERSION}`, `FORGE_CONNECTOR_EXECUTABLE=${process.execPath}`,
434
+ '/usr/bin/env', '-i', 'PATH=/usr/bin:/bin:/usr/sbin:/sbin', `FORGE_BUILD_VERSION=${input.productVersion}`, `FORGE_CONNECTOR_EXECUTABLE=${process.execPath}`,
344
435
  input.executable, input.command, '--controller-home', input.controllerHome,
345
436
  ];
346
437
  return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict><key>Label</key><string>${xml(input.label)}</string><key>ProgramArguments</key><array>${argumentsList.map((argument) => `<string>${xml(argument)}</string>`).join('')}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>ThrottleInterval</key><integer>5</integer><key>StandardOutPath</key><string>${xml(input.logPath)}</string><key>StandardErrorPath</key><string>${xml(input.logPath)}</string></dict></plist>\n`;
@@ -361,7 +452,7 @@ export function recoveryLaunchdServicePid(label: string): number | undefined {
361
452
  }
362
453
 
363
454
  export function recoveryLaunchdPid(role: RecoveryRuntimeRole): number | undefined {
364
- return recoveryLaunchdServicePid(role === 'gateway' ? RECOVERY_GATEWAY_LABEL : RECOVERY_WATCHDOG_LABEL);
455
+ return recoveryLaunchdServicePid(recoverySystemdLabel(role));
365
456
  }
366
457
 
367
458
  function defaultCurrentPid(controllerHome: string, role: RecoveryRuntimeRole): number | undefined {
@@ -374,8 +465,11 @@ export async function verifyRecoveryReleaseActivation(input: {
374
465
  controllerHome: string;
375
466
  config: RecoveryConfig;
376
467
  expectedRelease: RecoveryReleaseDescriptor;
468
+ profile?: RecoveryInstallProfile;
377
469
  timeoutMs?: number;
378
- }, dependencies: Pick<RecoveryInstallerDependencies, 'servicePid'> = {}): Promise<RecoveryActivationVerification> {
470
+ }, dependencies: Pick<RecoveryInstallerDependencies, 'servicePid' | 'platform' | 'systemdEnv'> = {}): Promise<RecoveryActivationVerification> {
471
+ const profile = normalizeRecoveryInstallProfile(input.profile ?? input.config.installProfile, 'self-healing');
472
+ const selectedRoles = new Set(recoveryInstallProfileRoles(profile));
379
473
  const deadline = Date.now() + (input.timeoutMs ?? 60_000);
380
474
  let last: RecoveryActivationVerification = {
381
475
  ok: false,
@@ -388,45 +482,57 @@ export async function verifyRecoveryReleaseActivation(input: {
388
482
  if (!current || current.releasePath !== input.expectedRelease.releasePath || current.manifestSha256 !== input.expectedRelease.manifestSha256) {
389
483
  failures.push('Recovery current authority does not match expected release');
390
484
  }
391
- const gateway = readRecoveryRuntimeIdentity(input.controllerHome, 'gateway');
392
- const watchdog = readRecoveryRuntimeIdentity(input.controllerHome, 'watchdog');
393
485
  const observedPids: Partial<Record<RecoveryRuntimeRole, number>> = {};
394
- const servicePid = dependencies.servicePid ?? ((_controllerHome: string, role: RecoveryRuntimeRole) => recoveryLaunchdPid(role));
395
- for (const [role, identity] of [['gateway', gateway], ['watchdog', watchdog]] as const) {
486
+ const platform = dependencies.platform ?? process.platform;
487
+ const servicePid = dependencies.servicePid ?? ((_controllerHome: string, role: RecoveryRuntimeRole) => (
488
+ platform === 'linux'
489
+ ? systemdUserServicePid(recoverySystemdLabel(role), dependencies.systemdEnv ?? process.env)
490
+ : recoveryLaunchdPid(role)
491
+ ));
492
+ for (const role of ['daemon', 'gateway', 'watchdog'] as const) {
396
493
  const managedPid = servicePid(input.controllerHome, role);
397
- if (!managedPid) failures.push(`${role} launchd PID is unavailable`);
494
+ if (!selectedRoles.has(role)) {
495
+ if (managedPid) failures.push(`${role} service remains active outside Recovery profile ${profile}`);
496
+ continue;
497
+ }
498
+ const identity = readRecoveryRuntimeIdentity(input.controllerHome, role);
499
+ if (!managedPid) failures.push(`${role} service-owner PID is unavailable`);
398
500
  else observedPids[role] = managedPid;
399
501
  if (input.expectedRelease.legacy) continue;
400
502
  if (!identity) failures.push(`${role} runtime identity missing`);
401
503
  else {
402
504
  if (!isProcessAlive(identity.pid)) failures.push(`${role} PID ${identity.pid} is not alive`);
403
- if (managedPid !== undefined && identity.pid !== managedPid) failures.push(`${role} runtime identity PID does not match launchd PID`);
505
+ if (managedPid !== undefined && identity.pid !== managedPid) failures.push(`${role} runtime identity PID does not match service-owner PID`);
404
506
  if (identity.releasePath !== input.expectedRelease.releasePath) failures.push(`${role} release path mismatch`);
405
507
  if (identity.releaseRevision !== input.expectedRelease.releaseRevision) failures.push(`${role} release revision mismatch`);
406
508
  if (identity.manifestSha256 !== input.expectedRelease.manifestSha256) failures.push(`${role} manifest hash mismatch`);
407
509
  }
408
510
  }
409
511
  let healthStatus: number | undefined;
410
- try {
411
- const response = await fetch(`http://${input.config.gateway?.host ?? '127.0.0.1'}:${input.config.gateway?.port ?? 8787}/health`, {
412
- signal: AbortSignal.timeout(3_000),
413
- });
414
- healthStatus = response.status;
415
- const health = await response.json() as { status?: unknown; releaseRevision?: unknown; manifestSha256?: unknown };
416
- if (response.status !== 200 || health.status !== 'ok') failures.push(`gateway health HTTP ${response.status}`);
417
- if (!input.expectedRelease.legacy || health.releaseRevision !== undefined) {
418
- if (health.releaseRevision !== input.expectedRelease.releaseRevision) failures.push('gateway health release revision mismatch');
419
- }
420
- if (!input.expectedRelease.legacy || health.manifestSha256 !== undefined) {
421
- if (health.manifestSha256 !== input.expectedRelease.manifestSha256) failures.push('gateway health manifest hash mismatch');
512
+ if ([...selectedRoles].some(recoveryRoleServesGateway)) {
513
+ try {
514
+ const response = await fetch(`http://${input.config.gateway?.host ?? '127.0.0.1'}:${input.config.gateway?.port ?? 8787}/health`, {
515
+ signal: AbortSignal.timeout(3_000),
516
+ });
517
+ healthStatus = response.status;
518
+ const health = await response.json() as { status?: unknown; version?: unknown; releaseRevision?: unknown; manifestSha256?: unknown };
519
+ if (response.status !== 200 || health.status !== 'ok') failures.push(`gateway health HTTP ${response.status}`);
520
+ if (input.expectedRelease.productVersion && health.version !== input.expectedRelease.productVersion) failures.push('gateway health product version mismatch');
521
+ if (!input.expectedRelease.legacy || health.releaseRevision !== undefined) {
522
+ if (health.releaseRevision !== input.expectedRelease.releaseRevision) failures.push('gateway health release revision mismatch');
523
+ }
524
+ if (!input.expectedRelease.legacy || health.manifestSha256 !== undefined) {
525
+ if (health.manifestSha256 !== input.expectedRelease.manifestSha256) failures.push('gateway health manifest hash mismatch');
526
+ }
527
+ } catch (error) {
528
+ failures.push(`gateway health unavailable: ${error instanceof Error ? error.message : String(error)}`);
422
529
  }
423
- } catch (error) {
424
- failures.push(`gateway health unavailable: ${error instanceof Error ? error.message : String(error)}`);
425
530
  }
426
531
  last = {
427
532
  ok: failures.length === 0,
428
533
  expectedReleaseRevision: input.expectedRelease.releaseRevision,
429
534
  failures,
535
+ daemonPid: observedPids.daemon,
430
536
  gatewayPid: observedPids.gateway,
431
537
  watchdogPid: observedPids.watchdog,
432
538
  healthStatus,
@@ -437,146 +543,215 @@ export async function verifyRecoveryReleaseActivation(input: {
437
543
  return last;
438
544
  }
439
545
 
440
- interface RecoveryReleaseLockRecord {
441
- schemaVersion?: 1;
442
- pid: number;
443
- instanceId: string;
444
- processStartTime?: string;
445
- acquiredAt: string;
446
- action?: string;
447
- }
546
+ export const RECOVERY_RELEASE_LOCK_WAIT_MS = 10 * 60_000;
547
+ export const RECOVERY_RELEASE_LOCK_POLL_MS = 2_000;
448
548
 
449
- function processStartTime(pid: number): string | undefined {
450
- const result = runProcess('/bin/ps', ['-o', 'lstart=', '-p', String(pid)], { timeoutMs: 2_000, maxOutputBytes: 4_096 });
451
- return result.ok ? result.stdout.trim() || undefined : undefined;
549
+ /**
550
+ * An explicit operator release install must not fail merely because the Recovery
551
+ * daemon is inside its own bounded autonomous mutation (release step, tunnel
552
+ * repair, watchdog escalation). Wait for the single writer to finish, then take
553
+ * the same mutation lock; the lock still guarantees exactly one writer, and the
554
+ * bounded wait still fails closed if the holder never releases.
555
+ */
556
+ export async function acquireRecoveryReleaseLock(
557
+ controllerHome: string,
558
+ dependencies: {
559
+ acquire?: typeof acquireRecoveryOperationLock;
560
+ sleep?: (ms: number) => Promise<void>;
561
+ now?: () => number;
562
+ waitMs?: number;
563
+ report?: (detail: string) => void;
564
+ } = {},
565
+ ): Promise<{ path: string; instanceId: string; close: () => void }> {
566
+ const acquire = dependencies.acquire ?? acquireRecoveryOperationLock;
567
+ const sleep = dependencies.sleep ?? ((ms: number) => new Promise<void>((resolveSleep) => setTimeout(resolveSleep, ms)));
568
+ const now = dependencies.now ?? Date.now;
569
+ const report = dependencies.report ?? ((detail: string) => process.stderr.write(detail));
570
+ const deadline = now() + (dependencies.waitMs ?? RECOVERY_RELEASE_LOCK_WAIT_MS);
571
+ let reported = false;
572
+ for (;;) {
573
+ const attempt = acquire({
574
+ controllerHome,
575
+ action: 'install_recovery_release',
576
+ instanceIdPrefix: 'recovery-release-',
577
+ });
578
+ if (attempt.acquired) {
579
+ return {
580
+ path: attempt.handle.path,
581
+ instanceId: attempt.handle.record.instanceId,
582
+ close: attempt.handle.close,
583
+ };
584
+ }
585
+ if (now() >= deadline) {
586
+ throw new Error(`RECOVERY_OPERATION_LOCK_BUSY: ${attempt.owner.action} pid=${attempt.owner.pid} acquiredAt=${attempt.owner.acquiredAt}`);
587
+ }
588
+ if (!reported) {
589
+ report(
590
+ `recovery release install waiting for in-flight Recovery mutation: action=${attempt.owner.action} pid=${attempt.owner.pid} acquiredAt=${attempt.owner.acquiredAt}\n`,
591
+ );
592
+ reported = true;
593
+ }
594
+ await sleep(RECOVERY_RELEASE_LOCK_POLL_MS);
595
+ }
452
596
  }
453
597
 
454
- function readRecoveryReleaseLock(path: string): RecoveryReleaseLockRecord | undefined {
455
- try {
456
- const parsed = JSON.parse(readFileSync(path, 'utf8')) as RecoveryReleaseLockRecord;
457
- return Number.isInteger(parsed.pid) && typeof parsed.instanceId === 'string' && typeof parsed.acquiredAt === 'string'
458
- ? parsed
459
- : undefined;
460
- } catch {
461
- return undefined;
462
- }
598
+ function recoveryRoleLabel(role: RecoveryRuntimeRole): string {
599
+ return recoverySystemdLabel(role);
463
600
  }
464
601
 
465
- function recoveryReleaseLockOwnerAlive(lock: RecoveryReleaseLockRecord): boolean {
466
- if (!isProcessAlive(lock.pid)) return false;
467
- if (!lock.processStartTime) return true;
468
- const observed = processStartTime(lock.pid);
469
- return observed === undefined || observed === lock.processStartTime;
470
- }
471
-
472
- function acquireRecoveryReleaseLock(controllerHome: string): { path: string; instanceId: string; close: () => void } {
473
- const path = join(recoveryRoot(controllerHome), 'locks', 'operation.lock');
474
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
475
- const instanceId = `recovery-release-${randomUUID()}`;
476
- const record: RecoveryReleaseLockRecord = {
477
- schemaVersion: 1,
478
- pid: process.pid,
479
- instanceId,
480
- processStartTime: processStartTime(process.pid),
481
- acquiredAt: new Date().toISOString(),
482
- action: 'install_recovery_release',
483
- };
484
- let fd: number | undefined;
485
- for (let attempt = 0; attempt < 2; attempt += 1) {
486
- try {
487
- fd = openSync(path, 'wx', 0o600);
488
- writeFileSync(fd, JSON.stringify(record));
489
- break;
490
- } catch (error) {
491
- if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
492
- const existing = readRecoveryReleaseLock(path);
493
- if (!existing) throw new Error('RECOVERY_OPERATION_LOCK_UNCERTAIN');
494
- if (recoveryReleaseLockOwnerAlive(existing)) throw new Error('RECOVERY_OPERATION_LOCK_BUSY');
495
- const latest = readRecoveryReleaseLock(path);
496
- if (!latest || latest.instanceId !== existing.instanceId || recoveryReleaseLockOwnerAlive(latest)) {
497
- if (attempt === 1) throw new Error('RECOVERY_OPERATION_LOCK_BUSY');
498
- continue;
499
- }
500
- try { writeFileSync(`${path}.stale-${Date.now()}-${existing.instanceId}`, readFileSync(path)); } catch { /* evidence best effort */ }
501
- rmSync(path, { force: true });
602
+ function retireRecoveryRoleService(input: {
603
+ controllerHome: string;
604
+ role: RecoveryRuntimeRole;
605
+ dependencies: RecoveryInstallerDependencies;
606
+ }): void {
607
+ const platform = input.dependencies.platform ?? process.platform;
608
+ const label = recoveryRoleLabel(input.role);
609
+ if (platform === 'linux' && !input.dependencies.handoff) {
610
+ const env = input.dependencies.systemdEnv ?? process.env;
611
+ const unit = systemdUserUnitName(label);
612
+ const unitPath = systemdUserUnitPath(unit, env);
613
+ const disabled = runProcess('systemctl', ['--user', 'disable', '--now', unit], {
614
+ env,
615
+ timeoutMs: 30_000,
616
+ maxOutputBytes: 64 * 1024,
617
+ });
618
+ const detail = `${disabled.stderr}\n${disabled.stdout}`;
619
+ if (!disabled.ok && !/not loaded|not found|does not exist|not enabled|no files found/i.test(detail)) {
620
+ throw new Error(`RECOVERY_SYSTEMD_RETIRE_FAILED: ${unit}: ${detail.trim()}`);
502
621
  }
622
+ rmSync(unitPath, { force: true });
623
+ const reload = runProcess('systemctl', ['--user', 'daemon-reload'], {
624
+ env,
625
+ timeoutMs: 30_000,
626
+ maxOutputBytes: 64 * 1024,
627
+ });
628
+ if (!reload.ok) throw new Error(`RECOVERY_SYSTEMD_DAEMON_RELOAD_FAILED: ${reload.stderr || reload.stdout}`);
629
+ return;
503
630
  }
504
- if (fd === undefined) throw new Error('RECOVERY_OPERATION_LOCK_BUSY');
505
- return {
506
- path,
507
- instanceId,
508
- close: () => {
509
- closeSync(fd);
510
- const current = readRecoveryReleaseLock(path);
511
- if (current?.instanceId === instanceId) rmSync(path, { force: true });
512
- },
513
- };
631
+ if (platform !== 'darwin' && !input.dependencies.handoff) {
632
+ throw new Error(`RECOVERY_RELEASE_ACTIVATION_UNSUPPORTED_PLATFORM: ${platform}`);
633
+ }
634
+ const uid = (input.dependencies.uid ?? (() => typeof process.getuid === 'function' ? process.getuid() : Number.NaN))();
635
+ if (!Number.isInteger(uid)) throw new Error('RECOVERY_LAUNCHD_UID_UNAVAILABLE');
636
+ const runner = input.dependencies.launchctlRunner ?? defaultRecoveryLaunchctl;
637
+ const target = `gui/${uid}/${label}`;
638
+ const observed = runner(['print', target]);
639
+ if (observed.ok) {
640
+ const bootout = runner(['bootout', target]);
641
+ if (!bootout.ok && !bootoutAlreadyGone(`${bootout.stderr}\n${bootout.stdout}`)) {
642
+ throw new Error(`RECOVERY_SERVICE_RETIRE_FAILED: ${label}`);
643
+ }
644
+ }
645
+ rmSync(join(recoveryRoot(input.controllerHome), 'launchd', `${label}.plist`), { force: true });
646
+ rmSync(launchAgentPath(label), { force: true });
514
647
  }
515
648
 
516
649
  async function handoffRecoveryServices(input: {
517
650
  controllerHome: string;
518
651
  config: RecoveryConfig;
519
652
  expectedRelease: RecoveryReleaseDescriptor;
653
+ profile: RecoveryInstallProfile;
520
654
  dependencies: RecoveryInstallerDependencies;
521
- }): Promise<{ gateway: SafeHandoffResult; watchdog: SafeHandoffResult; verification: RecoveryActivationVerification }> {
522
- if (process.platform !== 'darwin' && !input.dependencies.handoff) throw new Error('RECOVERY_RELEASE_ACTIVATION_REQUIRES_MACOS');
523
- const uid = (input.dependencies.uid ?? (() => typeof process.getuid === 'function' ? process.getuid() : Number.NaN))();
524
- if (!Number.isInteger(uid)) throw new Error('RECOVERY_LAUNCHD_UID_UNAVAILABLE');
525
- const domain = `gui/${uid}`;
526
- retireStaleRecoveryLaunchAgents(input.controllerHome, uid, input.dependencies.launchctlRunner);
527
- const installAgent = input.dependencies.installAgent ?? installLaunchAgent;
528
- const generated = (() => {
529
- const root = recoveryRoot(input.controllerHome);
530
- const generatedRoot = join(root, 'launchd');
531
- const auditRoot = join(root, 'audit');
655
+ }): Promise<{ services: Partial<Record<RecoveryRuntimeRole, RecoveryServiceHandoffResult>>; verification: RecoveryActivationVerification }> {
656
+ const platform = input.dependencies.platform ?? process.platform;
657
+ const selectedRoles = new Set(recoveryInstallProfileRoles(input.profile));
658
+ for (const role of ['gateway', 'watchdog'] as const) {
659
+ if (!selectedRoles.has(role)) retireRecoveryRoleService({ controllerHome: input.controllerHome, role, dependencies: input.dependencies });
660
+ }
661
+
662
+ const services: Partial<Record<RecoveryRuntimeRole, RecoveryServiceHandoffResult>> = {};
663
+ if (platform === 'linux' && !input.dependencies.handoff) {
664
+ const env = input.dependencies.systemdEnv ?? process.env;
665
+ const available = input.dependencies.systemdAvailable ?? systemdUserAvailable;
666
+ if (selectedRoles.size > 0 && !available(env)) throw new Error('RECOVERY_SYSTEMD_USER_UNAVAILABLE');
667
+ const installUnit = input.dependencies.installSystemdUnit ?? installSystemdUserUnit;
668
+ for (const role of recoveryInstallProfileRoles(input.profile)) {
669
+ const servicePath = installUnit({
670
+ unitName: recoveryRoleLabel(role),
671
+ unit: recoverySystemdUserUnitInput(input.controllerHome, role, env, recoveryReleaseProductVersion(input.expectedRelease)),
672
+ env,
673
+ errorPrefix: 'RECOVERY_SYSTEMD_INSTALL_FAILED',
674
+ });
675
+ services[role] = {
676
+ platform: 'systemd-user',
677
+ serviceRegistered: true,
678
+ pidReady: false,
679
+ portReady: !recoveryRoleServesGateway(role),
680
+ servicePath,
681
+ };
682
+ }
683
+ } else {
684
+ if (platform !== 'darwin' && !input.dependencies.handoff) throw new Error(`RECOVERY_RELEASE_ACTIVATION_UNSUPPORTED_PLATFORM: ${platform}`);
685
+ const uid = (input.dependencies.uid ?? (() => typeof process.getuid === 'function' ? process.getuid() : Number.NaN))();
686
+ if (!Number.isInteger(uid)) throw new Error('RECOVERY_LAUNCHD_UID_UNAVAILABLE');
687
+ const domain = `gui/${uid}`;
688
+ retireStaleRecoveryLaunchAgents(input.controllerHome, uid, input.dependencies.launchctlRunner);
689
+ const installAgent = input.dependencies.installAgent ?? installLaunchAgent;
690
+ const currentPid = input.dependencies.currentPid ?? defaultCurrentPid;
691
+ const handoff = input.dependencies.handoff ?? safeLaunchdHandoff;
692
+ const generatedRoot = join(recoveryRoot(input.controllerHome), 'launchd');
693
+ const auditRoot = join(recoveryRoot(input.controllerHome), 'audit');
532
694
  mkdirSync(generatedRoot, { recursive: true, mode: 0o700 });
533
695
  mkdirSync(auditRoot, { recursive: true, mode: 0o700 });
534
- const gatewayPath = join(generatedRoot, `${RECOVERY_GATEWAY_LABEL}.plist`);
535
- const watchdogPath = join(generatedRoot, `${RECOVERY_WATCHDOG_LABEL}.plist`);
536
- writeFileSync(gatewayPath, recoveryPlist({ label: RECOVERY_GATEWAY_LABEL, executable: join(recoveryCurrentPath(input.controllerHome), 'forge-recovery-gateway'), command: 'gateway', controllerHome: resolve(input.controllerHome), logPath: join(auditRoot, 'gateway.log') }), { mode: 0o600 });
537
- writeFileSync(watchdogPath, recoveryPlist({ label: RECOVERY_WATCHDOG_LABEL, executable: join(recoveryCurrentPath(input.controllerHome), 'forge-recovery-watchdog'), command: 'watchdog', controllerHome: resolve(input.controllerHome), logPath: join(auditRoot, 'watchdog.log') }), { mode: 0o600 });
538
- return {
539
- gateway: installAgent(gatewayPath, RECOVERY_GATEWAY_LABEL).path,
540
- watchdog: installAgent(watchdogPath, RECOVERY_WATCHDOG_LABEL).path,
541
- };
542
- })();
543
- const currentPid = input.dependencies.currentPid ?? defaultCurrentPid;
544
- const handoff = input.dependencies.handoff ?? safeLaunchdHandoff;
545
- const gateway = await handoff({
546
- label: RECOVERY_GATEWAY_LABEL,
547
- plistPath: generated.gateway,
548
- domain,
549
- oldPid: currentPid(input.controllerHome, 'gateway'),
550
- port: input.config.gateway?.port,
551
- maxBootoutWaitMs: 20_000,
552
- maxBootstrapRetry: 3,
553
- });
554
- if (!gateway.serviceRegistered || !gateway.pidWaitClean || !gateway.portWaitClean) {
555
- throw new Error('RECOVERY_GATEWAY_HANDOFF_FAILED');
696
+ const productVersion = recoveryReleaseProductVersion(input.expectedRelease);
697
+ for (const role of recoveryInstallProfileRoles(input.profile)) {
698
+ const label = recoveryRoleLabel(role);
699
+ const generatedPath = join(generatedRoot, `${label}.plist`);
700
+ writeFileSync(generatedPath, recoveryPlist({
701
+ label,
702
+ executable: join(recoveryCurrentPath(input.controllerHome), role === 'daemon' ? 'forge-recovery' : role === 'gateway' ? 'forge-recovery-gateway' : 'forge-recovery-watchdog'),
703
+ command: role,
704
+ controllerHome: resolve(input.controllerHome),
705
+ logPath: join(auditRoot, `${role}.log`),
706
+ productVersion,
707
+ }), { mode: 0o600 });
708
+ const installedPath = installAgent(generatedPath, label).path;
709
+ const launched = await handoff({
710
+ label,
711
+ plistPath: installedPath,
712
+ domain,
713
+ oldPid: currentPid(input.controllerHome, role),
714
+ ...(recoveryRoleServesGateway(role) ? { port: input.config.gateway?.port } : {}),
715
+ maxBootoutWaitMs: 20_000,
716
+ maxBootstrapRetry: 3,
717
+ });
718
+ if (!launched.serviceRegistered || !launched.pidWaitClean || (recoveryRoleServesGateway(role) && !launched.portWaitClean)) {
719
+ throw new Error(role === 'daemon' ? 'RECOVERY_DAEMON_HANDOFF_FAILED' : role === 'gateway' ? 'RECOVERY_GATEWAY_HANDOFF_FAILED' : 'RECOVERY_WATCHDOG_HANDOFF_FAILED');
720
+ }
721
+ services[role] = {
722
+ platform: 'launchd',
723
+ serviceRegistered: launched.serviceRegistered,
724
+ pidReady: launched.pidWaitClean,
725
+ portReady: recoveryRoleServesGateway(role) ? launched.portWaitClean : true,
726
+ servicePath: installedPath,
727
+ launchd: launched,
728
+ };
729
+ }
556
730
  }
557
- const watchdog = await handoff({
558
- label: RECOVERY_WATCHDOG_LABEL,
559
- plistPath: generated.watchdog,
560
- domain,
561
- oldPid: currentPid(input.controllerHome, 'watchdog'),
562
- maxBootoutWaitMs: 20_000,
563
- maxBootstrapRetry: 3,
564
- });
565
- if (!watchdog.serviceRegistered || !watchdog.pidWaitClean) throw new Error('RECOVERY_WATCHDOG_HANDOFF_FAILED');
731
+
566
732
  const verification = await (input.dependencies.verify
567
733
  ?? ((verifyInput) => verifyRecoveryReleaseActivation(verifyInput, input.dependencies)))({
568
734
  controllerHome: input.controllerHome,
569
735
  config: input.config,
570
736
  expectedRelease: input.expectedRelease,
737
+ profile: input.profile,
571
738
  });
572
739
  if (!verification.ok) throw new Error(`RECOVERY_RELEASE_VERIFICATION_FAILED: ${verification.failures.join('; ')}`);
573
- return { gateway, watchdog, verification };
740
+ for (const role of recoveryInstallProfileRoles(input.profile)) {
741
+ const service = services[role];
742
+ if (!service) continue;
743
+ service.pid = role === 'daemon' ? verification.daemonPid : role === 'gateway' ? verification.gatewayPid : verification.watchdogPid;
744
+ service.pidReady = Boolean(service.pid);
745
+ if (recoveryRoleServesGateway(role)) service.portReady = verification.healthStatus === 200;
746
+ }
747
+ return { services, verification };
574
748
  }
575
749
 
576
750
  function sameRecoveryReleasePayload(left: RecoveryReleaseDescriptor, right: RecoveryReleaseDescriptor): boolean {
577
751
  return left.releaseRevision === right.releaseRevision
578
752
  && left.sourceCommit === right.sourceCommit
579
753
  && left.cleanWorkspace === right.cleanWorkspace
754
+ && left.productVersion === right.productVersion
580
755
  && RECOVERY_RELEASE_BINARIES.every((binary) => left.artifacts[binary].sha256 === right.artifacts[binary].sha256);
581
756
  }
582
757
 
@@ -587,7 +762,8 @@ export async function activateRecoveryRelease(input: {
587
762
  }, dependencies: RecoveryInstallerDependencies = {}): Promise<RecoveryActivationResult> {
588
763
  const controllerHome = resolve(input.controllerHome);
589
764
  const config = input.config ?? loadRecoveryConfig(controllerHome);
590
- const lock = acquireRecoveryReleaseLock(controllerHome);
765
+ const profile = normalizeRecoveryInstallProfile(config.installProfile, 'self-healing');
766
+ const lock = await acquireRecoveryReleaseLock(controllerHome);
591
767
  const current = readCurrentRecoveryRelease(controllerHome);
592
768
  try {
593
769
  if (current && sameRecoveryReleasePayload(current, input.candidate)) {
@@ -596,6 +772,7 @@ export async function activateRecoveryRelease(input: {
596
772
  controllerHome,
597
773
  config,
598
774
  expectedRelease: current,
775
+ profile,
599
776
  });
600
777
  if (verification.ok) {
601
778
  return {
@@ -608,28 +785,37 @@ export async function activateRecoveryRelease(input: {
608
785
  }
609
786
  const previous = current ?? captureLegacyRecoveryRelease(controllerHome, dependencies);
610
787
  const migratedLegacy = previous?.legacy ? previous : undefined;
611
- publishRecoveryRelease(controllerHome, input.candidate.releasePath, previous?.releasePath);
612
- publishRecoveryCompatibilityLinks(controllerHome);
788
+ const drainArmed = Boolean(current);
613
789
  try {
614
- const activated = await handoffRecoveryServices({ controllerHome, config, expectedRelease: input.candidate, dependencies });
615
- return { release: input.candidate, previous, migratedLegacy, handoff: { gateway: activated.gateway, watchdog: activated.watchdog }, verification: activated.verification };
616
- } catch (activationError) {
617
- if (!previous) throw activationError;
618
- publishRecoveryRelease(controllerHome, previous.releasePath, input.candidate.releasePath);
790
+ // The shared operation lock already excludes every Recovery mutation.
791
+ // Stop new transport admission during handoff; retained read streams are
792
+ // connections, not effects, and cannot be a second quiescence authority.
793
+ if (drainArmed) setRecoveryGatewayDraining(controllerHome, true);
794
+ publishRecoveryRelease(controllerHome, input.candidate.releasePath, previous?.releasePath);
619
795
  publishRecoveryCompatibilityLinks(controllerHome);
620
- let rollbackResult: Awaited<ReturnType<typeof handoffRecoveryServices>>;
621
796
  try {
622
- rollbackResult = await handoffRecoveryServices({ controllerHome, config, expectedRelease: previous, dependencies });
623
- } catch (rollbackError) {
797
+ const activated = await handoffRecoveryServices({ controllerHome, config, expectedRelease: input.candidate, profile, dependencies });
798
+ return { release: input.candidate, previous, migratedLegacy, handoff: activated.services, verification: activated.verification };
799
+ } catch (activationError) {
800
+ if (!previous) throw activationError;
801
+ publishRecoveryRelease(controllerHome, previous.releasePath, input.candidate.releasePath);
802
+ publishRecoveryCompatibilityLinks(controllerHome);
803
+ let rollbackResult: Awaited<ReturnType<typeof handoffRecoveryServices>>;
804
+ try {
805
+ rollbackResult = await handoffRecoveryServices({ controllerHome, config, expectedRelease: previous, profile, dependencies });
806
+ } catch (rollbackError) {
807
+ throw new Error(
808
+ `RECOVERY_RELEASE_ACTIVATION_FAILED: ${activationError instanceof Error ? activationError.message : String(activationError)}; `
809
+ + `rollback to ${previous.releaseRevision} failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,
810
+ );
811
+ }
624
812
  throw new Error(
625
- `RECOVERY_RELEASE_ACTIVATION_FAILED: ${activationError instanceof Error ? activationError.message : String(activationError)}; `
626
- + `rollback to ${previous.releaseRevision} failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,
813
+ `RECOVERY_RELEASE_ACTIVATION_FAILED_ROLLED_BACK: ${activationError instanceof Error ? activationError.message : String(activationError)}; `
814
+ + `restored=${previous.releaseRevision}; recovery=${rollbackResult.verification.daemonPid ?? rollbackResult.verification.gatewayPid ?? 'unknown'}`,
627
815
  );
628
816
  }
629
- throw new Error(
630
- `RECOVERY_RELEASE_ACTIVATION_FAILED_ROLLED_BACK: ${activationError instanceof Error ? activationError.message : String(activationError)}; `
631
- + `restored=${previous.releaseRevision}; gateway=${rollbackResult.verification.gatewayPid ?? 'unknown'}; watchdog=${rollbackResult.verification.watchdogPid ?? 'unknown'}`,
632
- );
817
+ } finally {
818
+ if (drainArmed) setRecoveryGatewayDraining(controllerHome, false);
633
819
  }
634
820
  } finally {
635
821
  lock.close();
@@ -647,7 +833,7 @@ export interface RecoveryTunnelLaunchdContract {
647
833
  }
648
834
 
649
835
  function inspectLaunchdRestartContract(
650
- service: Pick<PublicTunnelServiceConfig | PrimaryConnectorServiceConfig, 'platform' | 'label' | 'plistPath'>,
836
+ service: { platform: 'launchd'; label: string; plistPath?: string },
651
837
  allowConditionalKeepAlive: boolean,
652
838
  ): RecoveryTunnelLaunchdContract {
653
839
  const plistPath = service.plistPath ?? launchAgentPath(service.label);
@@ -670,38 +856,68 @@ function inspectLaunchdRestartContract(
670
856
  }
671
857
 
672
858
  export function inspectRecoveryTunnelLaunchdContract(
673
- service: Pick<PublicTunnelServiceConfig | PrimaryConnectorServiceConfig, 'platform' | 'label' | 'plistPath'>,
859
+ service: LaunchdPublicTunnelServiceConfig,
674
860
  ): RecoveryTunnelLaunchdContract {
675
861
  return inspectLaunchdRestartContract(service, false);
676
862
  }
677
863
 
678
864
  export function inspectPrimaryConnectorLaunchdContract(
679
- service: Pick<PrimaryConnectorServiceConfig, 'platform' | 'label' | 'plistPath'>,
865
+ service: LaunchdPrimaryConnectorServiceConfig,
680
866
  ): RecoveryTunnelLaunchdContract {
681
867
  return inspectLaunchdRestartContract(service, true);
682
868
  }
683
869
 
684
870
  export function inspectPrimaryPublicTunnelLaunchdContract(
685
- service: Pick<PublicTunnelServiceConfig, 'platform' | 'label' | 'plistPath'>,
871
+ service: LaunchdPublicTunnelServiceConfig,
686
872
  ): RecoveryTunnelLaunchdContract {
687
873
  return inspectLaunchdRestartContract(service, true);
688
874
  }
689
875
 
876
+ function pathInside(parent: string, candidate: string): boolean {
877
+ const rel = relative(resolve(parent), resolve(candidate));
878
+ return rel === '' || (!rel.startsWith('..') && !rel.startsWith('/') && !rel.startsWith('\\'));
879
+ }
880
+
881
+ function assertDurablePrimaryRuntimeSourceRoot(controllerHome: string, sourceRoot: string, env: NodeJS.ProcessEnv = process.env): void {
882
+ const root = resolve(sourceRoot);
883
+ const normalizedSegments = root.replace(/\\/g, '/').split('/').filter(Boolean);
884
+ const explicitWorktreeHome = env.FORGE_WORKTREE_HOME?.trim();
885
+ const underExplicitWorktreeHome = explicitWorktreeHome ? pathInside(explicitWorktreeHome, root) : false;
886
+ if (normalizedSegments.includes('managed-worktrees') || underExplicitWorktreeHome) {
887
+ throw new Error(`RECOVERY_PRIMARY_RUNTIME_SOURCE_ROOT_DURABLE_REQUIRED: ${root}`);
888
+ }
889
+ if (pathInside(resolve(controllerHome, 'managed-worktrees'), root)) {
890
+ throw new Error(`RECOVERY_PRIMARY_RUNTIME_SOURCE_ROOT_DURABLE_REQUIRED: ${root}`);
891
+ }
892
+ }
893
+
690
894
  export async function installStandaloneRecovery(input: {
691
895
  controllerHome: string;
692
896
  repoRoot: string;
897
+ primaryRuntimeSourceRepositoryId?: string;
693
898
  sourceRoot?: string;
694
899
  port?: number;
695
900
  publicMcpUrl?: string;
696
901
  recoveryPublicUrl?: string;
697
- recoveryTunnelService?: PublicTunnelServiceConfig;
902
+ recoveryTunnelService?: RecoveryTunnelServiceConfig;
698
903
  primaryPublicTunnelService?: PublicTunnelServiceConfig;
699
904
  primaryRuntimeService?: PrimaryRuntimeServiceConfig;
700
905
  primaryConnectorService?: PrimaryConnectorServiceConfig;
906
+ profile?: RecoveryInstallProfile;
907
+ /** @deprecated Compatibility alias for profile=manual without config/service activation. */
701
908
  stageOnly?: boolean;
702
909
  }, dependencies: RecoveryInstallerDependencies = {}): Promise<RecoveryInstallResult> {
703
910
  const controllerHome = resolve(input.controllerHome);
911
+ const profile = input.stageOnly
912
+ ? 'manual'
913
+ : normalizeRecoveryInstallProfile(input.profile, 'self-healing');
704
914
  const sourceRoot = resolve(input.sourceRoot ?? input.repoRoot);
915
+ const primaryRuntimeSourceRoot = resolve(input.repoRoot);
916
+ const primaryRuntimeSourceRepositoryId = input.primaryRuntimeSourceRepositoryId?.trim();
917
+ if (!input.stageOnly) {
918
+ assertDurablePrimaryRuntimeSourceRoot(controllerHome, primaryRuntimeSourceRoot);
919
+ if (!primaryRuntimeSourceRepositoryId) throw new Error('RECOVERY_PRIMARY_RUNTIME_SOURCE_REPOSITORY_ID_REQUIRED');
920
+ }
705
921
  if (input.recoveryTunnelService?.platform === 'launchd') {
706
922
  const tunnelContract = inspectRecoveryTunnelLaunchdContract(input.recoveryTunnelService);
707
923
  if (!tunnelContract.plistInstalled) {
@@ -729,19 +945,23 @@ export async function installStandaloneRecovery(input: {
729
945
  throw new Error('RECOVERY_PRIMARY_CONNECTOR_LAUNCHD_RESTART_CONTRACT_REQUIRED: RunAtLoad=true and KeepAlive=true or KeepAlive.SuccessfulExit=false');
730
946
  }
731
947
  }
948
+ const staged = stageRecoveryRelease({ controllerHome, sourceRoot }, dependencies);
949
+ if (input.stageOnly) {
950
+ return { controllerHome, profile: 'manual', staged, config: loadRecoveryConfig(controllerHome) };
951
+ }
732
952
  const config = initializeStandaloneRecovery(controllerHome, input.port ?? 8787, {
953
+ installProfile: profile,
733
954
  ...(input.publicMcpUrl ? { publicMcpUrl: input.publicMcpUrl } : {}),
734
955
  ...(input.recoveryPublicUrl ? { recoveryPublicUrl: input.recoveryPublicUrl } : {}),
735
956
  ...(input.recoveryTunnelService ? { recoveryTunnelService: input.recoveryTunnelService } : {}),
736
957
  ...(input.primaryPublicTunnelService ? { primaryPublicTunnelService: input.primaryPublicTunnelService } : {}),
737
958
  ...(input.primaryRuntimeService ? { primaryRuntimeService: input.primaryRuntimeService } : {}),
738
- primaryRuntimeSourceRoot: resolve(input.repoRoot),
959
+ primaryRuntimeSourceRoot,
960
+ ...(primaryRuntimeSourceRepositoryId ? { primaryRuntimeSourceRepositoryId } : {}),
739
961
  ...(input.primaryConnectorService ? { primaryConnectorService: input.primaryConnectorService } : {}),
740
962
  });
741
- const staged = stageRecoveryRelease({ controllerHome, sourceRoot }, dependencies);
742
- if (input.stageOnly) return { controllerHome, staged, config };
743
963
  const activated = await activateRecoveryRelease({ controllerHome, config, candidate: staged.release }, dependencies);
744
- return { controllerHome, staged, activated, config };
964
+ return { controllerHome, profile, staged, activated, config };
745
965
  }
746
966
 
747
967
  export function recoveryReleaseAuthoritySnapshot(controllerHome: string): {
@@ -749,6 +969,7 @@ export function recoveryReleaseAuthoritySnapshot(controllerHome: string): {
749
969
  previous?: RecoveryReleaseDescriptor;
750
970
  currentPath: string;
751
971
  previousPath: string;
972
+ daemonLaunchAgent: string;
752
973
  gatewayLaunchAgent: string;
753
974
  watchdogLaunchAgent: string;
754
975
  } {
@@ -757,6 +978,7 @@ export function recoveryReleaseAuthoritySnapshot(controllerHome: string): {
757
978
  previous: readPreviousRecoveryRelease(controllerHome),
758
979
  currentPath: recoveryCurrentPath(controllerHome),
759
980
  previousPath: recoveryPreviousPath(controllerHome),
981
+ daemonLaunchAgent: launchAgentPath(RECOVERY_DAEMON_LABEL),
760
982
  gatewayLaunchAgent: launchAgentPath(RECOVERY_GATEWAY_LABEL),
761
983
  watchdogLaunchAgent: launchAgentPath(RECOVERY_WATCHDOG_LABEL),
762
984
  };