@moretea-labs/forge 1.7.2 → 1.8.0

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 (685) hide show
  1. package/AGENTS.md +58 -62
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +42 -62
  4. package/README.md +2 -2
  5. package/SKILL.md +0 -1
  6. package/THIRD_PARTY_NOTICES.md +5 -2
  7. package/adapters/browser/index.ts +2 -0
  8. package/adapters/chatgpt/browser-delivery-host.ts +71 -0
  9. package/adapters/chatgpt/browser-delivery-runtime.ts +783 -0
  10. package/adapters/chatgpt/controller-binding-store.ts +55 -0
  11. package/adapters/chatgpt/controller-host.ts +119 -0
  12. package/adapters/chatgpt/controller-round-host.ts +78 -0
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +45 -0
  14. package/adapters/chatgpt/provider-delivery.ts +113 -0
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +192 -0
  16. package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
  17. package/adapters/computer/desktop-operator-contract.ts +7 -0
  18. package/adapters/computer/desktop-operator-discovery.ts +201 -0
  19. package/adapters/computer/desktop-operator-negotiation.ts +174 -0
  20. package/adapters/computer/desktop-operator-provider.ts +312 -0
  21. package/adapters/computer/index.ts +1 -0
  22. package/adapters/computer/interaction-target-authority.ts +944 -0
  23. package/adapters/controller-process/binding-store.ts +34 -0
  24. package/adapters/controller-process/controller-host.ts +26 -0
  25. package/adapters/mcp/access-mode.ts +132 -0
  26. package/adapters/mcp/audit.ts +32 -0
  27. package/adapters/mcp/auth.ts +407 -0
  28. package/adapters/mcp/controller-round-compatibility.ts +184 -0
  29. package/adapters/mcp/frozen-client-semantic-compatibility.ts +292 -0
  30. package/adapters/mcp/instructions.ts +36 -0
  31. package/adapters/mcp/legacy-context.ts +18 -0
  32. package/adapters/mcp/multi-repository.ts +437 -0
  33. package/adapters/mcp/oauth.ts +391 -0
  34. package/adapters/mcp/paths.ts +140 -0
  35. package/adapters/mcp/policy.ts +232 -0
  36. package/adapters/mcp/proxy-env.ts +62 -0
  37. package/adapters/mcp/readiness-projection.ts +24 -0
  38. package/adapters/mcp/redaction.ts +21 -0
  39. package/adapters/mcp/repo.ts +24 -0
  40. package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
  41. package/adapters/mcp/runtime-gateway/context-adapter.ts +829 -0
  42. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +191 -0
  43. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +147 -0
  44. package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
  45. package/adapters/mcp/runtime-gateway/execution-tools.ts +207 -0
  46. package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
  47. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +411 -0
  48. package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
  49. package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
  50. package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
  51. package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
  52. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +308 -0
  53. package/adapters/mcp/runtime-gateway/process-tools.ts +289 -0
  54. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +76 -0
  55. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +268 -0
  56. package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
  57. package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
  58. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +110 -0
  59. package/adapters/mcp/runtime-gateway/result-adapter.ts +108 -0
  60. package/adapters/mcp/runtime-gateway/router.ts +728 -0
  61. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1251 -0
  62. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +634 -0
  63. package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
  64. package/adapters/mcp/runtime-gateway/runtime-tools.ts +68 -0
  65. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +186 -0
  66. package/adapters/mcp/runtime-gateway/shared-adapter.ts +25 -0
  67. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +870 -0
  68. package/adapters/mcp/runtime-gateway/work-adapter.ts +3186 -0
  69. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +502 -0
  70. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
  71. package/adapters/mcp/server.ts +1048 -0
  72. package/adapters/mcp/setup.ts +791 -0
  73. package/adapters/mcp/tool-capability-cache.ts +87 -0
  74. package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
  75. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5265 -0
  76. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +150 -0
  77. package/adapters/mcp/tool-mapping/repository-tools.ts +1691 -0
  78. package/adapters/mcp/tool-mapping/tools.ts +9 -0
  79. package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
  80. package/adapters/mcp/toolset-names.ts +74 -0
  81. package/adapters/mcp/toolset-selection.ts +41 -0
  82. package/adapters/mcp/toolset.ts +336 -0
  83. package/adapters/mcp/transports/http.ts +1456 -0
  84. package/adapters/mcp/transports/session-registry.ts +513 -0
  85. package/adapters/mcp/transports/stdio.ts +8 -0
  86. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +139 -0
  87. package/adapters/mcp/types.ts +41 -0
  88. package/assets/AGENTS.md +2 -16
  89. package/assets/CLAUDE.md +2 -16
  90. package/assets/hooks/AGENTS.md +3 -14
  91. package/assets/hooks/CLAUDE.md +3 -14
  92. package/assets/hooks/hook-input.sh +9 -2
  93. package/assets/hooks/lib/workflow-state.sh +28 -10
  94. package/assets/hooks/prompt-guard.sh +63 -37
  95. package/assets/hooks/security-sentinel.sh +2 -1
  96. package/assets/hooks/session-start-context.sh +7 -5
  97. package/assets/hooks/stop-orchestrator.sh +13 -3
  98. package/assets/initializer-question-pack.v4.json +11 -7
  99. package/assets/partials/05-workflow.partial.md +18 -73
  100. package/assets/partials/07-footer.partial.md +1 -1
  101. package/assets/partials-agents/02-operating-mode.partial.md +1 -2
  102. package/assets/partials-agents/04-task-protocol.partial.md +14 -59
  103. package/assets/partials-agents/06-quality-safety.partial.md +11 -16
  104. package/assets/partials-agents/08-deep-docs.partial.md +1 -2
  105. package/assets/plan-map.json +11 -11
  106. package/assets/plugin-registry.v1.json +9 -3
  107. package/assets/plugin-trust-policy.v1.json +6 -0
  108. package/assets/recovery/ForgeRecovery.ps1 +100 -0
  109. package/assets/recovery/forge-wsl-rescue +271 -0
  110. package/assets/reference-configs/development-protocol.md +11 -15
  111. package/assets/reference-configs/document-generation.md +1 -1
  112. package/assets/reference-configs/global-working-rules.md +9 -1
  113. package/assets/reference-configs/harness-overview.md +22 -112
  114. package/assets/reference-configs/hook-operations.md +1 -1
  115. package/assets/reference-configs/sprint-contracts.md +2 -2
  116. package/assets/skill-version.json +2 -2
  117. package/assets/templates/helpers/architecture-event.ts +3 -34
  118. package/assets/templates/helpers/architecture-queue.sh +61 -10
  119. package/assets/templates/helpers/archive-workflow.sh +90 -180
  120. package/assets/templates/helpers/capability-config.ts +8 -11
  121. package/assets/templates/helpers/capability-resolver.ts +37 -19
  122. package/assets/templates/helpers/capture-plan.sh +2 -2
  123. package/assets/templates/helpers/check-architecture-sync.sh +1 -9
  124. package/assets/templates/helpers/check-task-sync.sh +82 -11
  125. package/assets/templates/helpers/check-task-workflow.sh +1 -3
  126. package/assets/templates/helpers/context-contract-sync.sh +1 -12
  127. package/assets/templates/helpers/contract-worktree.sh +26 -24
  128. package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
  129. package/assets/templates/helpers/inspect-project-state.ts +1 -1
  130. package/assets/templates/helpers/migrate-project-template.sh +1 -1
  131. package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
  132. package/assets/templates/helpers/new-plan.sh +2 -2
  133. package/assets/templates/helpers/plan-to-todo.sh +3 -31
  134. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  135. package/assets/templates/helpers/switch-plan.sh +1 -1
  136. package/assets/templates/helpers/workflow-contract.ts +1 -0
  137. package/assets/workflow-contract.v1.json +63 -171
  138. package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
  139. package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
  140. package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
  141. package/docs/architecture/CURRENT.md +148 -13
  142. package/docs/architecture/EVOLUTION.md +13 -0
  143. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
  144. package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
  145. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
  146. package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
  147. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +35 -0
  148. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +296 -0
  149. package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
  150. package/docs/architecture/domains/verification.md +7 -0
  151. package/docs/architecture/domains/workflow-engine.md +1 -1
  152. package/docs/architecture/index.md +3 -3
  153. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
  154. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
  155. package/docs/forge-chatgpt-mcp-setup.md +5 -3
  156. package/docs/forge-plugin-management.md +20 -4
  157. package/docs/forge-runtime-self-healing-loop.md +2 -0
  158. package/docs/operations/benchmark-cross-version-freeze.md +38 -0
  159. package/docs/operations/controller-browser-plugin.md +3 -1
  160. package/docs/operations/homebrew.md +2 -2
  161. package/docs/operations/platform-support.md +16 -8
  162. package/docs/operations/platform-support.zh-CN.md +2 -2
  163. package/docs/operations/standalone-disaster-recovery.md +26 -1
  164. package/docs/operations/uu-remote-rescue.md +41 -0
  165. package/docs/operations/windows-wsl-host-rescue.md +133 -0
  166. package/docs/public-usage-guide.md +1 -1
  167. package/docs/public-usage-guide.zh-CN.md +1 -1
  168. package/docs/tutorials/01-install-and-start.md +1 -1
  169. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  170. package/docs/tutorials/02-connect-chatgpt.md +4 -4
  171. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
  172. package/docs/wiki/Implementation.md +1 -1
  173. package/package.json +23 -14
  174. package/packages/kernel/controller/api/index.ts +2 -0
  175. package/packages/kernel/controller/application/controller-round-service.ts +29 -0
  176. package/packages/kernel/controller/application/controller-service.ts +30 -0
  177. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +308 -0
  178. package/packages/kernel/controller/domain/controller-round.ts +107 -0
  179. package/packages/kernel/controller/domain/execution-quality.ts +133 -0
  180. package/packages/kernel/controller/domain/types.ts +56 -0
  181. package/packages/kernel/controller/index.ts +1 -0
  182. package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
  183. package/packages/kernel/controller/infrastructure/controller-round-store.ts +1384 -0
  184. package/packages/kernel/controller/infrastructure/controller-session-store.ts +578 -0
  185. package/packages/kernel/controller/ports/controller-host.ts +16 -0
  186. package/packages/kernel/goal/api/index.ts +1 -0
  187. package/packages/kernel/goal/domain/types.ts +40 -0
  188. package/packages/kernel/goal/index.ts +1 -0
  189. package/packages/kernel/identity/api/index.ts +5 -0
  190. package/packages/kernel/identity/application/identity-service.ts +67 -0
  191. package/packages/kernel/identity/domain/scope.ts +63 -0
  192. package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
  193. package/packages/kernel/identity/domain/types.ts +44 -0
  194. package/packages/kernel/identity/domain/workspace.ts +56 -0
  195. package/packages/kernel/identity/index.ts +1 -0
  196. package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
  197. package/packages/kernel/memory/api/index.ts +5 -0
  198. package/packages/kernel/memory/application/experience-service.ts +118 -0
  199. package/packages/kernel/memory/application/outcome-service.ts +77 -0
  200. package/packages/kernel/memory/domain/experience.ts +103 -0
  201. package/packages/kernel/memory/domain/operational-prior.ts +92 -0
  202. package/packages/kernel/memory/index.ts +1 -0
  203. package/packages/kernel/progression/api/index.ts +2 -0
  204. package/packages/kernel/progression/application/projector.ts +211 -0
  205. package/packages/kernel/progression/domain/types.ts +116 -0
  206. package/packages/kernel/progression/index.ts +1 -0
  207. package/packages/kernel/scheduler/api/index.ts +6 -0
  208. package/packages/kernel/scheduler/application/continuation-service.ts +212 -0
  209. package/packages/kernel/scheduler/application/eligibility.ts +252 -0
  210. package/packages/kernel/scheduler/application/schedule-service.ts +24 -0
  211. package/packages/kernel/scheduler/application/settlement.ts +197 -0
  212. package/packages/kernel/scheduler/domain/continuation.ts +38 -0
  213. package/packages/kernel/scheduler/domain/schedule.ts +149 -0
  214. package/packages/kernel/scheduler/index.ts +1 -0
  215. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +38 -0
  216. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +785 -0
  217. package/packages/kernel/work/api/index.ts +15 -0
  218. package/packages/kernel/work/application/work-service.ts +148 -0
  219. package/packages/kernel/work/domain/admission-policy.ts +34 -0
  220. package/packages/kernel/work/domain/check-receipt.ts +43 -0
  221. package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
  222. package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
  223. package/packages/kernel/work/domain/engineering-design.ts +281 -0
  224. package/packages/kernel/work/domain/engineering-profile.ts +242 -0
  225. package/packages/kernel/work/domain/execution-concurrency.ts +254 -0
  226. package/packages/kernel/work/domain/execution-snapshot.ts +33 -0
  227. package/packages/kernel/work/domain/implementation-review.ts +510 -0
  228. package/packages/kernel/work/domain/project-engineering-contract.ts +202 -0
  229. package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
  230. package/packages/kernel/work/domain/state-machine.ts +259 -0
  231. package/packages/kernel/work/domain/types.ts +528 -0
  232. package/packages/kernel/work/index.ts +1 -0
  233. package/packages/kernel/work/infrastructure/work-contract-store.ts +1876 -0
  234. package/packages/kernel/work/ports/work-contract-store.ts +18 -0
  235. package/packages/plugin-runtime/browser/index.ts +3 -0
  236. package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
  237. package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
  238. package/packages/plugin-runtime/browser/session-authority.ts +61 -0
  239. package/packages/plugin-runtime/computer/index.ts +6 -0
  240. package/packages/plugin-runtime/computer/provider-error.ts +26 -0
  241. package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
  242. package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
  243. package/packages/plugin-runtime/computer/provider.ts +19 -0
  244. package/packages/plugin-runtime/computer/target-authority.ts +190 -0
  245. package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
  246. package/packages/plugin-runtime/external/index.ts +1 -0
  247. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +457 -0
  248. package/packages/plugin-runtime/index.ts +3 -0
  249. package/packages/protocols/browser/index.ts +1 -0
  250. package/packages/protocols/browser/session.ts +98 -0
  251. package/packages/protocols/computer/contract.ts +199 -0
  252. package/packages/protocols/computer/index.ts +1 -0
  253. package/packages/protocols/failure/contract.ts +158 -0
  254. package/packages/protocols/failure/index.ts +1 -0
  255. package/packages/protocols/handoff/index.ts +1 -0
  256. package/packages/protocols/handoff/status.ts +22 -0
  257. package/packages/protocols/mcp/execution-context.ts +11 -0
  258. package/packages/protocols/mcp/index.ts +2 -0
  259. package/packages/protocols/mcp/tool-contract.ts +42 -0
  260. package/packages/workflow-runtime/api/index.ts +2 -0
  261. package/packages/workflow-runtime/application/interpreter.ts +342 -0
  262. package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
  263. package/packages/workflow-runtime/index.ts +1 -0
  264. package/scripts/assemble-template.ts +1 -1
  265. package/scripts/benchmark-computer-hotpath.ts +255 -0
  266. package/scripts/benchmark-controller-round2.ts +54 -2
  267. package/scripts/benchmark-ios-agent-device.ts +3 -6
  268. package/scripts/benchmark-mcp-transport.ts +3 -4
  269. package/scripts/benchmark-operational-memory-activation.ts +235 -0
  270. package/scripts/benchmark-operational-shadow.ts +28 -0
  271. package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
  272. package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
  273. package/scripts/canary-controller-semantic-wait.ts +195 -0
  274. package/scripts/canary-global-hook.sh +2 -2
  275. package/scripts/check-mcp-compatibility.ts +173 -7
  276. package/scripts/check-platform-support.mjs +28 -2
  277. package/scripts/check-public-docs.sh +1 -1
  278. package/scripts/check-release-readiness.sh +1 -1
  279. package/scripts/check-repository-hygiene.mjs +92 -0
  280. package/scripts/check-runtime-architecture.mjs +1206 -95
  281. package/scripts/check-source-duplication.mjs +9 -1
  282. package/scripts/check-tarball-install-smoke.sh +98 -15
  283. package/scripts/controller-ngrok-rotation.sh +5 -4
  284. package/scripts/create-project-dirs.sh +10 -74
  285. package/scripts/forge-local-recovery-helper.mjs +204 -0
  286. package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
  287. package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
  288. package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
  289. package/scripts/hook-shim.sh +2 -2
  290. package/scripts/init-project.sh +12 -70
  291. package/scripts/install-desktop-operator-registration.ts +4 -2
  292. package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
  293. package/scripts/install-standalone-recovery.ts +122 -10
  294. package/scripts/install-uu-remote-rescue-registration.ts +74 -0
  295. package/scripts/install-windows-host-recovery-registration.ts +22 -0
  296. package/scripts/lib/controller-home-env.sh +6 -3
  297. package/scripts/lib/project-init-lib.sh +39 -582
  298. package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
  299. package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
  300. package/scripts/open-source-audit-allowlist.txt +12 -1
  301. package/scripts/portability-bindings.mjs +70 -0
  302. package/scripts/run-bounded-command.mjs +55 -0
  303. package/scripts/run-bun-test-file.ts +22 -6
  304. package/scripts/run-governed-gate.ts +4 -1
  305. package/scripts/smoke-mcp-http-runtime.ts +1 -1
  306. package/scripts/smoke-runtime-recovery.ts +34 -1
  307. package/scripts/smoke-schedule-engine.ts +1 -1
  308. package/scripts/stage-runtime-release.ts +6 -1
  309. package/scripts/test-governance.ts +51 -2
  310. package/src/cli/agent-jobs/job-manager.ts +10 -1
  311. package/src/cli/chatgpt-browser/bind-server.ts +2 -1
  312. package/src/cli/chatgpt-browser/binding.ts +20 -10
  313. package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
  314. package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
  315. package/src/cli/chatgpt-browser/engine.ts +10 -11
  316. package/src/cli/chatgpt-browser/native-provider.ts +24 -83
  317. package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
  318. package/src/cli/chatgpt-browser/types.ts +1 -0
  319. package/src/cli/commands/bootstrap-control.ts +334 -0
  320. package/src/cli/commands/brain-assistant.ts +38 -0
  321. package/src/cli/commands/brain.ts +193 -40
  322. package/src/cli/commands/chatgpt.ts +127 -0
  323. package/src/cli/commands/computer.ts +437 -0
  324. package/src/cli/commands/doctor.ts +18 -1
  325. package/src/cli/commands/init-hook.ts +71 -61
  326. package/src/cli/commands/init.ts +3 -1
  327. package/src/cli/commands/mcp.ts +16 -10
  328. package/src/cli/commands/plugin.ts +38 -12
  329. package/src/cli/commands/recovery.ts +493 -80
  330. package/src/cli/commands/runtime.ts +9 -4
  331. package/src/cli/commands/setup-profile.ts +89 -39
  332. package/src/cli/commands/status.ts +2 -1
  333. package/src/cli/controller/check-runner.ts +487 -98
  334. package/src/cli/controller/context/types.ts +7 -1
  335. package/src/cli/controller/context-pack.ts +23 -3
  336. package/src/cli/controller/issue-store.ts +1 -1
  337. package/src/cli/controller/systemd-user.ts +110 -0
  338. package/src/cli/controller/types.ts +10 -70
  339. package/src/cli/controller/worklog.ts +67 -13
  340. package/src/cli/editing/edit-operation-contract.ts +92 -0
  341. package/src/cli/editing/edit-session.ts +289 -13
  342. package/src/cli/github/plugin.ts +17 -12
  343. package/src/cli/hook/route-registry.ts +3 -5
  344. package/src/cli/hook/runtime.ts +49 -3
  345. package/src/cli/index.ts +3 -0
  346. package/src/cli/local-bridge/connector-freshness.ts +2 -2
  347. package/src/cli/local-bridge/console-automations.ts +2 -2
  348. package/src/cli/local-bridge/facade-api.ts +41 -14
  349. package/src/cli/local-bridge/job-store.ts +1 -1
  350. package/src/cli/local-bridge/server.ts +24 -27
  351. package/src/cli/mcp/access-mode.ts +2 -132
  352. package/src/cli/mcp/access-tools.ts +2 -387
  353. package/src/cli/mcp/audit.ts +2 -32
  354. package/src/cli/mcp/auth.ts +2 -404
  355. package/src/cli/mcp/instructions.ts +2 -34
  356. package/src/cli/mcp/legacy-context.ts +2 -16
  357. package/src/cli/mcp/legacy-tool-service.ts +2 -5308
  358. package/src/cli/mcp/multi-repository.ts +2 -436
  359. package/src/cli/mcp/oauth.ts +2 -375
  360. package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
  361. package/src/cli/mcp/paths.ts +2 -140
  362. package/src/cli/mcp/policy.ts +2 -232
  363. package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
  364. package/src/cli/mcp/proxy-env.ts +2 -62
  365. package/src/cli/mcp/readiness-projection.ts +2 -24
  366. package/src/cli/mcp/redaction.ts +2 -21
  367. package/src/cli/mcp/repo.ts +2 -24
  368. package/src/cli/mcp/repository-tools.ts +2 -1613
  369. package/src/cli/mcp/server.ts +2 -951
  370. package/src/cli/mcp/setup.ts +2 -737
  371. package/src/cli/mcp/tool-capability-cache.ts +2 -87
  372. package/src/cli/mcp/tool-contract.ts +2 -14
  373. package/src/cli/mcp/tools.ts +2 -9
  374. package/src/cli/mcp/toolset-names.ts +2 -70
  375. package/src/cli/mcp/toolset-selection.ts +2 -41
  376. package/src/cli/mcp/toolset.ts +2 -336
  377. package/src/cli/mcp/transports/AGENTS.md +1 -10
  378. package/src/cli/mcp/transports/CLAUDE.md +1 -10
  379. package/src/cli/mcp/transports/http.ts +2 -1414
  380. package/src/cli/mcp/transports/session-registry.ts +2 -490
  381. package/src/cli/mcp/transports/stdio.ts +2 -8
  382. package/src/cli/mcp/types.ts +2 -41
  383. package/src/cli/mcp/umbrella-tools.ts +2 -117
  384. package/src/cli/repositories/command-executor.ts +64 -18
  385. package/src/cli/repositories/command-scope.ts +117 -1
  386. package/src/cli/repositories/controller-home.ts +119 -1
  387. package/src/cli/repositories/identity.ts +13 -0
  388. package/src/cli/repositories/registry.ts +68 -49
  389. package/src/cli/repositories/repository-snapshot.ts +53 -7
  390. package/src/cli/repositories/runtime-storage.ts +27 -2
  391. package/src/cli/repositories/safe-patch.ts +34 -38
  392. package/src/cli/repositories/selected-path-actions.ts +22 -2
  393. package/src/cli/repositories/structured-git.ts +105 -25
  394. package/src/cli/runtime/helper-runner.ts +7 -0
  395. package/src/cli/runtime-invocation.ts +8 -1
  396. package/src/cli/tools/codegraph.ts +44 -25
  397. package/src/core/adoption/manifest-templates.ts +1 -1
  398. package/src/core/adoption/plan.ts +3 -26
  399. package/src/core/adoption/workflow-contract-plan.ts +17 -9
  400. package/src/effects/process-runner.ts +8 -14
  401. package/src/runtime/context/assistant-context.ts +154 -0
  402. package/src/runtime/context/assistant-work-context.ts +147 -0
  403. package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
  404. package/src/runtime/context/codegraph-read-provider.ts +55 -22
  405. package/src/runtime/context/codegraph-sidecar.cjs +8 -1
  406. package/src/runtime/context/context-closure.ts +268 -0
  407. package/src/runtime/context/context-plane.ts +211 -0
  408. package/src/runtime/context/project-engineering-contract.ts +60 -0
  409. package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
  410. package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
  411. package/src/runtime/control-plane/bootstrap/index.ts +5 -0
  412. package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
  413. package/src/runtime/control-plane/bootstrap/store.ts +21 -0
  414. package/src/runtime/control-plane/bootstrap/types.ts +87 -0
  415. package/src/runtime/control-plane/cleanup-artifact-retention.ts +328 -0
  416. package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
  417. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +320 -0
  418. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +169 -0
  419. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +350 -0
  420. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +85 -0
  421. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +369 -99
  422. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +103 -15
  423. package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
  424. package/src/runtime/control-plane/execution/repository-work-attribution.ts +2 -2
  425. package/src/runtime/control-plane/execution/retained-work-resume.ts +336 -6
  426. package/src/runtime/control-plane/execution/validation.ts +28 -3
  427. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +116 -0
  428. package/src/runtime/control-plane/execution/work-completion-authority.ts +44 -2
  429. package/src/runtime/control-plane/execution/work-evidence-policy.ts +34 -3
  430. package/src/runtime/control-plane/execution/work-execution-support.ts +42 -22
  431. package/src/runtime/control-plane/execution/work-finalization-service.ts +1103 -166
  432. package/src/runtime/control-plane/execution/work-handle-authority.ts +219 -22
  433. package/src/runtime/control-plane/execution/work-handle-store.ts +77 -0
  434. package/src/runtime/control-plane/execution/work-head-settlement.ts +75 -0
  435. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +145 -23
  436. package/src/runtime/control-plane/execution/work-operation-service.ts +49 -13
  437. package/src/runtime/control-plane/execution/work-preparation-service.ts +31 -15
  438. package/src/runtime/control-plane/execution/work-remote-delivery.ts +1 -1
  439. package/src/runtime/control-plane/execution/work-task-receipt.ts +20 -1
  440. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +407 -76
  441. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +88 -18
  442. package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
  443. package/src/runtime/control-plane/execution/work-verification-service.ts +258 -4
  444. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +200 -15
  445. package/src/runtime/control-plane/facade/capability-registry.ts +21 -10
  446. package/src/runtime/control-plane/facade/controller-round-relay.ts +3 -1118
  447. package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
  448. package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -3
  449. package/src/runtime/control-plane/facade/goal-workloop.ts +972 -137
  450. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +158 -0
  451. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +10 -0
  452. package/src/runtime/control-plane/facade/index.ts +4 -2
  453. package/src/runtime/control-plane/facade/operation-digest.ts +22 -58
  454. package/src/runtime/control-plane/facade/plan-contract-store.ts +1004 -38
  455. package/src/runtime/control-plane/facade/repository-work-admission.ts +19 -1
  456. package/src/runtime/control-plane/facade/requirement-authority.ts +218 -0
  457. package/src/runtime/control-plane/facade/requirement-board.ts +6 -1
  458. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +48 -0
  459. package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
  460. package/src/runtime/control-plane/facade/suggested-actions.ts +4 -1
  461. package/src/runtime/control-plane/facade/types.ts +189 -532
  462. package/src/runtime/control-plane/facade/work-admission-policy.ts +84 -35
  463. package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
  464. package/src/runtime/control-plane/facade/work-implementation-review.ts +2 -0
  465. package/src/runtime/control-plane/facade/work-state-machine.ts +2 -250
  466. package/src/runtime/control-plane/generated-cache-retention.ts +239 -18
  467. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +12 -1
  468. package/src/runtime/control-plane/global-scheduler/maintenance.ts +76 -13
  469. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
  470. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
  471. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -1
  472. package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
  473. package/src/runtime/control-plane/global-scheduler/scheduler.ts +173 -81
  474. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
  475. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +397 -0
  476. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
  477. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +301 -560
  478. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +20 -4
  479. package/src/runtime/control-plane/launcher/thin-launcher.ts +50 -4
  480. package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
  481. package/src/runtime/control-plane/persistence/experience-store.ts +169 -0
  482. package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
  483. package/src/runtime/control-plane/persistence/requirement-store.ts +30 -22
  484. package/src/runtime/control-plane/persistence/sqlite-store.ts +545 -76
  485. package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
  486. package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
  487. package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
  488. package/src/runtime/control-plane/release-retention.ts +56 -16
  489. package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
  490. package/src/runtime/control-plane/routing/route-policy.ts +19 -0
  491. package/src/runtime/control-plane/runtime-cleanup.ts +574 -37
  492. package/src/runtime/control-plane/runtime-generation.ts +6 -0
  493. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +465 -0
  494. package/src/runtime/control-plane/workspace/workspace-store.ts +80 -0
  495. package/src/runtime/diagnostics/incident-repair.ts +406 -0
  496. package/src/runtime/diagnostics/mcp-timing.ts +125 -6
  497. package/src/runtime/diagnostics/performance.ts +6 -14
  498. package/src/runtime/diagnostics/read-only-tool.ts +1 -1
  499. package/src/runtime/evidence/artifact-store.ts +65 -2
  500. package/src/runtime/evidence/operational-shadow.ts +38 -0
  501. package/src/runtime/evidence/process-check-receipt.ts +5 -43
  502. package/src/runtime/evidence/sensitive-output.ts +4 -4
  503. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
  504. package/src/runtime/execution/jobs/store.ts +121 -2
  505. package/src/runtime/execution/managed-workspace.ts +55 -5
  506. package/src/runtime/execution/process-runtime/canary.ts +19 -5
  507. package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
  508. package/src/runtime/execution/process-runtime/check-facade.ts +23 -9
  509. package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
  510. package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
  511. package/src/runtime/execution/process-runtime/check-result.ts +73 -2
  512. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -3
  513. package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
  514. package/src/runtime/execution/process-runtime/command-facade.ts +38 -87
  515. package/src/runtime/execution/process-runtime/index.ts +1 -0
  516. package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
  517. package/src/runtime/execution/process-runtime/lightweight-managed.ts +32 -1
  518. package/src/runtime/execution/process-runtime/persisted-check.ts +128 -21
  519. package/src/runtime/execution/process-runtime/resource-claims.ts +60 -3
  520. package/src/runtime/execution/process-runtime/runtime.ts +132 -15
  521. package/src/runtime/execution/process-runtime/types.ts +22 -0
  522. package/src/runtime/execution/workers/executor.ts +16 -13
  523. package/src/runtime/execution/workers/worker-entry.ts +1 -1
  524. package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
  525. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
  526. package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
  527. package/src/runtime/gateway/mcp/process-tools.ts +2 -289
  528. package/src/runtime/gateway/mcp/router.ts +2 -1125
  529. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
  530. package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
  531. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
  532. package/src/runtime/maintenance/cleanup.ts +38 -24
  533. package/src/runtime/platform/browser-product-discovery.ts +174 -0
  534. package/src/runtime/platform/computer-platform.ts +14 -0
  535. package/src/runtime/platform/executable-discovery.ts +54 -0
  536. package/src/runtime/platform/fetch-http-server.ts +90 -0
  537. package/src/runtime/platform/service-manager.ts +138 -0
  538. package/src/runtime/platform/windows-host-discovery.ts +118 -0
  539. package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
  540. package/src/runtime/plugins/action-application.ts +135 -0
  541. package/src/runtime/plugins/app-store-connect-adapter.ts +9 -21
  542. package/src/runtime/plugins/browser-adapter.ts +140 -100
  543. package/src/runtime/plugins/browser-automation-service.ts +15 -36
  544. package/src/runtime/plugins/browser-macos-bridge.ts +69 -21
  545. package/src/runtime/plugins/browser-manifest-surface.ts +30 -6
  546. package/src/runtime/plugins/browser-native-session-service.ts +1 -1
  547. package/src/runtime/plugins/browser-provider-registry.ts +2 -109
  548. package/src/runtime/plugins/browser-registration.ts +16 -0
  549. package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
  550. package/src/runtime/plugins/browser-runtime.ts +41 -9
  551. package/src/runtime/plugins/browser-session-authority.ts +101 -291
  552. package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
  553. package/src/runtime/plugins/browser-session-store.ts +229 -36
  554. package/src/runtime/plugins/browser-session-types.ts +2 -98
  555. package/src/runtime/plugins/capability-authorization-grants.ts +17 -0
  556. package/src/runtime/plugins/catalog-trust.ts +82 -0
  557. package/src/runtime/plugins/computer-registration.ts +839 -0
  558. package/src/runtime/plugins/config-store.ts +119 -0
  559. package/src/runtime/plugins/desktop-operator-external-policy.ts +932 -0
  560. package/src/runtime/plugins/desktop-operator-registration.ts +111 -8
  561. package/src/runtime/plugins/errors.ts +8 -1
  562. package/src/runtime/plugins/external-adapter.ts +45 -322
  563. package/src/runtime/plugins/external-provider-policy.ts +48 -0
  564. package/src/runtime/plugins/external-registration.ts +9 -1
  565. package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
  566. package/src/runtime/plugins/external-unix-socket.ts +64 -193
  567. package/src/runtime/plugins/first-party-external-registration.ts +163 -0
  568. package/src/runtime/plugins/first-party-registry.ts +3 -1
  569. package/src/runtime/plugins/github-adapter.ts +1 -2
  570. package/src/runtime/plugins/gmail-adapter.ts +6 -6
  571. package/src/runtime/plugins/google-calendar-adapter.ts +6 -6
  572. package/src/runtime/plugins/google-shared.ts +31 -46
  573. package/src/runtime/plugins/google-tasks-adapter.ts +6 -6
  574. package/src/runtime/plugins/ios-adapter.ts +1 -2
  575. package/src/runtime/plugins/local-system-adapter.ts +180 -0
  576. package/src/runtime/plugins/macos-capability-broker.ts +15 -105
  577. package/src/runtime/plugins/resend-adapter.ts +15 -24
  578. package/src/runtime/plugins/store.ts +493 -51
  579. package/src/runtime/plugins/types.ts +8 -1
  580. package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
  581. package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
  582. package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
  583. package/src/runtime/plugins/xiaohongshu-publish.ts +86 -430
  584. package/src/runtime/projections/controller-context.ts +12 -6
  585. package/src/runtime/projections/git-status-sampler.ts +73 -22
  586. package/src/runtime/projections/materialized-view.ts +14 -8
  587. package/src/runtime/recovery/capability-action-application.ts +228 -0
  588. package/src/runtime/recovery/index.ts +3 -0
  589. package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
  590. package/src/runtime/recovery/local-jobs-repair.ts +14 -14
  591. package/src/runtime/recovery/maintenance-executor.ts +358 -76
  592. package/src/runtime/recovery/quarantine-retention.ts +256 -0
  593. package/src/runtime/release/identity.ts +6 -0
  594. package/src/runtime/resources/leases/store.ts +1 -1
  595. package/src/runtime/root/active-execution-power-assertion.ts +139 -0
  596. package/src/runtime/root/assistant-learning-loop.ts +45 -0
  597. package/src/runtime/root/browser-session-composition.ts +59 -0
  598. package/src/runtime/root/computer-composition.ts +191 -0
  599. package/src/runtime/root/computer-target-composition.ts +16 -0
  600. package/src/runtime/root/computer-target-persistence.ts +39 -0
  601. package/src/runtime/root/controller-round-composition.ts +107 -0
  602. package/src/runtime/root/controller-services.ts +2 -0
  603. package/src/runtime/root/entry.ts +4 -3
  604. package/src/runtime/root/gateway-adapter.ts +3 -3
  605. package/src/runtime/root/local-bridge.ts +5 -2
  606. package/src/runtime/root/mcp-transport.ts +5 -5
  607. package/src/runtime/root/package-connector-service.ts +108 -56
  608. package/src/runtime/root/package-runtime-release.ts +85 -18
  609. package/src/runtime/root/package-runtime-service.ts +60 -97
  610. package/src/runtime/root/release-execution-canary.ts +71 -0
  611. package/src/runtime/root/release-loader.ts +68 -0
  612. package/src/runtime/root/release-manifest.ts +106 -2
  613. package/src/runtime/root/release-materialize.ts +103 -4
  614. package/src/runtime/root/release-store.ts +31 -2
  615. package/src/runtime/root/runtime.ts +240 -25
  616. package/src/runtime/root/scheduled-controller-composition.ts +51 -0
  617. package/src/runtime/root/scheduler.ts +12 -4
  618. package/src/runtime/root/service-runner.ts +1 -1
  619. package/src/runtime/root/service.ts +41 -16
  620. package/src/runtime/root/status.ts +1 -0
  621. package/src/runtime/root/types.ts +24 -1
  622. package/src/runtime/shared/local-bridge-surface.ts +1 -1
  623. package/src/runtime/shared/process-environment.ts +19 -4
  624. package/src/runtime/shared/storage-capacity.ts +68 -0
  625. package/src/runtime/standalone-recovery/controller-home-migration.ts +1116 -0
  626. package/src/runtime/standalone-recovery/core.ts +984 -230
  627. package/src/runtime/standalone-recovery/entry.ts +182 -51
  628. package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
  629. package/src/runtime/standalone-recovery/installer.ts +212 -104
  630. package/src/runtime/standalone-recovery/mcp-server.ts +243 -0
  631. package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
  632. package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
  633. package/src/runtime/standalone-recovery/performance.ts +135 -0
  634. package/src/runtime/standalone-recovery/release.ts +5 -0
  635. package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
  636. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -1
  637. package/src/runtime/workflow/schedules/engine.ts +191 -343
  638. package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
  639. package/src/runtime/workflow/schedules/settlement.ts +2 -191
  640. package/src/runtime/workflow/schedules/store.ts +2 -607
  641. package/src/runtime/workflow/schedules/types.ts +2 -127
  642. package/src/runtime/workflow/schedules/work-continuation.ts +180 -15
  643. package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
  644. package/src/runtime/workflows/runtime.ts +486 -0
  645. package/src/testing/test-governance.ts +90 -29
  646. package/assets/templates/helpers/archive-architecture-request.sh +0 -244
  647. package/docs/architecture/history/README.md +0 -7
  648. package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
  649. package/docs/architecture/history/global-hook-runtime.md +0 -166
  650. package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
  651. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  652. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
  653. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
  654. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
  655. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
  656. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  657. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  658. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  659. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  660. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
  661. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  662. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  663. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  664. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  665. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  666. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  667. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  668. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  669. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  670. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
  671. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
  672. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
  673. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  674. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  675. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  676. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  677. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
  678. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
  679. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
  680. package/docs/architecture/requests/archive/2026/root.md +0 -68
  681. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
  682. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
  683. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
  684. package/scripts/archive-architecture-request.sh +0 -4
  685. package/scripts/run-skill-evals.ts +0 -1022
@@ -1,9 +1,22 @@
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
22
  RECOVERY_GATEWAY_LABEL,
@@ -16,11 +29,16 @@ import {
16
29
  } from '../../runtime/standalone-recovery/installer';
17
30
  import {
18
31
  RECOVERY_TOOLS,
32
+ RECOVERY_VERIFIER_OAUTH_CLIENT_ID,
33
+ RECOVERY_VERIFIER_OAUTH_CLIENT_NAME,
34
+ RECOVERY_VERIFIER_OAUTH_REDIRECT_URI,
19
35
  } from '../../runtime/standalone-recovery/entry';
20
36
  import {
21
37
  activateRuntimeRelease,
38
+ defaultPrimaryRuntimeServiceConfig,
22
39
  loadRecoveryConfig,
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,80 @@ 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
152
  launchdPid?: (role: 'gateway' | 'watchdog') => number | undefined;
153
+ systemdPid?: (role: 'gateway' | 'watchdog') => 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;
63
158
  }
64
159
 
65
160
  export interface RecoveryConnectorDescriptor {
66
161
  name: 'Forge Recovery';
162
+ identity: RecoveryMachineIdentity;
67
163
  transport: 'streamable_http';
68
164
  url: string;
69
165
  public: boolean;
@@ -78,9 +174,23 @@ export interface RecoveryConnectorDescriptor {
78
174
  };
79
175
  healthUrl: string;
80
176
  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 };
177
+ gateway: { label: string; platform: 'launchd' | 'systemd-user'; serviceInstalled: boolean; plistInstalled: boolean; running: boolean; pid?: number };
178
+ watchdog: { label: string; platform: 'launchd' | 'systemd-user'; serviceInstalled: boolean; plistInstalled: boolean; running: boolean; pid?: number };
179
+ tunnel: {
180
+ configured: boolean;
181
+ platform?: 'launchd' | 'systemd-user' | 'openai-secure-tunnel';
182
+ label?: string;
183
+ unitName?: string;
184
+ alias?: string;
185
+ tunnelId?: string;
186
+ serviceInstalled?: boolean;
187
+ plistInstalled: boolean;
188
+ restartSafe: boolean;
189
+ running: boolean;
190
+ healthy?: boolean;
191
+ ready?: boolean;
192
+ pid?: number;
193
+ };
84
194
  };
85
195
  tools: string[];
86
196
  warnings: string[];
@@ -100,25 +210,66 @@ export function recoveryConnectorDescriptor(
100
210
  const passphraseConfigured = Boolean(readMcpServiceOAuthPassphrase(home));
101
211
  const gatewayIdentity = readRecoveryRuntimeIdentity(home, 'gateway');
102
212
  const watchdogIdentity = readRecoveryRuntimeIdentity(home, 'watchdog');
213
+ const platform = dependencies.platform ?? process.platform;
214
+ const servicePlatform: 'launchd' | 'systemd-user' = platform === 'linux' ? 'systemd-user' : 'launchd';
103
215
  const pathExists = dependencies.pathExists ?? existsSync;
104
216
  const launchdPid = dependencies.launchdPid ?? recoveryLaunchdPid;
217
+ const systemdPid = dependencies.systemdPid ?? ((role: 'gateway' | 'watchdog') => systemdUserServicePid(role === 'gateway' ? RECOVERY_GATEWAY_LABEL : RECOVERY_WATCHDOG_LABEL));
218
+ const managedPid = (role: 'gateway' | 'watchdog') => servicePlatform === 'systemd-user' ? systemdPid(role) : launchdPid(role);
105
219
  const processAlive = dependencies.processAlive ?? isProcessAlive;
106
- const tunnelService = config.recoveryTunnelService?.platform === 'launchd' ? config.recoveryTunnelService : undefined;
220
+ const configuredTunnel = config.recoveryTunnelService;
221
+ const tunnelService = configuredTunnel?.platform === 'launchd' ? configuredTunnel : undefined;
222
+ const systemdTunnelService = configuredTunnel?.platform === 'systemd-user' ? configuredTunnel : undefined;
223
+ const openAiTunnelService = configuredTunnel?.platform === 'openai-secure-tunnel' ? configuredTunnel : undefined;
107
224
  const tunnelContract = tunnelService ? inspectRecoveryTunnelLaunchdContract(tunnelService) : undefined;
108
225
  const tunnelLaunchdPid = tunnelService
109
226
  ? (dependencies.tunnelLaunchdPid ?? recoveryLaunchdServicePid)(tunnelService.label)
110
227
  : undefined;
111
228
  const tunnelPlistInstalled = Boolean(tunnelContract?.plistPath && pathExists(tunnelContract.plistPath));
112
- const tunnelRestartSafe = Boolean(tunnelContract?.restartSafe);
113
- const tunnelRunning = Boolean(tunnelLaunchdPid && processAlive(tunnelLaunchdPid));
229
+ const systemdTunnelUnitName = systemdTunnelService?.unitName.trim();
230
+ const systemdTunnelIdentityValid = Boolean(systemdTunnelUnitName && /^[A-Za-z0-9_.@-]{1,180}\.service$/.test(systemdTunnelUnitName));
231
+ const systemdTunnelInstalled = Boolean(systemdTunnelIdentityValid && pathExists(systemdUserUnitPath(systemdTunnelUnitName!)));
232
+ const systemdTunnelPid = systemdTunnelIdentityValid
233
+ ? (dependencies.tunnelSystemdPid ?? ((unitName: string) => systemdUserServicePid(unitName)))(systemdTunnelUnitName!)
234
+ : undefined;
235
+ const openAiTunnelStatus = openAiTunnelService
236
+ ? (dependencies.openAiTunnelStatus ?? ((service: OpenAiSecureTunnelServiceConfig) => {
237
+ const status = spawnSync('tunnel-client', openAiSecureTunnelStatusArgs(service.alias, service), {
238
+ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15_000,
239
+ });
240
+ return parseOpenAiSecureTunnelRuntimeStatus(status.stdout || '{}', { alias: service.alias, tunnelId: service.tunnelId, mcpServerUrl: service.mcpServerUrl });
241
+ }))(openAiTunnelService)
242
+ : undefined;
243
+ let openAiRestartSafe = false;
244
+ if (openAiTunnelService) {
245
+ try { openAiSecureTunnelConnectArgs(openAiTunnelService); openAiRestartSafe = true; } catch { openAiRestartSafe = false; }
246
+ }
247
+ const tunnelRestartSafe = openAiTunnelService
248
+ ? openAiRestartSafe
249
+ : systemdTunnelService
250
+ ? systemdTunnelIdentityValid && systemdTunnelInstalled
251
+ : Boolean(tunnelContract?.restartSafe);
252
+ const tunnelRunning = openAiTunnelService
253
+ ? openAiTunnelStatus?.running === true
254
+ : systemdTunnelService
255
+ ? Boolean(systemdTunnelPid && processAlive(systemdTunnelPid))
256
+ : Boolean(tunnelLaunchdPid && processAlive(tunnelLaunchdPid));
257
+ const tunnelHealthy = openAiTunnelService ? openAiTunnelStatus?.healthy === true : tunnelRunning;
258
+ const tunnelReady = openAiTunnelService ? openAiTunnelStatus?.ok === true : tunnelRestartSafe && tunnelRunning;
114
259
  const gatewayPlistInstalled = pathExists(authority.gatewayLaunchAgent);
115
260
  const watchdogPlistInstalled = pathExists(authority.watchdogLaunchAgent);
116
- const gatewayLaunchdPid = launchdPid('gateway');
117
- const watchdogLaunchdPid = launchdPid('watchdog');
261
+ const gatewayServiceInstalled = servicePlatform === 'systemd-user'
262
+ ? pathExists(systemdUserUnitPath(RECOVERY_GATEWAY_LABEL))
263
+ : gatewayPlistInstalled;
264
+ const watchdogServiceInstalled = servicePlatform === 'systemd-user'
265
+ ? pathExists(systemdUserUnitPath(RECOVERY_WATCHDOG_LABEL))
266
+ : watchdogPlistInstalled;
267
+ const gatewayManagedPid = managedPid('gateway');
268
+ const watchdogManagedPid = managedPid('watchdog');
118
269
  const gatewayRunning = Boolean(
119
270
  gatewayIdentity
120
- && gatewayLaunchdPid
121
- && gatewayIdentity.pid === gatewayLaunchdPid
271
+ && gatewayManagedPid
272
+ && gatewayIdentity.pid === gatewayManagedPid
122
273
  && processAlive(gatewayIdentity.pid)
123
274
  && authority.current
124
275
  && gatewayIdentity.releaseRevision === authority.current.releaseRevision
@@ -126,33 +277,40 @@ export function recoveryConnectorDescriptor(
126
277
  );
127
278
  const watchdogRunning = Boolean(
128
279
  watchdogIdentity
129
- && watchdogLaunchdPid
130
- && watchdogIdentity.pid === watchdogLaunchdPid
280
+ && watchdogManagedPid
281
+ && watchdogIdentity.pid === watchdogManagedPid
131
282
  && processAlive(watchdogIdentity.pid)
132
283
  && authority.current
133
284
  && watchdogIdentity.releaseRevision === authority.current.releaseRevision
134
285
  && watchdogIdentity.manifestSha256 === authority.current.manifestSha256,
135
286
  );
136
- const installed = Boolean(authority.current && gatewayPlistInstalled && watchdogPlistInstalled);
287
+ const installed = Boolean(authority.current && gatewayServiceInstalled && watchdogServiceInstalled);
137
288
  const publicEndpoint = Boolean(configuredUrl?.startsWith('https://'));
138
289
  const warnings: string[] = [];
139
290
  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.');
291
+ if (!gatewayServiceInstalled || !watchdogServiceInstalled) warnings.push(`Forge Recovery ${servicePlatform} services are not fully installed. Run forge recovery install.`);
141
292
  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.');
293
+ 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.');
294
+ else if (openAiTunnelService) {
295
+ 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.');
296
+ else if (!tunnelRunning) warnings.push(`The dedicated OpenAI Recovery tunnel runtime ${openAiTunnelService.alias} is not running.`);
297
+ else if (!tunnelHealthy || !tunnelReady) warnings.push(`The dedicated OpenAI Recovery tunnel runtime ${openAiTunnelService.alias} is not healthy and ready.`);
298
+ } else if (!configuredUrl) warnings.push('The configured Recovery tunnel requires --recovery-public-url.');
299
+ else if (!publicEndpoint) warnings.push('ChatGPT Recovery Connector requires an HTTPS public endpoint for managed public tunnels.');
300
+ else if (systemdTunnelService && !systemdTunnelInstalled) warnings.push(`The dedicated Forge Recovery systemd-user tunnel unit ${systemdTunnelUnitName ?? systemdTunnelService.unitName} is not installed.`);
301
+ else if (systemdTunnelService && !tunnelRunning) warnings.push(`The dedicated Forge Recovery systemd-user tunnel unit ${systemdTunnelUnitName} is not running.`);
302
+ else if (!systemdTunnelService && !tunnelPlistInstalled) warnings.push('The dedicated Forge Recovery tunnel plist is not installed.');
303
+ else if (!systemdTunnelService && !tunnelRestartSafe) warnings.push('The dedicated Forge Recovery tunnel plist must use RunAtLoad=true and unconditional KeepAlive=true.');
304
+ else if (!systemdTunnelService && !tunnelRunning) warnings.push('The dedicated Forge Recovery tunnel service is not running.');
148
305
  if (!passphraseConfigured) warnings.push('MCP OAuth passphrase is not configured. Run forge mcp setup chatgpt first.');
149
306
 
150
307
  return {
151
308
  name: 'Forge Recovery',
309
+ identity: recoveryMachineIdentity(config, { platform }),
152
310
  transport: 'streamable_http',
153
311
  url,
154
312
  public: publicEndpoint,
155
- readyForChatGPT: installed && gatewayRunning && watchdogRunning && tunnelRestartSafe && tunnelRunning && publicEndpoint && passphraseConfigured,
313
+ readyForChatGPT: installed && gatewayRunning && watchdogRunning && tunnelReady && (openAiTunnelService ? true : publicEndpoint) && passphraseConfigured,
156
314
  installed,
157
315
  currentRelease: authority.current?.releaseRevision,
158
316
  previousRelease: authority.previous?.releaseRevision,
@@ -165,23 +323,32 @@ export function recoveryConnectorDescriptor(
165
323
  services: {
166
324
  gateway: {
167
325
  label: RECOVERY_GATEWAY_LABEL,
326
+ platform: servicePlatform,
327
+ serviceInstalled: gatewayServiceInstalled,
168
328
  plistInstalled: gatewayPlistInstalled,
169
329
  running: gatewayRunning,
170
330
  ...(gatewayIdentity ? { pid: gatewayIdentity.pid } : {}),
171
331
  },
172
332
  watchdog: {
173
333
  label: RECOVERY_WATCHDOG_LABEL,
334
+ platform: servicePlatform,
335
+ serviceInstalled: watchdogServiceInstalled,
174
336
  plistInstalled: watchdogPlistInstalled,
175
337
  running: watchdogRunning,
176
338
  ...(watchdogIdentity ? { pid: watchdogIdentity.pid } : {}),
177
339
  },
178
340
  tunnel: {
179
- configured: Boolean(tunnelService),
341
+ configured: Boolean(configuredTunnel),
342
+ ...(configuredTunnel ? { platform: configuredTunnel.platform } : {}),
180
343
  ...(tunnelService ? { label: tunnelService.label } : {}),
344
+ ...(systemdTunnelService ? { unitName: systemdTunnelUnitName ?? systemdTunnelService.unitName } : {}),
345
+ ...(openAiTunnelService ? { alias: openAiTunnelService.alias, tunnelId: openAiTunnelService.tunnelId } : {}),
346
+ serviceInstalled: openAiTunnelService ? openAiRestartSafe : systemdTunnelService ? systemdTunnelInstalled : tunnelPlistInstalled,
181
347
  plistInstalled: tunnelPlistInstalled,
182
348
  restartSafe: tunnelRestartSafe,
183
349
  running: tunnelRunning,
184
- ...(tunnelLaunchdPid ? { pid: tunnelLaunchdPid } : {}),
350
+ ...(openAiTunnelService ? { healthy: tunnelHealthy, ready: tunnelReady } : {}),
351
+ ...(systemdTunnelPid ? { pid: systemdTunnelPid } : tunnelLaunchdPid ? { pid: tunnelLaunchdPid } : {}),
185
352
  },
186
353
  },
187
354
  tools: RECOVERY_TOOLS.map((tool) => tool.name),
@@ -189,6 +356,12 @@ export function recoveryConnectorDescriptor(
189
356
  };
190
357
  }
191
358
 
359
+ export function recoveryConnectorHasExternalTransport(
360
+ connector: Pick<RecoveryConnectorDescriptor, 'public' | 'services'>,
361
+ ): boolean {
362
+ return connector.public || connector.services.tunnel.platform === 'openai-secure-tunnel';
363
+ }
364
+
192
365
  export interface RecoveryConnectorVerificationDependencies {
193
366
  fetcher?: typeof fetch;
194
367
  random?: (size: number) => Buffer;
@@ -203,7 +376,15 @@ export interface RecoveryConnectorVerificationResult {
203
376
  authorizationMetadata: { ok: boolean; status?: number };
204
377
  protectedResourceMetadata: { ok: boolean; status?: number };
205
378
  unauthenticatedChallenge: { ok: boolean; status?: number };
206
- oauthPkce: { ok: boolean; registrationStatus?: number; authorizationStatus?: number; tokenStatus?: number };
379
+ oauthPkce: {
380
+ ok: boolean;
381
+ registrationStatus?: number;
382
+ authorizationStatus?: number;
383
+ tokenStatus?: number;
384
+ clientOwner?: 'recovery_verifier';
385
+ clientReused?: boolean;
386
+ credentialRetirement?: 'shared_recovery_token_not_individually_revocable';
387
+ };
207
388
  mcp: {
208
389
  ok: boolean;
209
390
  initializeStatus?: number;
@@ -224,7 +405,25 @@ function jsonObject(value: unknown): Record<string, unknown> {
224
405
  }
225
406
 
226
407
  async function responseJson(response: Response): Promise<Record<string, unknown>> {
227
- try { return jsonObject(await response.json()); } catch { return {}; }
408
+ try {
409
+ const text = await response.text();
410
+ if (!text.trim()) return {};
411
+ if (/text\/event-stream/i.test(response.headers.get('content-type') ?? '')) {
412
+ for (const line of text.split(/\r?\n/)) {
413
+ if (!line.startsWith('data:')) continue;
414
+ try {
415
+ const payload = jsonObject(JSON.parse(line.slice('data:'.length).trim()));
416
+ if (Object.keys(payload).length > 0) return payload;
417
+ } catch {
418
+ // Ignore non-JSON SSE fields and keep looking for the MCP data event.
419
+ }
420
+ }
421
+ return {};
422
+ }
423
+ return jsonObject(JSON.parse(text));
424
+ } catch {
425
+ return {};
426
+ }
228
427
  }
229
428
 
230
429
  export async function verifyRecoveryConnector(
@@ -250,7 +449,9 @@ export async function verifyRecoveryConnector(
250
449
  });
251
450
 
252
451
  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.');
452
+ if (!recoveryConnectorHasExternalTransport(connector)) {
453
+ failures.push('readiness: Recovery Connector has neither an HTTPS public endpoint nor a dedicated OpenAI Secure MCP Tunnel.');
454
+ }
254
455
 
255
456
  const origin = new URL(connector.url).origin;
256
457
  try {
@@ -325,12 +526,13 @@ export async function verifyRecoveryConnector(
325
526
  failures.push('oauthPkce: MCP OAuth passphrase is not configured.');
326
527
  } else {
327
528
  try {
328
- const redirectUri = 'http://127.0.0.1/forge-recovery-oauth-callback';
529
+ const redirectUri = RECOVERY_VERIFIER_OAUTH_REDIRECT_URI;
329
530
  const registration = await request(`${origin}/recovery/oauth/register`, {
330
531
  method: 'POST',
331
532
  headers: { 'content-type': 'application/json' },
332
533
  body: JSON.stringify({
333
- client_name: 'Forge Recovery Verification',
534
+ client_id: RECOVERY_VERIFIER_OAUTH_CLIENT_ID,
535
+ client_name: RECOVERY_VERIFIER_OAUTH_CLIENT_NAME,
334
536
  redirect_uris: [redirectUri],
335
537
  grant_types: ['authorization_code'],
336
538
  response_types: ['code'],
@@ -339,7 +541,11 @@ export async function verifyRecoveryConnector(
339
541
  });
340
542
  const registrationBody = await responseJson(registration);
341
543
  const clientId = typeof registrationBody.client_id === 'string' ? registrationBody.client_id : '';
342
- if (registration.status !== 201 || !clientId) throw new Error(`dynamic registration HTTP ${registration.status}`);
544
+ const clientOwner = registrationBody.forge_client_owner;
545
+ const clientReused = registrationBody.forge_client_reused === true;
546
+ if (registration.status !== 201 || clientId !== RECOVERY_VERIFIER_OAUTH_CLIENT_ID || clientOwner !== 'recovery_verifier') {
547
+ throw new Error(`dynamic verifier registration HTTP ${registration.status}`);
548
+ }
343
549
 
344
550
  const verifier = random(32).toString('base64url');
345
551
  const challenge = createHash('sha256').update(verifier).digest('base64url');
@@ -388,8 +594,16 @@ export async function verifyRecoveryConnector(
388
594
  registrationStatus: registration.status,
389
595
  authorizationStatus: authorization.status,
390
596
  tokenStatus: tokenResponse.status,
597
+ clientOwner: 'recovery_verifier',
598
+ clientReused,
599
+ // Recovery MCP currently uses one configured shared bearer token, so
600
+ // there is no verifier-specific credential that can be revoked without
601
+ // revoking Recovery itself. The reserved client slot keeps verifier
602
+ // registration state bounded across success and failure loops.
603
+ credentialRetirement: 'shared_recovery_token_not_individually_revocable',
391
604
  };
392
605
 
606
+ let mcpSessionId: string | undefined;
393
607
  const rpc = async (id: number | undefined, method: string, params?: Record<string, unknown>) => {
394
608
  const response = await request(connector.url, {
395
609
  method: 'POST',
@@ -398,53 +612,84 @@ export async function verifyRecoveryConnector(
398
612
  accept: 'application/json, text/event-stream',
399
613
  'content-type': 'application/json',
400
614
  'mcp-protocol-version': '2025-06-18',
615
+ ...(mcpSessionId ? { 'mcp-session-id': mcpSessionId } : {}),
401
616
  },
402
617
  body: JSON.stringify({ jsonrpc: '2.0', ...(id === undefined ? {} : { id }), method, ...(params ? { params } : {}) }),
403
618
  });
404
- if (method === 'notifications/initialized') return { response, result: {} as Record<string, unknown> };
619
+ if (method === 'notifications/initialized') {
620
+ if (response.status !== 202) throw new Error(`${method} HTTP ${response.status}`);
621
+ return { response, result: {} as Record<string, unknown> };
622
+ }
405
623
  const body = await responseJson(response);
406
624
  const rpcError = jsonObject(body.error);
407
625
  if (response.status !== 200 || Object.keys(rpcError).length > 0) {
408
626
  throw new Error(`${method} HTTP ${response.status}${typeof rpcError.message === 'string' ? `: ${rpcError.message}` : ''}`);
409
627
  }
628
+ if (method === 'initialize') {
629
+ const returnedSessionId = response.headers.get('mcp-session-id')?.trim();
630
+ if (!returnedSessionId) throw new Error('initialize HTTP 200: Mcp-Session-Id missing');
631
+ mcpSessionId = returnedSessionId;
632
+ }
410
633
  return { response, result: jsonObject(body.result) };
411
634
  };
412
-
413
- const initialized = await rpc(11, 'initialize', {
414
- protocolVersion: '2025-06-18',
415
- capabilities: {},
416
- clientInfo: { name: 'forge-recovery-verifier', version: FORGE_VERSION },
417
- });
418
- const initializedNotification = await rpc(undefined, 'notifications/initialized');
419
- const listed = await rpc(12, 'tools/list');
420
- const tools = Array.isArray(listed.result.tools)
421
- ? listed.result.tools.map((tool) => jsonObject(tool).name).filter((name): name is string => typeof name === 'string')
422
- : [];
423
- const expectedTools = RECOVERY_TOOLS.map((tool) => tool.name);
424
- const runtimeStatusCall = await rpc(13, 'tools/call', { name: 'runtime_status', arguments: {} });
425
- const listReleasesCall = await rpc(14, 'tools/call', { name: 'list_releases', arguments: {} });
426
- const initializedResult = initialized.result;
427
- const serverInfo = jsonObject(initializedResult.serverInfo);
428
- const mcpOk = initialized.response.status === 200
429
- && initializedNotification.response.status === 202
430
- && initializedResult.protocolVersion === '2025-06-18'
431
- && serverInfo.name === 'forge-standalone-recovery'
432
- && serverInfo.version === FORGE_VERSION
433
- && JSON.stringify(tools) === JSON.stringify(expectedTools)
434
- && runtimeStatusCall.response.status === 200
435
- && listReleasesCall.response.status === 200;
436
- probes.mcp = {
437
- ok: mcpOk,
438
- initializeStatus: initialized.response.status,
439
- initializedNotificationStatus: initializedNotification.response.status,
440
- protocolVersion: typeof initializedResult.protocolVersion === 'string' ? initializedResult.protocolVersion : undefined,
441
- serverName: typeof serverInfo.name === 'string' ? serverInfo.name : undefined,
442
- serverVersion: typeof serverInfo.version === 'string' ? serverInfo.version : undefined,
443
- tools,
444
- runtimeStatusCall: runtimeStatusCall.response.status === 200,
445
- listReleasesCall: listReleasesCall.response.status === 200,
635
+ const closeMcpSession = async (): Promise<void> => {
636
+ const sessionId = mcpSessionId;
637
+ if (!sessionId) return;
638
+ mcpSessionId = undefined;
639
+ try {
640
+ await request(connector.url, {
641
+ method: 'DELETE',
642
+ headers: {
643
+ authorization: `Bearer ${accessToken}`,
644
+ accept: 'application/json, text/event-stream',
645
+ 'mcp-protocol-version': '2025-06-18',
646
+ 'mcp-session-id': sessionId,
647
+ },
648
+ });
649
+ } catch {
650
+ // Session cleanup is best-effort and must not hide the verifier result.
651
+ }
446
652
  };
447
- if (!mcpOk) failures.push('mcp: initialize, Forge version, tool surface, or read-only calls did not match the Recovery contract.');
653
+
654
+ try {
655
+ const initialized = await rpc(11, 'initialize', {
656
+ protocolVersion: '2025-06-18',
657
+ capabilities: {},
658
+ clientInfo: { name: 'forge-recovery-verifier', version: FORGE_VERSION },
659
+ });
660
+ const initializedNotification = await rpc(undefined, 'notifications/initialized');
661
+ const listed = await rpc(12, 'tools/list');
662
+ const tools = Array.isArray(listed.result.tools)
663
+ ? listed.result.tools.map((tool) => jsonObject(tool).name).filter((name): name is string => typeof name === 'string')
664
+ : [];
665
+ const expectedTools = RECOVERY_TOOLS.map((tool) => tool.name);
666
+ const runtimeStatusCall = await rpc(13, 'tools/call', { name: 'runtime_status', arguments: {} });
667
+ const listReleasesCall = await rpc(14, 'tools/call', { name: 'list_releases', arguments: {} });
668
+ const initializedResult = initialized.result;
669
+ const serverInfo = jsonObject(initializedResult.serverInfo);
670
+ const mcpOk = initialized.response.status === 200
671
+ && initializedNotification.response.status === 202
672
+ && initializedResult.protocolVersion === '2025-06-18'
673
+ && serverInfo.name === 'forge-standalone-recovery'
674
+ && serverInfo.version === FORGE_VERSION
675
+ && JSON.stringify(tools) === JSON.stringify(expectedTools)
676
+ && runtimeStatusCall.response.status === 200
677
+ && listReleasesCall.response.status === 200;
678
+ probes.mcp = {
679
+ ok: mcpOk,
680
+ initializeStatus: initialized.response.status,
681
+ initializedNotificationStatus: initializedNotification.response.status,
682
+ protocolVersion: typeof initializedResult.protocolVersion === 'string' ? initializedResult.protocolVersion : undefined,
683
+ serverName: typeof serverInfo.name === 'string' ? serverInfo.name : undefined,
684
+ serverVersion: typeof serverInfo.version === 'string' ? serverInfo.version : undefined,
685
+ tools,
686
+ runtimeStatusCall: runtimeStatusCall.response.status === 200,
687
+ listReleasesCall: listReleasesCall.response.status === 200,
688
+ };
689
+ if (!mcpOk) failures.push('mcp: initialize, Forge version, tool surface, or read-only calls did not match the Recovery contract.');
690
+ } finally {
691
+ await closeMcpSession();
692
+ }
448
693
  } catch (error) {
449
694
  failures.push(`oauthPkce/mcp: ${error instanceof Error ? error.message : String(error)}`);
450
695
  }
@@ -534,7 +779,7 @@ export function buildRecoveryCommand(): Command {
534
779
  recoveryPublicUrl: current.recoveryPublicUrl,
535
780
  recoveryTunnelService: current.recoveryTunnelService,
536
781
  primaryPublicTunnelService: current.primaryPublicTunnelService,
537
- primaryRuntimeService: current.primaryRuntimeService ?? { platform: 'launchd' },
782
+ primaryRuntimeService: current.primaryRuntimeService ?? defaultPrimaryRuntimeServiceConfig(),
538
783
  primaryConnectorService,
539
784
  });
540
785
  const refreshed = loadRecoveryConfig(home);
@@ -620,6 +865,92 @@ export function buildRecoveryCommand(): Command {
620
865
  if (!result.ok) process.exitCode = 1;
621
866
  });
622
867
 
868
+ command.command('migrate-controller-home')
869
+ .description('Transactionally relocate Forge Controller Home on Linux/WSL through an independent transient systemd worker')
870
+ .requiredOption('--from <path>', 'Current Controller Home authority')
871
+ .requiredOption('--controller-home <path>', 'Destination user-level Controller Home')
872
+ .option('--archive-suffix <suffix>', 'Stable suffix for an authority-free destination shell archive')
873
+ .option('--timeout-ms <ms>', 'Overall migration transaction timeout in milliseconds', '240000')
874
+ .action(async (opts: { from: string; controllerHome: string; archiveSuffix?: string; timeoutMs: string }) => {
875
+ if (process.platform !== 'linux') throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_LINUX_ONLY');
876
+ const timeoutMs = Number(opts.timeoutMs);
877
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 30_000 || timeoutMs > 900_000) {
878
+ throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_TIMEOUT_INVALID');
879
+ }
880
+ const scheduled = scheduleLinuxControllerHomeMigration({
881
+ sourceHome: resolve(opts.from),
882
+ destinationHome: resolveControllerHome(opts.controllerHome),
883
+ ...(opts.archiveSuffix ? { archiveSuffix: opts.archiveSuffix } : {}),
884
+ timeoutMs,
885
+ });
886
+ output({
887
+ status: 'migration_scheduled',
888
+ operationId: scheduled.request.operationId,
889
+ sourceHome: scheduled.request.sourceHome,
890
+ destinationHome: scheduled.request.destinationHome,
891
+ workerUnit: scheduled.request.workerUnit,
892
+ receiptPath: scheduled.request.receiptPath,
893
+ logPath: scheduled.request.logPath,
894
+ preflight: scheduled.preflight,
895
+ });
896
+ });
897
+
898
+ command.command('migrate-controller-home-worker')
899
+ .description('Internal Linux/WSL transient worker for a durable Controller Home migration request')
900
+ .requiredOption('--request <path>', 'Durable migration request written before service cutover')
901
+ .action(async (opts: { request: string }) => {
902
+ if (process.platform !== 'linux') throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_LINUX_ONLY');
903
+ const receipt = await runLinuxControllerHomeMigrationRequest(resolve(opts.request));
904
+ // A durable terminal receipt, including failed-after-rollback, is a completed worker transaction.
905
+ // Only an unhandled process failure before a terminal receipt should trigger systemd Restart=on-failure.
906
+ output(receipt);
907
+ });
908
+
909
+ command.command('rollback-controller-home-migration')
910
+ .description('Rollback a stopped Controller Home relocation before any writer is restarted')
911
+ .requiredOption('--from <path>', 'Original Controller Home path')
912
+ .requiredOption('--controller-home <path>', 'Relocated user-level Controller Home path')
913
+ .option('--archived-destination-home <path>', 'Archived authority-free destination shell returned by migration')
914
+ .action(async (opts: { from: string; controllerHome: string; archivedDestinationHome?: string }) => {
915
+ if (process.platform !== 'linux') throw new Error('RECOVERY_CONTROLLER_HOME_MIGRATION_LINUX_ONLY');
916
+ const sourceHome = resolve(opts.from);
917
+ const destinationHome = resolveControllerHome(opts.controllerHome);
918
+ const preflight = recoveryControllerHomeMigrationPreflight(destinationHome, sourceHome);
919
+ if (preflight.liveOwners.length > 0) {
920
+ throw new Error(`RECOVERY_CONTROLLER_HOME_MIGRATION_OWNERS_LIVE: ${preflight.liveOwners.map((entry) => `${entry.label}:${entry.pid}`).join(',')}`);
921
+ }
922
+ const relocation: StoppedControllerHomeAuthorityRelocation = {
923
+ migrated: true,
924
+ sourceHome,
925
+ destinationHome,
926
+ ...(opts.archivedDestinationHome ? { archivedDestinationHome: resolve(opts.archivedDestinationHome) } : {}),
927
+ };
928
+ rollbackStoppedControllerHomeAuthorityRelocation(relocation);
929
+ output({ status: 'rolled_back', relocation });
930
+ });
931
+
932
+ command.command('windows-host-register')
933
+ .description('Register the Windows-host Recovery provider using discovered or explicitly bound host paths')
934
+ .option('--controller-home <path>', 'Override Controller Home')
935
+ .option('--rescue-root <path>', 'Override the local Recovery helper root')
936
+ .option('--recovery-script-host <path>', 'Host-visible path to ForgeRecovery.ps1')
937
+ .option('--recovery-script-windows <path>', 'Windows path to ForgeRecovery.ps1')
938
+ .option('--powershell-host <path>', 'Host-visible path to powershell.exe')
939
+ .option('--powershell-windows <path>', 'Windows path to powershell.exe')
940
+ .option('--disabled', 'Register the provider disabled')
941
+ .action((opts) => {
942
+ const result = installWindowsHostRecoveryPlugin({
943
+ controllerHome: resolveControllerHome(opts.controllerHome),
944
+ rescueRoot: opts.rescueRoot,
945
+ recoveryScriptHostPath: opts.recoveryScriptHost,
946
+ recoveryScriptWindowsPath: opts.recoveryScriptWindows,
947
+ powershellHostPath: opts.powershellHost,
948
+ powershellWindowsPath: opts.powershellWindows,
949
+ enabled: opts.disabled !== true,
950
+ });
951
+ output({ status: 'registered', ...result });
952
+ });
953
+
623
954
  command.command('install')
624
955
  .description('Build and activate the independent Forge Recovery Gateway and Watchdog release')
625
956
  .requiredOption('--controller-home <path>', 'Explicit Controller Home')
@@ -628,11 +959,25 @@ export function buildRecoveryCommand(): Command {
628
959
  .option('--recovery-public-url <url>', 'Dedicated Forge Recovery MCP public URL')
629
960
  .option('--recovery-tunnel-service-label <label>', 'Dedicated Recovery tunnel launchd label')
630
961
  .option('--recovery-tunnel-service-plist <path>', 'Absolute Recovery tunnel launchd plist path')
962
+ .option('--recovery-tunnel-systemd-unit <unit>', 'Dedicated Recovery systemd --user tunnel unit (Linux only)')
963
+ .option('--recovery-openai-tunnel-id <id>', 'Dedicated Recovery OpenAI Secure MCP Tunnel id')
964
+ .option('--recovery-openai-tunnel-alias <alias>', 'Dedicated Recovery tunnel-client runtime alias (default: machine-specific forge-recovery-<id>)')
965
+ .option('--recovery-openai-runtime-api-key-ref <ref>', 'Recovery tunnel runtime API key reference (env:NAME or file:/absolute/path)')
966
+ .option('--recovery-openai-profile <profile>', 'Optional tunnel-client runtime profile name for Recovery')
967
+ .option('--recovery-openai-profile-dir <path>', 'Optional absolute tunnel-client profile directory for Recovery')
968
+ .option('--recovery-openai-admin-profile <profile>', 'Optional tunnel-client admin profile used for Recovery runtime connect')
631
969
  .option('--primary-connector-service-label <label>', 'Primary OAuth/Connector launchd label managed by standalone Recovery')
632
970
  .option('--primary-connector-service-plist <path>', 'Absolute primary OAuth/Connector launchd plist path')
633
971
  .option('--primary-connector-local-url <url>', 'Local primary OAuth/Connector MCP endpoint used to distinguish Connector from tunnel failures')
634
972
  .option('--primary-tunnel-service-label <label>', 'Primary public MCP tunnel launchd label managed by standalone Recovery')
635
973
  .option('--primary-tunnel-service-plist <path>', 'Absolute primary public MCP tunnel launchd plist path')
974
+ .option('--primary-openai-tunnel-id <id>', 'Primary OpenAI Secure MCP Tunnel id managed by Recovery')
975
+ .option('--primary-openai-tunnel-alias <alias>', 'Primary tunnel-client runtime alias (default: forge)')
976
+ .option('--primary-openai-runtime-api-key-ref <ref>', 'Primary tunnel runtime API key reference (env:NAME or file:/absolute/path)')
977
+ .option('--primary-openai-profile <profile>', 'Optional tunnel-client runtime profile name for primary Forge')
978
+ .option('--primary-openai-profile-dir <path>', 'Optional absolute tunnel-client profile directory for primary Forge')
979
+ .option('--primary-openai-admin-profile <profile>', 'Optional tunnel-client admin profile used for primary runtime connect')
980
+ .option('--primary-runtime-source-root <path>', 'Stable canonical/package source used by Recovery for future Runtime staging')
636
981
  .option('--stage-only', 'Build and canary the Recovery release without activating services')
637
982
  .action(async (opts: {
638
983
  controllerHome: string;
@@ -641,36 +986,104 @@ export function buildRecoveryCommand(): Command {
641
986
  recoveryPublicUrl?: string;
642
987
  recoveryTunnelServiceLabel?: string;
643
988
  recoveryTunnelServicePlist?: string;
989
+ recoveryTunnelSystemdUnit?: string;
990
+ recoveryOpenaiTunnelId?: string;
991
+ recoveryOpenaiTunnelAlias?: string;
992
+ recoveryOpenaiRuntimeApiKeyRef?: string;
993
+ recoveryOpenaiProfile?: string;
994
+ recoveryOpenaiProfileDir?: string;
995
+ recoveryOpenaiAdminProfile?: string;
644
996
  primaryConnectorServiceLabel?: string;
645
997
  primaryConnectorServicePlist?: string;
646
998
  primaryConnectorLocalUrl?: string;
647
999
  primaryTunnelServiceLabel?: string;
648
1000
  primaryTunnelServicePlist?: string;
1001
+ primaryOpenaiTunnelId?: string;
1002
+ primaryOpenaiTunnelAlias?: string;
1003
+ primaryOpenaiRuntimeApiKeyRef?: string;
1004
+ primaryOpenaiProfile?: string;
1005
+ primaryOpenaiProfileDir?: string;
1006
+ primaryOpenaiAdminProfile?: string;
1007
+ primaryRuntimeSourceRoot?: string;
649
1008
  stageOnly?: boolean;
650
1009
  }) => {
651
1010
  const home = resolveControllerHome(opts.controllerHome);
652
1011
  const port = Number(opts.port);
653
1012
  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;
1013
+ const recoveryLaunchdTunnel = launchdService(opts.recoveryTunnelServiceLabel, opts.recoveryTunnelServicePlist, 'RECOVERY_TUNNEL') as PublicTunnelServiceConfig | undefined;
1014
+ const recoverySystemdTunnel = systemdRecoveryTunnelService(opts.recoveryTunnelSystemdUnit);
1015
+ if (recoveryLaunchdTunnel && process.platform === 'linux') throw new Error('RECOVERY_TUNNEL_LAUNCHD_UNSUPPORTED_ON_LINUX');
1016
+ if (recoverySystemdTunnel && process.platform !== 'linux') throw new Error('RECOVERY_TUNNEL_SYSTEMD_UNSUPPORTED_ON_THIS_PLATFORM');
1017
+ const recoveryLocalMcpUrl = `http://127.0.0.1:${port}/recovery/mcp`;
1018
+ const recoveryOpenAiTunnel = openAiTunnelService({
1019
+ tunnelId: opts.recoveryOpenaiTunnelId,
1020
+ alias: opts.recoveryOpenaiTunnelAlias,
1021
+ runtimeApiKeyRef: opts.recoveryOpenaiRuntimeApiKeyRef,
1022
+ profile: opts.recoveryOpenaiProfile,
1023
+ profileDir: opts.recoveryOpenaiProfileDir,
1024
+ adminProfile: opts.recoveryOpenaiAdminProfile,
1025
+ mcpServerUrl: recoveryLocalMcpUrl,
1026
+ defaultAlias: recoveryOpenAiTunnelDefaultAlias(home),
1027
+ role: 'RECOVERY',
1028
+ });
1029
+ const recoveryTunnelOwners = [recoveryLaunchdTunnel, recoverySystemdTunnel, recoveryOpenAiTunnel].filter(Boolean);
1030
+ if (recoveryTunnelOwners.length > 1) throw new Error('RECOVERY_TUNNEL_OWNER_CONFLICT');
1031
+ const recoveryTunnelService: RecoveryTunnelServiceConfig | undefined = recoveryOpenAiTunnel ?? recoverySystemdTunnel ?? recoveryLaunchdTunnel;
1032
+
1033
+ const primaryConnectorLaunchd = launchdService(opts.primaryConnectorServiceLabel, opts.primaryConnectorServicePlist, 'RECOVERY_PRIMARY_CONNECTOR') as PrimaryConnectorServiceConfig | undefined;
656
1034
  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;
1035
+ let primaryConnectorService: PrimaryConnectorServiceConfig | undefined;
1036
+ if (process.platform === 'linux') {
1037
+ if (primaryConnectorLaunchd) throw new Error('RECOVERY_PRIMARY_CONNECTOR_LAUNCHD_UNSUPPORTED_ON_LINUX');
1038
+ primaryConnectorService = primaryConnectorLocalUrl ? { platform: 'systemd-user', localMcpUrl: primaryConnectorLocalUrl } : undefined;
1039
+ } else {
1040
+ if (primaryConnectorLocalUrl && !primaryConnectorLaunchd) throw new Error('RECOVERY_PRIMARY_CONNECTOR_LABEL_REQUIRED_FOR_LOCAL_URL');
1041
+ primaryConnectorService = primaryConnectorLaunchd
1042
+ ? { ...primaryConnectorLaunchd, ...(primaryConnectorLocalUrl ? { localMcpUrl: primaryConnectorLocalUrl } : {}) }
1043
+ : undefined;
1044
+ }
1045
+
1046
+ const primaryLaunchdTunnel = launchdService(opts.primaryTunnelServiceLabel, opts.primaryTunnelServicePlist, 'RECOVERY_PRIMARY_TUNNEL') as PublicTunnelServiceConfig | undefined;
1047
+ const primaryOpenAiTunnel = primaryConnectorLocalUrl ? openAiTunnelService({
1048
+ tunnelId: opts.primaryOpenaiTunnelId,
1049
+ alias: opts.primaryOpenaiTunnelAlias,
1050
+ runtimeApiKeyRef: opts.primaryOpenaiRuntimeApiKeyRef,
1051
+ profile: opts.primaryOpenaiProfile,
1052
+ profileDir: opts.primaryOpenaiProfileDir,
1053
+ adminProfile: opts.primaryOpenaiAdminProfile,
1054
+ mcpServerUrl: primaryConnectorLocalUrl,
1055
+ defaultAlias: 'forge',
1056
+ role: 'RECOVERY_PRIMARY',
1057
+ }) : undefined;
1058
+ const primaryOpenAiOptionsSupplied = Boolean(opts.primaryOpenaiTunnelId || opts.primaryOpenaiTunnelAlias || opts.primaryOpenaiRuntimeApiKeyRef || opts.primaryOpenaiProfile || opts.primaryOpenaiProfileDir || opts.primaryOpenaiAdminProfile);
1059
+ if (primaryOpenAiOptionsSupplied && !primaryConnectorLocalUrl) throw new Error('RECOVERY_PRIMARY_CONNECTOR_LOCAL_URL_REQUIRED_FOR_OPENAI_TUNNEL');
1060
+ if (primaryLaunchdTunnel && primaryOpenAiTunnel) throw new Error('RECOVERY_PRIMARY_TUNNEL_OWNER_CONFLICT');
1061
+ const primaryPublicTunnelService = primaryOpenAiTunnel ?? primaryLaunchdTunnel;
662
1062
  if (primaryPublicTunnelService && !primaryConnectorService) throw new Error('RECOVERY_PRIMARY_CONNECTOR_REQUIRED_FOR_PRIMARY_TUNNEL');
1063
+ assertDistinctRecoveryOpenAiTunnelIdentity(recoveryTunnelService, primaryPublicTunnelService);
1064
+
663
1065
  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
- }
1066
+ if (recoveryOpenAiTunnel && recoveryPublicUrl) throw new Error('RECOVERY_OPENAI_TUNNEL_PUBLIC_URL_CONFLICT');
1067
+ if ((recoveryLaunchdTunnel || recoverySystemdTunnel) && !recoveryPublicUrl) throw new Error('RECOVERY_PUBLIC_URL_AND_TUNNEL_SERVICE_MUST_BE_CONFIGURED_TOGETHER');
1068
+ if (!recoveryTunnelService && recoveryPublicUrl) throw new Error('RECOVERY_PUBLIC_URL_AND_TUNNEL_SERVICE_MUST_BE_CONFIGURED_TOGETHER');
1069
+
667
1070
  const packageRoot = resolve(import.meta.dir, '..', '..', '..');
1071
+ const primaryRuntimeSourceRoot = opts.primaryRuntimeSourceRoot ? resolve(opts.primaryRuntimeSourceRoot) : packageRoot;
1072
+ const primaryRuntimeSourceRepository = findRegisteredRepositoryByCheckoutRoot(primaryRuntimeSourceRoot, home);
1073
+ if (!opts.stageOnly && !primaryRuntimeSourceRepository) {
1074
+ throw new Error(`RECOVERY_PRIMARY_RUNTIME_SOURCE_REPOSITORY_NOT_REGISTERED: ${primaryRuntimeSourceRoot}`);
1075
+ }
1076
+ if (!opts.stageOnly && /[\\/]\.forge[\\/]managed-worktrees[\\/]/.test(primaryRuntimeSourceRoot)) {
1077
+ throw new Error('RECOVERY_PRIMARY_RUNTIME_SOURCE_ROOT_DURABLE_REQUIRED');
1078
+ }
668
1079
  const result = await installStandaloneRecovery({
669
1080
  controllerHome: home,
670
- repoRoot: packageRoot,
1081
+ repoRoot: primaryRuntimeSourceRoot,
1082
+ ...(primaryRuntimeSourceRepository ? { primaryRuntimeSourceRepositoryId: primaryRuntimeSourceRepository.repoId } : {}),
671
1083
  sourceRoot: packageRoot,
672
1084
  port,
673
1085
  stageOnly: opts.stageOnly === true,
1086
+ primaryRuntimeService: defaultPrimaryRuntimeServiceConfig(),
674
1087
  publicMcpUrl: endpoint(opts.publicMcpUrl, 'PUBLIC_MCP_URL'),
675
1088
  recoveryPublicUrl,
676
1089
  recoveryTunnelService,