@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,13 +1,25 @@
1
1
  import { createHash, randomBytes } from 'crypto';
2
+ import { spawnSync } from 'child_process';
2
3
  import { existsSync } from 'fs';
3
- import { isAbsolute, resolve } from 'path';
4
+ import { hostname } from 'os';
5
+ import { isAbsolute, join, resolve } from 'path';
4
6
  import { Command } from 'commander';
5
- import { readMcpServiceOAuthPassphrase } from '../mcp/auth';
6
- import { resolveControllerHome } from '../repositories/controller-home';
7
+ import { readMcpServiceOAuthPassphrase } from '../../../adapters/mcp/auth';
8
+ import {
9
+ openAiSecureTunnelConnectArgs,
10
+ openAiSecureTunnelStatusArgs,
11
+ parseOpenAiSecureTunnelRuntimeStatus,
12
+ type OpenAiSecureTunnelRuntimeObservation,
13
+ } from '../../../adapters/mcp/tunnels/openai-secure-tunnel';
14
+ import { findRegisteredRepositoryByCheckoutRoot } from '../repositories/registry';
15
+ import {
16
+ resolveControllerHome,
17
+ rollbackStoppedControllerHomeAuthorityRelocation,
18
+ type StoppedControllerHomeAuthorityRelocation,
19
+ } from '../repositories/controller-home';
7
20
  import { FORGE_VERSION } from '../../version';
8
21
  import {
9
- RECOVERY_GATEWAY_LABEL,
10
- RECOVERY_WATCHDOG_LABEL,
22
+ RECOVERY_DAEMON_LABEL,
11
23
  inspectRecoveryTunnelLaunchdContract,
12
24
  installStandaloneRecovery,
13
25
  recoveryLaunchdPid,
@@ -16,11 +28,17 @@ import {
16
28
  } from '../../runtime/standalone-recovery/installer';
17
29
  import {
18
30
  RECOVERY_TOOLS,
31
+ RECOVERY_VERIFIER_OAUTH_CLIENT_ID,
32
+ RECOVERY_VERIFIER_OAUTH_CLIENT_NAME,
33
+ RECOVERY_VERIFIER_OAUTH_REDIRECT_URI,
19
34
  } from '../../runtime/standalone-recovery/entry';
20
35
  import {
21
36
  activateRuntimeRelease,
37
+ defaultPrimaryRuntimeServiceConfig,
22
38
  loadRecoveryConfig,
39
+ normalizeRecoveryInstallProfile,
23
40
  recoverPrimaryRuntime,
41
+ recoveryMachineIdentity,
24
42
  restartPrimaryConnector,
25
43
  restartPrimaryRuntime,
26
44
  restartRecoveryGateway,
@@ -28,12 +46,25 @@ import {
28
46
  rollbackPrevious,
29
47
  runtimeStatus,
30
48
  verifyStableRuntime,
49
+ type OpenAiSecureTunnelServiceConfig,
31
50
  type PrimaryConnectorServiceConfig,
51
+ type RecoveryMachineIdentity,
32
52
  type PublicTunnelServiceConfig,
53
+ type RecoveryTunnelServiceConfig,
54
+ type SystemdUserRecoveryTunnelServiceConfig,
33
55
  } from '../../runtime/standalone-recovery/core';
34
56
  import { readRecoveryRuntimeIdentity } from '../../runtime/standalone-recovery/release';
57
+ import {
58
+ assertRecoveryControllerHomeMigrationReady,
59
+ recoveryControllerHomeMigrationPreflight,
60
+ runLinuxControllerHomeMigrationRequest,
61
+ scheduleLinuxControllerHomeMigration,
62
+ type RecoveryControllerHomeMigrationPreflight,
63
+ } from '../../runtime/standalone-recovery/controller-home-migration';
35
64
  import { configureCodegraph, ensureCodegraph } from '../tools/codegraph';
36
65
  import { isProcessAlive } from '../../runtime/shared/process-tree';
66
+ import { systemdUserServicePid, systemdUserUnitName, systemdUserUnitPath } from '../controller/systemd-user';
67
+ import { installWindowsHostRecoveryPlugin } from '../../runtime/plugins/windows-host-recovery-install';
37
68
 
38
69
  function output(value: unknown, json = true): void {
39
70
  console.log(json ? JSON.stringify(value, null, 2) : String(value));
@@ -55,15 +86,125 @@ function launchdService(label?: string, plistPath?: string, role = 'RECOVERY_TUN
55
86
  return { platform: 'launchd', label, ...(plistPath ? { plistPath } : {}) };
56
87
  }
57
88
 
89
+ function systemdRecoveryTunnelService(unitName?: string): SystemdUserRecoveryTunnelServiceConfig | undefined {
90
+ if (!unitName) return undefined;
91
+ const normalized = systemdUserUnitName(unitName);
92
+ if (!/^[A-Za-z0-9_.@-]{1,180}\.service$/.test(normalized)) throw new Error('RECOVERY_TUNNEL_SYSTEMD_UNIT_INVALID');
93
+ return { platform: 'systemd-user', unitName: normalized };
94
+ }
95
+
96
+ export function recoveryOpenAiTunnelDefaultAlias(
97
+ controllerHome: string,
98
+ platform: NodeJS.Platform = process.platform,
99
+ host = hostname(),
100
+ ): string {
101
+ const suffix = createHash('sha256').update(`${host}\0${platform}\0${resolve(controllerHome)}`).digest('hex').slice(0, 12);
102
+ return `forge-recovery-${suffix}`;
103
+ }
104
+
105
+ export function assertDistinctRecoveryOpenAiTunnelIdentity(
106
+ recovery: RecoveryTunnelServiceConfig | undefined,
107
+ primary: PublicTunnelServiceConfig | undefined,
108
+ ): void {
109
+ if (recovery?.platform !== 'openai-secure-tunnel' || primary?.platform !== 'openai-secure-tunnel') return;
110
+ if (recovery.alias === primary.alias) throw new Error('RECOVERY_OPENAI_TUNNEL_ALIAS_CONFLICT');
111
+ if (recovery.tunnelId === primary.tunnelId) throw new Error('RECOVERY_OPENAI_TUNNEL_ID_CONFLICT');
112
+ }
113
+
114
+ function openAiTunnelService(input: {
115
+ tunnelId?: string;
116
+ alias?: string;
117
+ runtimeApiKeyRef?: string;
118
+ profile?: string;
119
+ profileDir?: string;
120
+ adminProfile?: string;
121
+ mcpServerUrl: string;
122
+ defaultAlias: string;
123
+ role: string;
124
+ }): OpenAiSecureTunnelServiceConfig | undefined {
125
+ const supplied = Boolean(input.tunnelId || input.alias || input.runtimeApiKeyRef || input.profile || input.profileDir || input.adminProfile);
126
+ if (!supplied) return undefined;
127
+ if (!input.tunnelId) throw new Error(`${input.role}_OPENAI_TUNNEL_ID_REQUIRED`);
128
+ if (input.profileDir && !isAbsolute(input.profileDir)) throw new Error(`${input.role}_OPENAI_PROFILE_DIR_ABSOLUTE_REQUIRED`);
129
+ const service: OpenAiSecureTunnelServiceConfig = {
130
+ platform: 'openai-secure-tunnel',
131
+ alias: input.alias?.trim() || input.defaultAlias,
132
+ tunnelId: input.tunnelId.trim(),
133
+ mcpServerUrl: input.mcpServerUrl,
134
+ runtimeApiKeyRef: input.runtimeApiKeyRef?.trim(),
135
+ profile: input.profile?.trim(),
136
+ profileDir: input.profileDir ? resolve(input.profileDir) : undefined,
137
+ adminProfile: input.adminProfile?.trim(),
138
+ };
139
+ try { openAiSecureTunnelConnectArgs(service); } catch (error) {
140
+ throw new Error(`${input.role}_${error instanceof Error ? error.message : 'OPENAI_TUNNEL_CONFIG_INVALID'}`);
141
+ }
142
+ return service;
143
+ }
144
+
145
+
146
+ export { recoveryControllerHomeMigrationPreflight };
147
+ export type { RecoveryControllerHomeMigrationPreflight };
148
+
58
149
  export interface RecoveryConnectorDependencies {
150
+ platform?: NodeJS.Platform;
59
151
  pathExists?: (path: string) => boolean;
60
- launchdPid?: (role: 'gateway' | 'watchdog') => number | undefined;
152
+ launchdPid?: (role: 'daemon') => number | undefined;
153
+ systemdPid?: (role: 'daemon') => number | undefined;
61
154
  tunnelLaunchdPid?: (label: string) => number | undefined;
155
+ tunnelSystemdPid?: (unitName: string) => number | undefined;
156
+ openAiTunnelStatus?: (service: OpenAiSecureTunnelServiceConfig) => OpenAiSecureTunnelRuntimeObservation;
62
157
  processAlive?: (pid: number) => boolean;
158
+ processParentPid?: (pid: number) => number | undefined;
159
+ }
160
+
161
+ function recoveryProcessParentPid(pid: number): number | undefined {
162
+ if (process.platform === 'win32' || !Number.isInteger(pid) || pid <= 1) return undefined;
163
+ const result = spawnSync('ps', ['-o', 'ppid=', '-p', String(pid)], {
164
+ encoding: 'utf8',
165
+ timeout: 2_000,
166
+ maxBuffer: 8 * 1024,
167
+ });
168
+ if (result.status !== 0) return undefined;
169
+ const parent = Number.parseInt((result.stdout ?? '').trim(), 10);
170
+ return Number.isInteger(parent) && parent > 0 ? parent : undefined;
171
+ }
172
+
173
+ /**
174
+ * launchd may own a tiny wrapper (/usr/bin/env) while Recovery records the
175
+ * compiled child PID in its runtime identity. Treat only that bounded process
176
+ * lineage as the same managed service; unrelated live PIDs still fail closed.
177
+ */
178
+ export function recoveryManagedServiceOwnsRuntimeProcess(
179
+ managedPid: number | undefined,
180
+ runtimePid: number | undefined,
181
+ dependencies: {
182
+ processAlive?: (pid: number) => boolean;
183
+ processParentPid?: (pid: number) => number | undefined;
184
+ maxDepth?: number;
185
+ } = {},
186
+ ): boolean {
187
+ if (!managedPid || !runtimePid || managedPid <= 0 || runtimePid <= 0) return false;
188
+ const processAlive = dependencies.processAlive ?? isProcessAlive;
189
+ if (!processAlive(managedPid) || !processAlive(runtimePid)) return false;
190
+ if (managedPid === runtimePid) return true;
191
+ const parentPid = dependencies.processParentPid ?? recoveryProcessParentPid;
192
+ const maxDepth = Math.max(1, Math.min(Math.trunc(dependencies.maxDepth ?? 8), 32));
193
+ const visited = new Set<number>([runtimePid]);
194
+ let current = runtimePid;
195
+ for (let depth = 0; depth < maxDepth; depth += 1) {
196
+ const parent = parentPid(current);
197
+ if (!parent || parent <= 0 || visited.has(parent)) return false;
198
+ if (parent === managedPid) return true;
199
+ visited.add(parent);
200
+ current = parent;
201
+ }
202
+ return false;
63
203
  }
64
204
 
65
205
  export interface RecoveryConnectorDescriptor {
66
206
  name: 'Forge Recovery';
207
+ identity: RecoveryMachineIdentity;
67
208
  transport: 'streamable_http';
68
209
  url: string;
69
210
  public: boolean;
@@ -78,9 +219,22 @@ export interface RecoveryConnectorDescriptor {
78
219
  };
79
220
  healthUrl: string;
80
221
  services: {
81
- gateway: { label: string; plistInstalled: boolean; running: boolean; pid?: number };
82
- watchdog: { label: string; plistInstalled: boolean; running: boolean; pid?: number };
83
- tunnel: { configured: boolean; label?: string; plistInstalled: boolean; restartSafe: boolean; running: boolean; pid?: number };
222
+ recovery: { label: string; platform: 'launchd' | 'systemd-user'; serviceInstalled: boolean; plistInstalled: boolean; running: boolean; pid?: number };
223
+ tunnel: {
224
+ configured: boolean;
225
+ platform?: 'launchd' | 'systemd-user' | 'openai-secure-tunnel';
226
+ label?: string;
227
+ unitName?: string;
228
+ alias?: string;
229
+ tunnelId?: string;
230
+ serviceInstalled?: boolean;
231
+ plistInstalled: boolean;
232
+ restartSafe: boolean;
233
+ running: boolean;
234
+ healthy?: boolean;
235
+ ready?: boolean;
236
+ pid?: number;
237
+ };
84
238
  };
85
239
  tools: string[];
86
240
  warnings: string[];
@@ -98,61 +252,97 @@ export function recoveryConnectorDescriptor(
98
252
  const url = configuredUrl ?? `${localOrigin}/recovery/mcp`;
99
253
  const origin = new URL(url).origin;
100
254
  const passphraseConfigured = Boolean(readMcpServiceOAuthPassphrase(home));
101
- const gatewayIdentity = readRecoveryRuntimeIdentity(home, 'gateway');
102
- const watchdogIdentity = readRecoveryRuntimeIdentity(home, 'watchdog');
255
+ const recoveryIdentity = readRecoveryRuntimeIdentity(home, 'daemon');
256
+ const platform = dependencies.platform ?? process.platform;
257
+ const servicePlatform: 'launchd' | 'systemd-user' = platform === 'linux' ? 'systemd-user' : 'launchd';
103
258
  const pathExists = dependencies.pathExists ?? existsSync;
104
259
  const launchdPid = dependencies.launchdPid ?? recoveryLaunchdPid;
260
+ const systemdPid = dependencies.systemdPid ?? ((_role: 'daemon') => systemdUserServicePid(RECOVERY_DAEMON_LABEL));
261
+ const managedPid = () => servicePlatform === 'systemd-user' ? systemdPid('daemon') : launchdPid('daemon');
105
262
  const processAlive = dependencies.processAlive ?? isProcessAlive;
106
- const tunnelService = config.recoveryTunnelService?.platform === 'launchd' ? config.recoveryTunnelService : undefined;
263
+ const serviceOwnsRuntimeProcess = (managedPid: number | undefined, runtimePid: number | undefined) =>
264
+ recoveryManagedServiceOwnsRuntimeProcess(managedPid, runtimePid, {
265
+ processAlive,
266
+ processParentPid: dependencies.processParentPid,
267
+ });
268
+ const configuredTunnel = config.recoveryTunnelService;
269
+ const tunnelService = configuredTunnel?.platform === 'launchd' ? configuredTunnel : undefined;
270
+ const systemdTunnelService = configuredTunnel?.platform === 'systemd-user' ? configuredTunnel : undefined;
271
+ const openAiTunnelService = configuredTunnel?.platform === 'openai-secure-tunnel' ? configuredTunnel : undefined;
107
272
  const tunnelContract = tunnelService ? inspectRecoveryTunnelLaunchdContract(tunnelService) : undefined;
108
273
  const tunnelLaunchdPid = tunnelService
109
274
  ? (dependencies.tunnelLaunchdPid ?? recoveryLaunchdServicePid)(tunnelService.label)
110
275
  : undefined;
111
276
  const tunnelPlistInstalled = Boolean(tunnelContract?.plistPath && pathExists(tunnelContract.plistPath));
112
- const tunnelRestartSafe = Boolean(tunnelContract?.restartSafe);
113
- const tunnelRunning = Boolean(tunnelLaunchdPid && processAlive(tunnelLaunchdPid));
114
- const gatewayPlistInstalled = pathExists(authority.gatewayLaunchAgent);
115
- const watchdogPlistInstalled = pathExists(authority.watchdogLaunchAgent);
116
- const gatewayLaunchdPid = launchdPid('gateway');
117
- const watchdogLaunchdPid = launchdPid('watchdog');
118
- const gatewayRunning = Boolean(
119
- gatewayIdentity
120
- && gatewayLaunchdPid
121
- && gatewayIdentity.pid === gatewayLaunchdPid
122
- && processAlive(gatewayIdentity.pid)
123
- && authority.current
124
- && gatewayIdentity.releaseRevision === authority.current.releaseRevision
125
- && gatewayIdentity.manifestSha256 === authority.current.manifestSha256,
126
- );
127
- const watchdogRunning = Boolean(
128
- watchdogIdentity
129
- && watchdogLaunchdPid
130
- && watchdogIdentity.pid === watchdogLaunchdPid
131
- && processAlive(watchdogIdentity.pid)
277
+ const systemdTunnelUnitName = systemdTunnelService?.unitName.trim();
278
+ const systemdTunnelIdentityValid = Boolean(systemdTunnelUnitName && /^[A-Za-z0-9_.@-]{1,180}\.service$/.test(systemdTunnelUnitName));
279
+ const systemdTunnelInstalled = Boolean(systemdTunnelIdentityValid && pathExists(systemdUserUnitPath(systemdTunnelUnitName!)));
280
+ const systemdTunnelPid = systemdTunnelIdentityValid
281
+ ? (dependencies.tunnelSystemdPid ?? ((unitName: string) => systemdUserServicePid(unitName)))(systemdTunnelUnitName!)
282
+ : undefined;
283
+ const openAiTunnelStatus = openAiTunnelService
284
+ ? (dependencies.openAiTunnelStatus ?? ((service: OpenAiSecureTunnelServiceConfig) => {
285
+ const status = spawnSync('tunnel-client', openAiSecureTunnelStatusArgs(service.alias, service), {
286
+ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15_000,
287
+ });
288
+ return parseOpenAiSecureTunnelRuntimeStatus(status.stdout || '{}', { alias: service.alias, tunnelId: service.tunnelId, mcpServerUrl: service.mcpServerUrl });
289
+ }))(openAiTunnelService)
290
+ : undefined;
291
+ let openAiRestartSafe = false;
292
+ if (openAiTunnelService) {
293
+ try { openAiSecureTunnelConnectArgs(openAiTunnelService); openAiRestartSafe = true; } catch { openAiRestartSafe = false; }
294
+ }
295
+ const tunnelRestartSafe = openAiTunnelService
296
+ ? openAiRestartSafe
297
+ : systemdTunnelService
298
+ ? systemdTunnelIdentityValid && systemdTunnelInstalled
299
+ : Boolean(tunnelContract?.restartSafe);
300
+ const tunnelRunning = openAiTunnelService
301
+ ? openAiTunnelStatus?.running === true
302
+ : systemdTunnelService
303
+ ? Boolean(systemdTunnelPid && processAlive(systemdTunnelPid))
304
+ : Boolean(tunnelLaunchdPid && processAlive(tunnelLaunchdPid));
305
+ const tunnelHealthy = openAiTunnelService ? openAiTunnelStatus?.healthy === true : tunnelRunning;
306
+ const tunnelReady = openAiTunnelService ? openAiTunnelStatus?.ok === true : tunnelRestartSafe && tunnelRunning;
307
+ const recoveryPlistInstalled = pathExists(authority.daemonLaunchAgent);
308
+ const recoveryServiceInstalled = servicePlatform === 'systemd-user'
309
+ ? pathExists(systemdUserUnitPath(RECOVERY_DAEMON_LABEL))
310
+ : recoveryPlistInstalled;
311
+ const recoveryManagedPid = managedPid();
312
+ const recoveryRunning = Boolean(
313
+ recoveryIdentity
314
+ && serviceOwnsRuntimeProcess(recoveryManagedPid, recoveryIdentity.pid)
132
315
  && authority.current
133
- && watchdogIdentity.releaseRevision === authority.current.releaseRevision
134
- && watchdogIdentity.manifestSha256 === authority.current.manifestSha256,
316
+ && recoveryIdentity.releaseRevision === authority.current.releaseRevision
317
+ && recoveryIdentity.manifestSha256 === authority.current.manifestSha256,
135
318
  );
136
- const installed = Boolean(authority.current && gatewayPlistInstalled && watchdogPlistInstalled);
319
+ const installed = Boolean(authority.current && recoveryServiceInstalled);
137
320
  const publicEndpoint = Boolean(configuredUrl?.startsWith('https://'));
138
321
  const warnings: string[] = [];
139
322
  if (!authority.current) warnings.push('No current immutable Forge Recovery release is installed. Run forge recovery install.');
140
- if (!gatewayPlistInstalled || !watchdogPlistInstalled) warnings.push('Forge Recovery launchd services are not fully installed. Run forge recovery install.');
141
- if (!gatewayRunning || !watchdogRunning) warnings.push('Forge Recovery Gateway or Watchdog is not running on the current Recovery release.');
142
- if (!configuredUrl) warnings.push('Recovery is loopback-only. Configure --recovery-public-url and a dedicated tunnel service before adding it to ChatGPT.');
143
- else if (!publicEndpoint) warnings.push('ChatGPT Recovery Connector requires an HTTPS public endpoint.');
144
- else if (!tunnelService) warnings.push('A dedicated Forge Recovery tunnel service is not configured.');
145
- else if (!tunnelPlistInstalled) warnings.push('The dedicated Forge Recovery tunnel plist is not installed.');
146
- else if (!tunnelRestartSafe) warnings.push('The dedicated Forge Recovery tunnel plist must use RunAtLoad=true and unconditional KeepAlive=true.');
147
- else if (!tunnelRunning) warnings.push('The dedicated Forge Recovery tunnel service is not running.');
323
+ if (!recoveryServiceInstalled) warnings.push(`Forge Recovery ${servicePlatform} service is not installed. Run forge recovery install.`);
324
+ if (!recoveryRunning) warnings.push('Forge Recovery service is not running on the current Recovery release.');
325
+ if (!configuredTunnel) warnings.push('Recovery is loopback-only. Configure a dedicated OpenAI Secure MCP Tunnel or an HTTPS tunnel service before adding it to ChatGPT.');
326
+ else if (openAiTunnelService) {
327
+ if (!tunnelRestartSafe) warnings.push('The dedicated OpenAI Recovery tunnel must use a valid alias, tunnel id, loopback MCP endpoint, and env:/file: runtime API key reference.');
328
+ else if (!tunnelRunning) warnings.push(`The dedicated OpenAI Recovery tunnel runtime ${openAiTunnelService.alias} is not running.`);
329
+ else if (!tunnelHealthy || !tunnelReady) warnings.push(`The dedicated OpenAI Recovery tunnel runtime ${openAiTunnelService.alias} is not healthy and ready.`);
330
+ } else if (!configuredUrl) warnings.push('The configured Recovery tunnel requires --recovery-public-url.');
331
+ else if (!publicEndpoint) warnings.push('ChatGPT Recovery Connector requires an HTTPS public endpoint for managed public tunnels.');
332
+ else if (systemdTunnelService && !systemdTunnelInstalled) warnings.push(`The dedicated Forge Recovery systemd-user tunnel unit ${systemdTunnelUnitName ?? systemdTunnelService.unitName} is not installed.`);
333
+ else if (systemdTunnelService && !tunnelRunning) warnings.push(`The dedicated Forge Recovery systemd-user tunnel unit ${systemdTunnelUnitName} is not running.`);
334
+ else if (!systemdTunnelService && !tunnelPlistInstalled) warnings.push('The dedicated Forge Recovery tunnel plist is not installed.');
335
+ else if (!systemdTunnelService && !tunnelRestartSafe) warnings.push('The dedicated Forge Recovery tunnel plist must use RunAtLoad=true and unconditional KeepAlive=true.');
336
+ else if (!systemdTunnelService && !tunnelRunning) warnings.push('The dedicated Forge Recovery tunnel service is not running.');
148
337
  if (!passphraseConfigured) warnings.push('MCP OAuth passphrase is not configured. Run forge mcp setup chatgpt first.');
149
338
 
150
339
  return {
151
340
  name: 'Forge Recovery',
341
+ identity: recoveryMachineIdentity(config, { platform }),
152
342
  transport: 'streamable_http',
153
343
  url,
154
344
  public: publicEndpoint,
155
- readyForChatGPT: installed && gatewayRunning && watchdogRunning && tunnelRestartSafe && tunnelRunning && publicEndpoint && passphraseConfigured,
345
+ readyForChatGPT: installed && recoveryRunning && tunnelReady && (openAiTunnelService ? true : publicEndpoint) && passphraseConfigured,
156
346
  installed,
157
347
  currentRelease: authority.current?.releaseRevision,
158
348
  previousRelease: authority.previous?.releaseRevision,
@@ -163,25 +353,26 @@ export function recoveryConnectorDescriptor(
163
353
  },
164
354
  healthUrl: `${origin}/recovery/health`,
165
355
  services: {
166
- gateway: {
167
- label: RECOVERY_GATEWAY_LABEL,
168
- plistInstalled: gatewayPlistInstalled,
169
- running: gatewayRunning,
170
- ...(gatewayIdentity ? { pid: gatewayIdentity.pid } : {}),
171
- },
172
- watchdog: {
173
- label: RECOVERY_WATCHDOG_LABEL,
174
- plistInstalled: watchdogPlistInstalled,
175
- running: watchdogRunning,
176
- ...(watchdogIdentity ? { pid: watchdogIdentity.pid } : {}),
356
+ recovery: {
357
+ label: RECOVERY_DAEMON_LABEL,
358
+ platform: servicePlatform,
359
+ serviceInstalled: recoveryServiceInstalled,
360
+ plistInstalled: recoveryPlistInstalled,
361
+ running: recoveryRunning,
362
+ ...(recoveryIdentity ? { pid: recoveryIdentity.pid } : {}),
177
363
  },
178
364
  tunnel: {
179
- configured: Boolean(tunnelService),
365
+ configured: Boolean(configuredTunnel),
366
+ ...(configuredTunnel ? { platform: configuredTunnel.platform } : {}),
180
367
  ...(tunnelService ? { label: tunnelService.label } : {}),
368
+ ...(systemdTunnelService ? { unitName: systemdTunnelUnitName ?? systemdTunnelService.unitName } : {}),
369
+ ...(openAiTunnelService ? { alias: openAiTunnelService.alias, tunnelId: openAiTunnelService.tunnelId } : {}),
370
+ serviceInstalled: openAiTunnelService ? openAiRestartSafe : systemdTunnelService ? systemdTunnelInstalled : tunnelPlistInstalled,
181
371
  plistInstalled: tunnelPlistInstalled,
182
372
  restartSafe: tunnelRestartSafe,
183
373
  running: tunnelRunning,
184
- ...(tunnelLaunchdPid ? { pid: tunnelLaunchdPid } : {}),
374
+ ...(openAiTunnelService ? { healthy: tunnelHealthy, ready: tunnelReady } : {}),
375
+ ...(systemdTunnelPid ? { pid: systemdTunnelPid } : tunnelLaunchdPid ? { pid: tunnelLaunchdPid } : {}),
185
376
  },
186
377
  },
187
378
  tools: RECOVERY_TOOLS.map((tool) => tool.name),
@@ -189,6 +380,12 @@ export function recoveryConnectorDescriptor(
189
380
  };
190
381
  }
191
382
 
383
+ export function recoveryConnectorHasExternalTransport(
384
+ connector: Pick<RecoveryConnectorDescriptor, 'public' | 'services'>,
385
+ ): boolean {
386
+ return connector.public || connector.services.tunnel.platform === 'openai-secure-tunnel';
387
+ }
388
+
192
389
  export interface RecoveryConnectorVerificationDependencies {
193
390
  fetcher?: typeof fetch;
194
391
  random?: (size: number) => Buffer;
@@ -203,7 +400,15 @@ export interface RecoveryConnectorVerificationResult {
203
400
  authorizationMetadata: { ok: boolean; status?: number };
204
401
  protectedResourceMetadata: { ok: boolean; status?: number };
205
402
  unauthenticatedChallenge: { ok: boolean; status?: number };
206
- oauthPkce: { ok: boolean; registrationStatus?: number; authorizationStatus?: number; tokenStatus?: number };
403
+ oauthPkce: {
404
+ ok: boolean;
405
+ registrationStatus?: number;
406
+ authorizationStatus?: number;
407
+ tokenStatus?: number;
408
+ clientOwner?: 'recovery_verifier';
409
+ clientReused?: boolean;
410
+ credentialRetirement?: 'shared_recovery_token_not_individually_revocable';
411
+ };
207
412
  mcp: {
208
413
  ok: boolean;
209
414
  initializeStatus?: number;
@@ -224,7 +429,25 @@ function jsonObject(value: unknown): Record<string, unknown> {
224
429
  }
225
430
 
226
431
  async function responseJson(response: Response): Promise<Record<string, unknown>> {
227
- try { return jsonObject(await response.json()); } catch { return {}; }
432
+ try {
433
+ const text = await response.text();
434
+ if (!text.trim()) return {};
435
+ if (/text\/event-stream/i.test(response.headers.get('content-type') ?? '')) {
436
+ for (const line of text.split(/\r?\n/)) {
437
+ if (!line.startsWith('data:')) continue;
438
+ try {
439
+ const payload = jsonObject(JSON.parse(line.slice('data:'.length).trim()));
440
+ if (Object.keys(payload).length > 0) return payload;
441
+ } catch {
442
+ // Ignore non-JSON SSE fields and keep looking for the MCP data event.
443
+ }
444
+ }
445
+ return {};
446
+ }
447
+ return jsonObject(JSON.parse(text));
448
+ } catch {
449
+ return {};
450
+ }
228
451
  }
229
452
 
230
453
  export async function verifyRecoveryConnector(
@@ -250,7 +473,9 @@ export async function verifyRecoveryConnector(
250
473
  });
251
474
 
252
475
  if (!connector.readyForChatGPT) failures.push(...connector.warnings.map((warning) => `readiness: ${warning}`));
253
- if (!connector.public) failures.push('readiness: Recovery Connector does not have an HTTPS public endpoint.');
476
+ if (!recoveryConnectorHasExternalTransport(connector)) {
477
+ failures.push('readiness: Recovery Connector has neither an HTTPS public endpoint nor a dedicated OpenAI Secure MCP Tunnel.');
478
+ }
254
479
 
255
480
  const origin = new URL(connector.url).origin;
256
481
  try {
@@ -325,12 +550,13 @@ export async function verifyRecoveryConnector(
325
550
  failures.push('oauthPkce: MCP OAuth passphrase is not configured.');
326
551
  } else {
327
552
  try {
328
- const redirectUri = 'http://127.0.0.1/forge-recovery-oauth-callback';
553
+ const redirectUri = RECOVERY_VERIFIER_OAUTH_REDIRECT_URI;
329
554
  const registration = await request(`${origin}/recovery/oauth/register`, {
330
555
  method: 'POST',
331
556
  headers: { 'content-type': 'application/json' },
332
557
  body: JSON.stringify({
333
- client_name: 'Forge Recovery Verification',
558
+ client_id: RECOVERY_VERIFIER_OAUTH_CLIENT_ID,
559
+ client_name: RECOVERY_VERIFIER_OAUTH_CLIENT_NAME,
334
560
  redirect_uris: [redirectUri],
335
561
  grant_types: ['authorization_code'],
336
562
  response_types: ['code'],
@@ -339,7 +565,11 @@ export async function verifyRecoveryConnector(
339
565
  });
340
566
  const registrationBody = await responseJson(registration);
341
567
  const clientId = typeof registrationBody.client_id === 'string' ? registrationBody.client_id : '';
342
- if (registration.status !== 201 || !clientId) throw new Error(`dynamic registration HTTP ${registration.status}`);
568
+ const clientOwner = registrationBody.forge_client_owner;
569
+ const clientReused = registrationBody.forge_client_reused === true;
570
+ if (registration.status !== 201 || clientId !== RECOVERY_VERIFIER_OAUTH_CLIENT_ID || clientOwner !== 'recovery_verifier') {
571
+ throw new Error(`dynamic verifier registration HTTP ${registration.status}`);
572
+ }
343
573
 
344
574
  const verifier = random(32).toString('base64url');
345
575
  const challenge = createHash('sha256').update(verifier).digest('base64url');
@@ -388,6 +618,13 @@ export async function verifyRecoveryConnector(
388
618
  registrationStatus: registration.status,
389
619
  authorizationStatus: authorization.status,
390
620
  tokenStatus: tokenResponse.status,
621
+ clientOwner: 'recovery_verifier',
622
+ clientReused,
623
+ // Recovery MCP currently uses one configured shared bearer token, so
624
+ // there is no verifier-specific credential that can be revoked without
625
+ // revoking Recovery itself. The reserved client slot keeps verifier
626
+ // registration state bounded across success and failure loops.
627
+ credentialRetirement: 'shared_recovery_token_not_individually_revocable',
391
628
  };
392
629
 
393
630
  const rpc = async (id: number | undefined, method: string, params?: Record<string, unknown>) => {
@@ -401,7 +638,13 @@ export async function verifyRecoveryConnector(
401
638
  },
402
639
  body: JSON.stringify({ jsonrpc: '2.0', ...(id === undefined ? {} : { id }), method, ...(params ? { params } : {}) }),
403
640
  });
404
- if (method === 'notifications/initialized') return { response, result: {} as Record<string, unknown> };
641
+ if (response.headers.get('mcp-session-id')?.trim()) {
642
+ throw new Error(`${method} unexpectedly established transport session state`);
643
+ }
644
+ if (method === 'notifications/initialized') {
645
+ if (response.status !== 202) throw new Error(`${method} HTTP ${response.status}`);
646
+ return { response, result: {} as Record<string, unknown> };
647
+ }
405
648
  const body = await responseJson(response);
406
649
  const rpcError = jsonObject(body.error);
407
650
  if (response.status !== 200 || Object.keys(rpcError).length > 0) {
@@ -444,7 +687,7 @@ export async function verifyRecoveryConnector(
444
687
  runtimeStatusCall: runtimeStatusCall.response.status === 200,
445
688
  listReleasesCall: listReleasesCall.response.status === 200,
446
689
  };
447
- if (!mcpOk) failures.push('mcp: initialize, Forge version, tool surface, or read-only calls did not match the Recovery contract.');
690
+ if (!mcpOk) failures.push('mcp: stateless initialize, Forge version, tool surface, or read-only calls did not match the Recovery contract.');
448
691
  } catch (error) {
449
692
  failures.push(`oauthPkce/mcp: ${error instanceof Error ? error.message : String(error)}`);
450
693
  }
@@ -516,6 +759,10 @@ export function buildRecoveryCommand(): Command {
516
759
  }) => {
517
760
  const home = resolveControllerHome(opts.controllerHome);
518
761
  const repoRoot = resolve(opts.repo);
762
+ const primaryRuntimeSourceRepository = findRegisteredRepositoryByCheckoutRoot(repoRoot, home);
763
+ if (!primaryRuntimeSourceRepository) {
764
+ throw new Error(`RECOVERY_PRIMARY_RUNTIME_SOURCE_REPOSITORY_NOT_REGISTERED: ${repoRoot}`);
765
+ }
519
766
  const current = loadRecoveryConfig(home);
520
767
  const primaryConnectorBase = launchdService(
521
768
  opts.primaryConnectorServiceLabel,
@@ -529,13 +776,15 @@ export function buildRecoveryCommand(): Command {
529
776
  const installed = await installStandaloneRecovery({
530
777
  controllerHome: home,
531
778
  repoRoot,
779
+ primaryRuntimeSourceRepositoryId: primaryRuntimeSourceRepository.repoId,
532
780
  port: current.gateway?.port ?? 8787,
533
781
  publicMcpUrl: current.publicMcpUrl,
534
782
  recoveryPublicUrl: current.recoveryPublicUrl,
535
783
  recoveryTunnelService: current.recoveryTunnelService,
536
784
  primaryPublicTunnelService: current.primaryPublicTunnelService,
537
- primaryRuntimeService: current.primaryRuntimeService ?? { platform: 'launchd' },
785
+ primaryRuntimeService: current.primaryRuntimeService ?? defaultPrimaryRuntimeServiceConfig(),
538
786
  primaryConnectorService,
787
+ profile: 'self-healing',
539
788
  });
540
789
  const refreshed = loadRecoveryConfig(home);
541
790
  const runtime = await stageAndActivateConfiguredRuntimeRelease(refreshed);
@@ -620,20 +869,121 @@ export function buildRecoveryCommand(): Command {
620
869
  if (!result.ok) process.exitCode = 1;
621
870
  });
622
871
 
872
+ command.command('migrate-controller-home')
873
+ .description('Transactionally relocate Forge Controller Home on Linux/WSL through an independent transient systemd worker')
874
+ .requiredOption('--from <path>', 'Current Controller Home authority')
875
+ .requiredOption('--controller-home <path>', 'Destination user-level Controller Home')
876
+ .option('--archive-suffix <suffix>', 'Stable suffix for an authority-free destination shell archive')
877
+ .option('--timeout-ms <ms>', 'Overall migration transaction timeout in milliseconds', '240000')
878
+ .action(async (opts: { from: string; controllerHome: string; archiveSuffix?: string; timeoutMs: string }) => {
879
+ if (process.platform !== 'linux') throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_LINUX_ONLY');
880
+ const timeoutMs = Number(opts.timeoutMs);
881
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 30_000 || timeoutMs > 900_000) {
882
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_TIMEOUT_INVALID');
883
+ }
884
+ const scheduled = scheduleLinuxControllerHomeMigration({
885
+ sourceHome: resolve(opts.from),
886
+ destinationHome: resolveControllerHome(opts.controllerHome),
887
+ ...(opts.archiveSuffix ? { archiveSuffix: opts.archiveSuffix } : {}),
888
+ timeoutMs,
889
+ });
890
+ output({
891
+ status: 'migration_scheduled',
892
+ operationId: scheduled.request.operationId,
893
+ sourceHome: scheduled.request.sourceHome,
894
+ destinationHome: scheduled.request.destinationHome,
895
+ workerUnit: scheduled.request.workerUnit,
896
+ receiptPath: scheduled.request.receiptPath,
897
+ logPath: scheduled.request.logPath,
898
+ preflight: scheduled.preflight,
899
+ });
900
+ });
901
+
902
+ command.command('migrate-controller-home-worker')
903
+ .description('Internal Linux/WSL transient worker for a durable Controller Home migration request')
904
+ .requiredOption('--request <path>', 'Durable migration request written before service cutover')
905
+ .action(async (opts: { request: string }) => {
906
+ if (process.platform !== 'linux') throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_LINUX_ONLY');
907
+ const receipt = await runLinuxControllerHomeMigrationRequest(resolve(opts.request));
908
+ // A durable terminal receipt, including failed-after-rollback, is a completed worker transaction.
909
+ // Only an unhandled process failure before a terminal receipt should trigger systemd Restart=on-failure.
910
+ output(receipt);
911
+ });
912
+
913
+ command.command('rollback-controller-home-migration')
914
+ .description('Rollback a stopped Controller Home relocation before any writer is restarted')
915
+ .requiredOption('--from <path>', 'Original Controller Home path')
916
+ .requiredOption('--controller-home <path>', 'Relocated user-level Controller Home path')
917
+ .option('--archived-destination-home <path>', 'Archived authority-free destination shell returned by migration')
918
+ .action(async (opts: { from: string; controllerHome: string; archivedDestinationHome?: string }) => {
919
+ if (process.platform !== 'linux') throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_LINUX_ONLY');
920
+ const sourceHome = resolve(opts.from);
921
+ const destinationHome = resolveControllerHome(opts.controllerHome);
922
+ const preflight = recoveryControllerHomeMigrationPreflight(destinationHome, sourceHome);
923
+ if (preflight.liveOwners.length > 0) {
924
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_OWNERS_LIVE: ${preflight.liveOwners.map((entry) => `${entry.label}:${entry.pid}`).join(',')}`);
925
+ }
926
+ const relocation: StoppedControllerHomeAuthorityRelocation = {
927
+ migrated: true,
928
+ sourceHome,
929
+ destinationHome,
930
+ ...(opts.archivedDestinationHome ? { archivedDestinationHome: resolve(opts.archivedDestinationHome) } : {}),
931
+ };
932
+ rollbackStoppedControllerHomeAuthorityRelocation(relocation);
933
+ output({ status: 'rolled_back', relocation });
934
+ });
935
+
936
+ command.command('windows-host-register')
937
+ .description('Register the Windows-host Recovery provider using discovered or explicitly bound host paths')
938
+ .option('--controller-home <path>', 'Override Controller Home')
939
+ .option('--rescue-root <path>', 'Override the local Recovery helper root')
940
+ .option('--recovery-script-host <path>', 'Host-visible path to ForgeRecovery.ps1')
941
+ .option('--recovery-script-windows <path>', 'Windows path to ForgeRecovery.ps1')
942
+ .option('--powershell-host <path>', 'Host-visible path to powershell.exe')
943
+ .option('--powershell-windows <path>', 'Windows path to powershell.exe')
944
+ .option('--disabled', 'Register the provider disabled')
945
+ .action((opts) => {
946
+ const result = installWindowsHostRecoveryPlugin({
947
+ controllerHome: resolveControllerHome(opts.controllerHome),
948
+ rescueRoot: opts.rescueRoot,
949
+ recoveryScriptHostPath: opts.recoveryScriptHost,
950
+ recoveryScriptWindowsPath: opts.recoveryScriptWindows,
951
+ powershellHostPath: opts.powershellHost,
952
+ powershellWindowsPath: opts.powershellWindows,
953
+ enabled: opts.disabled !== true,
954
+ });
955
+ output({ status: 'registered', ...result });
956
+ });
957
+
623
958
  command.command('install')
624
- .description('Build and activate the independent Forge Recovery Gateway and Watchdog release')
959
+ .description('Build Forge Recovery artifacts and activate only the explicitly selected persistent Recovery profile')
625
960
  .requiredOption('--controller-home <path>', 'Explicit Controller Home')
961
+ .option('--profile <profile>', 'Recovery install profile: manual, gateway, or self-healing', 'manual')
626
962
  .option('--port <port>', 'Loopback Recovery Gateway port', '8787')
627
963
  .option('--public-mcp-url <url>', 'Primary Forge MCP public URL')
628
964
  .option('--recovery-public-url <url>', 'Dedicated Forge Recovery MCP public URL')
629
965
  .option('--recovery-tunnel-service-label <label>', 'Dedicated Recovery tunnel launchd label')
630
966
  .option('--recovery-tunnel-service-plist <path>', 'Absolute Recovery tunnel launchd plist path')
967
+ .option('--recovery-tunnel-systemd-unit <unit>', 'Dedicated Recovery systemd --user tunnel unit (Linux only)')
968
+ .option('--recovery-openai-tunnel-id <id>', 'Dedicated Recovery OpenAI Secure MCP Tunnel id')
969
+ .option('--recovery-openai-tunnel-alias <alias>', 'Dedicated Recovery tunnel-client runtime alias (default: machine-specific forge-recovery-<id>)')
970
+ .option('--recovery-openai-runtime-api-key-ref <ref>', 'Recovery tunnel runtime API key reference (env:NAME or file:/absolute/path)')
971
+ .option('--recovery-openai-profile <profile>', 'Optional tunnel-client runtime profile name for Recovery')
972
+ .option('--recovery-openai-profile-dir <path>', 'Optional absolute tunnel-client profile directory for Recovery')
973
+ .option('--recovery-openai-admin-profile <profile>', 'Optional tunnel-client admin profile used for Recovery runtime connect')
631
974
  .option('--primary-connector-service-label <label>', 'Primary OAuth/Connector launchd label managed by standalone Recovery')
632
975
  .option('--primary-connector-service-plist <path>', 'Absolute primary OAuth/Connector launchd plist path')
633
976
  .option('--primary-connector-local-url <url>', 'Local primary OAuth/Connector MCP endpoint used to distinguish Connector from tunnel failures')
634
977
  .option('--primary-tunnel-service-label <label>', 'Primary public MCP tunnel launchd label managed by standalone Recovery')
635
978
  .option('--primary-tunnel-service-plist <path>', 'Absolute primary public MCP tunnel launchd plist path')
636
- .option('--stage-only', 'Build and canary the Recovery release without activating services')
979
+ .option('--primary-openai-tunnel-id <id>', 'Primary OpenAI Secure MCP Tunnel id managed by Recovery')
980
+ .option('--primary-openai-tunnel-alias <alias>', 'Primary tunnel-client runtime alias (default: forge)')
981
+ .option('--primary-openai-runtime-api-key-ref <ref>', 'Primary tunnel runtime API key reference (env:NAME or file:/absolute/path)')
982
+ .option('--primary-openai-profile <profile>', 'Optional tunnel-client runtime profile name for primary Forge')
983
+ .option('--primary-openai-profile-dir <path>', 'Optional absolute tunnel-client profile directory for primary Forge')
984
+ .option('--primary-openai-admin-profile <profile>', 'Optional tunnel-client admin profile used for primary runtime connect')
985
+ .option('--primary-runtime-source-root <path>', 'Stable canonical/package source used by Recovery for future Runtime staging')
986
+ .option('--stage-only', 'Compatibility alias: build/canary only and do not mutate installed Recovery config or services')
637
987
  .action(async (opts: {
638
988
  controllerHome: string;
639
989
  port: string;
@@ -641,36 +991,112 @@ export function buildRecoveryCommand(): Command {
641
991
  recoveryPublicUrl?: string;
642
992
  recoveryTunnelServiceLabel?: string;
643
993
  recoveryTunnelServicePlist?: string;
994
+ recoveryTunnelSystemdUnit?: string;
995
+ recoveryOpenaiTunnelId?: string;
996
+ recoveryOpenaiTunnelAlias?: string;
997
+ recoveryOpenaiRuntimeApiKeyRef?: string;
998
+ recoveryOpenaiProfile?: string;
999
+ recoveryOpenaiProfileDir?: string;
1000
+ recoveryOpenaiAdminProfile?: string;
644
1001
  primaryConnectorServiceLabel?: string;
645
1002
  primaryConnectorServicePlist?: string;
646
1003
  primaryConnectorLocalUrl?: string;
647
1004
  primaryTunnelServiceLabel?: string;
648
1005
  primaryTunnelServicePlist?: string;
1006
+ primaryOpenaiTunnelId?: string;
1007
+ primaryOpenaiTunnelAlias?: string;
1008
+ primaryOpenaiRuntimeApiKeyRef?: string;
1009
+ primaryOpenaiProfile?: string;
1010
+ primaryOpenaiProfileDir?: string;
1011
+ primaryOpenaiAdminProfile?: string;
1012
+ primaryRuntimeSourceRoot?: string;
1013
+ profile: string;
649
1014
  stageOnly?: boolean;
650
1015
  }) => {
651
1016
  const home = resolveControllerHome(opts.controllerHome);
1017
+ const profile = normalizeRecoveryInstallProfile(opts.stageOnly ? 'manual' : opts.profile, 'manual');
1018
+ if (opts.stageOnly && opts.profile !== 'manual') throw new Error('RECOVERY_STAGE_ONLY_PROFILE_CONFLICT');
652
1019
  const port = Number(opts.port);
653
1020
  if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new Error('RECOVERY_PORT_INVALID');
654
- const recoveryTunnelService = launchdService(opts.recoveryTunnelServiceLabel, opts.recoveryTunnelServicePlist, 'RECOVERY_TUNNEL') as PublicTunnelServiceConfig | undefined;
655
- const primaryConnectorBase = launchdService(opts.primaryConnectorServiceLabel, opts.primaryConnectorServicePlist, 'RECOVERY_PRIMARY_CONNECTOR') as PrimaryConnectorServiceConfig | undefined;
1021
+ const recoveryLaunchdTunnel = launchdService(opts.recoveryTunnelServiceLabel, opts.recoveryTunnelServicePlist, 'RECOVERY_TUNNEL') as PublicTunnelServiceConfig | undefined;
1022
+ const recoverySystemdTunnel = systemdRecoveryTunnelService(opts.recoveryTunnelSystemdUnit);
1023
+ if (recoveryLaunchdTunnel && process.platform === 'linux') throw new Error('RECOVERY_TUNNEL_LAUNCHD_UNSUPPORTED_ON_LINUX');
1024
+ if (recoverySystemdTunnel && process.platform !== 'linux') throw new Error('RECOVERY_TUNNEL_SYSTEMD_UNSUPPORTED_ON_THIS_PLATFORM');
1025
+ const recoveryLocalMcpUrl = `http://127.0.0.1:${port}/recovery/mcp`;
1026
+ const recoveryOpenAiTunnel = openAiTunnelService({
1027
+ tunnelId: opts.recoveryOpenaiTunnelId,
1028
+ alias: opts.recoveryOpenaiTunnelAlias,
1029
+ runtimeApiKeyRef: opts.recoveryOpenaiRuntimeApiKeyRef,
1030
+ profile: opts.recoveryOpenaiProfile,
1031
+ profileDir: opts.recoveryOpenaiProfileDir,
1032
+ adminProfile: opts.recoveryOpenaiAdminProfile,
1033
+ mcpServerUrl: recoveryLocalMcpUrl,
1034
+ defaultAlias: recoveryOpenAiTunnelDefaultAlias(home),
1035
+ role: 'RECOVERY',
1036
+ });
1037
+ const recoveryTunnelOwners = [recoveryLaunchdTunnel, recoverySystemdTunnel, recoveryOpenAiTunnel].filter(Boolean);
1038
+ if (recoveryTunnelOwners.length > 1) throw new Error('RECOVERY_TUNNEL_OWNER_CONFLICT');
1039
+ const recoveryTunnelService: RecoveryTunnelServiceConfig | undefined = recoveryOpenAiTunnel ?? recoverySystemdTunnel ?? recoveryLaunchdTunnel;
1040
+
1041
+ const primaryConnectorLaunchd = launchdService(opts.primaryConnectorServiceLabel, opts.primaryConnectorServicePlist, 'RECOVERY_PRIMARY_CONNECTOR') as PrimaryConnectorServiceConfig | undefined;
656
1042
  const primaryConnectorLocalUrl = endpoint(opts.primaryConnectorLocalUrl, 'RECOVERY_PRIMARY_CONNECTOR_LOCAL_URL');
657
- if (primaryConnectorLocalUrl && !primaryConnectorBase) throw new Error('RECOVERY_PRIMARY_CONNECTOR_LABEL_REQUIRED_FOR_LOCAL_URL');
658
- const primaryConnectorService = primaryConnectorBase
659
- ? { ...primaryConnectorBase, ...(primaryConnectorLocalUrl ? { localMcpUrl: primaryConnectorLocalUrl } : {}) }
660
- : undefined;
661
- const primaryPublicTunnelService = launchdService(opts.primaryTunnelServiceLabel, opts.primaryTunnelServicePlist, 'RECOVERY_PRIMARY_TUNNEL') as PublicTunnelServiceConfig | undefined;
1043
+ let primaryConnectorService: PrimaryConnectorServiceConfig | undefined;
1044
+ if (process.platform === 'linux') {
1045
+ if (primaryConnectorLaunchd) throw new Error('RECOVERY_PRIMARY_CONNECTOR_LAUNCHD_UNSUPPORTED_ON_LINUX');
1046
+ primaryConnectorService = primaryConnectorLocalUrl ? { platform: 'systemd-user', localMcpUrl: primaryConnectorLocalUrl } : undefined;
1047
+ } else {
1048
+ if (primaryConnectorLocalUrl && !primaryConnectorLaunchd) throw new Error('RECOVERY_PRIMARY_CONNECTOR_LABEL_REQUIRED_FOR_LOCAL_URL');
1049
+ primaryConnectorService = primaryConnectorLaunchd
1050
+ ? { ...primaryConnectorLaunchd, ...(primaryConnectorLocalUrl ? { localMcpUrl: primaryConnectorLocalUrl } : {}) }
1051
+ : undefined;
1052
+ }
1053
+
1054
+ const primaryLaunchdTunnel = launchdService(opts.primaryTunnelServiceLabel, opts.primaryTunnelServicePlist, 'RECOVERY_PRIMARY_TUNNEL') as PublicTunnelServiceConfig | undefined;
1055
+ const primaryOpenAiTunnel = primaryConnectorLocalUrl ? openAiTunnelService({
1056
+ tunnelId: opts.primaryOpenaiTunnelId,
1057
+ alias: opts.primaryOpenaiTunnelAlias,
1058
+ runtimeApiKeyRef: opts.primaryOpenaiRuntimeApiKeyRef,
1059
+ profile: opts.primaryOpenaiProfile,
1060
+ profileDir: opts.primaryOpenaiProfileDir,
1061
+ adminProfile: opts.primaryOpenaiAdminProfile,
1062
+ mcpServerUrl: primaryConnectorLocalUrl,
1063
+ defaultAlias: 'forge',
1064
+ role: 'RECOVERY_PRIMARY',
1065
+ }) : undefined;
1066
+ const primaryOpenAiOptionsSupplied = Boolean(opts.primaryOpenaiTunnelId || opts.primaryOpenaiTunnelAlias || opts.primaryOpenaiRuntimeApiKeyRef || opts.primaryOpenaiProfile || opts.primaryOpenaiProfileDir || opts.primaryOpenaiAdminProfile);
1067
+ if (primaryOpenAiOptionsSupplied && !primaryConnectorLocalUrl) throw new Error('RECOVERY_PRIMARY_CONNECTOR_LOCAL_URL_REQUIRED_FOR_OPENAI_TUNNEL');
1068
+ if (primaryLaunchdTunnel && primaryOpenAiTunnel) throw new Error('RECOVERY_PRIMARY_TUNNEL_OWNER_CONFLICT');
1069
+ const primaryPublicTunnelService = primaryOpenAiTunnel ?? primaryLaunchdTunnel;
662
1070
  if (primaryPublicTunnelService && !primaryConnectorService) throw new Error('RECOVERY_PRIMARY_CONNECTOR_REQUIRED_FOR_PRIMARY_TUNNEL');
1071
+ assertDistinctRecoveryOpenAiTunnelIdentity(recoveryTunnelService, primaryPublicTunnelService);
1072
+
663
1073
  const recoveryPublicUrl = endpoint(opts.recoveryPublicUrl, 'RECOVERY_PUBLIC_URL');
664
- if (Boolean(recoveryTunnelService) !== Boolean(recoveryPublicUrl)) {
665
- throw new Error('RECOVERY_PUBLIC_URL_AND_TUNNEL_SERVICE_MUST_BE_CONFIGURED_TOGETHER');
666
- }
1074
+ if (profile === 'manual' && (recoveryTunnelService || recoveryPublicUrl)) throw new Error('RECOVERY_PROFILE_GATEWAY_REQUIRED');
1075
+ if (recoveryOpenAiTunnel && recoveryPublicUrl) throw new Error('RECOVERY_OPENAI_TUNNEL_PUBLIC_URL_CONFLICT');
1076
+ if ((recoveryLaunchdTunnel || recoverySystemdTunnel) && !recoveryPublicUrl) throw new Error('RECOVERY_PUBLIC_URL_AND_TUNNEL_SERVICE_MUST_BE_CONFIGURED_TOGETHER');
1077
+ if (!recoveryTunnelService && recoveryPublicUrl) throw new Error('RECOVERY_PUBLIC_URL_AND_TUNNEL_SERVICE_MUST_BE_CONFIGURED_TOGETHER');
1078
+
667
1079
  const packageRoot = resolve(import.meta.dir, '..', '..', '..');
1080
+ const primaryRuntimeSourceRoot = opts.primaryRuntimeSourceRoot ? resolve(opts.primaryRuntimeSourceRoot) : packageRoot;
1081
+ const primaryRuntimeSourceRepository = findRegisteredRepositoryByCheckoutRoot(primaryRuntimeSourceRoot, home);
1082
+ if (!opts.stageOnly && !primaryRuntimeSourceRepository) {
1083
+ throw new Error(`RECOVERY_PRIMARY_RUNTIME_SOURCE_REPOSITORY_NOT_REGISTERED: ${primaryRuntimeSourceRoot}`);
1084
+ }
1085
+ if (!opts.stageOnly && /[\\/]\.forge[\\/]managed-worktrees[\\/]/.test(primaryRuntimeSourceRoot)) {
1086
+ throw new Error('RECOVERY_PRIMARY_RUNTIME_SOURCE_ROOT_DURABLE_REQUIRED');
1087
+ }
668
1088
  const result = await installStandaloneRecovery({
669
1089
  controllerHome: home,
670
- repoRoot: packageRoot,
671
- sourceRoot: packageRoot,
1090
+ repoRoot: primaryRuntimeSourceRoot,
1091
+ ...(primaryRuntimeSourceRepository ? { primaryRuntimeSourceRepositoryId: primaryRuntimeSourceRepository.repoId } : {}),
1092
+ // The package root is only the CLI's location. A package installation
1093
+ // is not a Git checkout, while the explicitly registered primary
1094
+ // Runtime source is the immutable Recovery release's provenance.
1095
+ sourceRoot: primaryRuntimeSourceRoot,
672
1096
  port,
1097
+ profile,
673
1098
  stageOnly: opts.stageOnly === true,
1099
+ primaryRuntimeService: defaultPrimaryRuntimeServiceConfig(),
674
1100
  publicMcpUrl: endpoint(opts.publicMcpUrl, 'PUBLIC_MCP_URL'),
675
1101
  recoveryPublicUrl,
676
1102
  recoveryTunnelService,
@@ -679,6 +1105,7 @@ export function buildRecoveryCommand(): Command {
679
1105
  });
680
1106
  output({
681
1107
  status: opts.stageOnly ? 'staged' : 'installed',
1108
+ profile: result.profile,
682
1109
  staged: result.staged.release,
683
1110
  activation: result.activated,
684
1111
  connector: recoveryConnectorDescriptor(home),