@moretea-labs/forge 1.5.1 → 1.7.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 (434) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +46 -0
  3. package/CLAUDE.md +2 -0
  4. package/DESIGN.md +7 -2
  5. package/README.md +4 -4
  6. package/README.zh-CN.md +2 -2
  7. package/SKILL.md +2 -2
  8. package/THIRD_PARTY_NOTICES.md +1 -1
  9. package/agents/openai.yaml +1 -1
  10. package/assets/hooks/hook-input.sh +11 -8
  11. package/assets/hooks/lib/session-state.sh +10 -7
  12. package/assets/hooks/lib/workflow-state.sh +13 -177
  13. package/assets/hooks/post-tool-observer.sh +8 -2
  14. package/assets/hooks/pre-edit-guard.sh +8 -39
  15. package/assets/hooks/prompt-guard.sh +7 -83
  16. package/assets/hooks/session-start-context.sh +56 -1
  17. package/assets/partials/05-workflow.partial.md +1 -1
  18. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  19. package/assets/plugin-registry.v1.json +54 -3
  20. package/assets/reference-configs/document-generation.md +1 -1
  21. package/assets/reference-configs/harness-overview.md +1 -1
  22. package/assets/reference-configs/hook-operations.md +1 -1
  23. package/assets/reference-configs/sprint-contracts.md +3 -3
  24. package/assets/skill-version.json +2 -2
  25. package/assets/templates/contract.template.md +2 -7
  26. package/assets/templates/helpers/capture-plan.sh +2 -2
  27. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  28. package/assets/templates/helpers/contract-worktree.sh +1 -21
  29. package/assets/templates/helpers/ensure-task-workflow.sh +13 -38
  30. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  31. package/assets/templates/helpers/new-plan.sh +1 -1
  32. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  33. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  34. package/assets/templates/helpers/sprint-backlog.sh +6 -6
  35. package/assets/templates/helpers/verify-contract.sh +1 -111
  36. package/assets/templates/helpers/verify-sprint.sh +3 -180
  37. package/assets/templates/plan.template.md +1 -1
  38. package/assets/templates/review.template.md +9 -34
  39. package/assets/workflow-contract.v1.json +10 -19
  40. package/docs/README.md +10 -10
  41. package/docs/architecture/CURRENT.md +170 -0
  42. package/docs/architecture/EVOLUTION.md +42 -0
  43. package/docs/architecture/current/README.md +4 -156
  44. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  45. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  46. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  47. package/docs/architecture/history/README.md +7 -0
  48. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  49. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  50. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  51. package/docs/architecture/history.md +3 -34
  52. package/docs/architecture/index.md +15 -98
  53. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  54. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  55. package/docs/architecture/versions/1.6.md +38 -0
  56. package/docs/architecture/versions/1.7.md +41 -0
  57. package/docs/forge-chatgpt-browser-engine.md +2 -2
  58. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  59. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  60. package/docs/forge-github-issue-launcher.md +1 -1
  61. package/docs/forge-runtime-self-healing-loop.md +1 -1
  62. package/docs/operations/controller-browser-plugin.md +38 -22
  63. package/docs/operations/controller-performance-and-502.md +124 -0
  64. package/docs/operations/controller-reliability-runbook.md +1 -1
  65. package/docs/operations/features.md +3 -3
  66. package/docs/operations/github-repository.md +1 -1
  67. package/docs/operations/homebrew.md +2 -2
  68. package/docs/operations/mcp-tool-exposure.md +5 -3
  69. package/docs/tutorials/01-install-and-start.md +1 -1
  70. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  71. package/docs/wiki/Architecture.md +6 -6
  72. package/docs/wiki/Implementation.md +2 -2
  73. package/docs/wiki/Installation.md +1 -1
  74. package/docs/wiki/Integrations.md +2 -2
  75. package/docs/wiki/Quick-Start.md +1 -1
  76. package/docs/wiki/Work-Lifecycle.md +1 -1
  77. package/package.json +29 -10
  78. package/scripts/activate-source-baseline.ts +17 -13
  79. package/scripts/benchmark-background-check-overlap.ts +228 -0
  80. package/scripts/benchmark-mcp-host.sh +117 -0
  81. package/scripts/benchmark-mcp-transport.ts +170 -0
  82. package/scripts/benchmark-quality-harness.ts +146 -0
  83. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  84. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  85. package/scripts/benchmark-typescript-navigation.ts +186 -0
  86. package/scripts/benchmark-work-efficiency.ts +334 -0
  87. package/scripts/capture-plan.sh +3 -3
  88. package/scripts/check-agent-tooling.sh +8 -35
  89. package/scripts/check-architecture-sync.sh +17 -86
  90. package/scripts/check-mcp-compatibility.ts +14 -0
  91. package/scripts/check-public-docs.sh +14 -1
  92. package/scripts/check-runtime-architecture.mjs +107 -64
  93. package/scripts/check-stable-baseline.ts +92 -0
  94. package/scripts/check-task-workflow.sh +4 -4
  95. package/scripts/contract-worktree.sh +1 -21
  96. package/scripts/ensure-task-workflow.sh +22 -55
  97. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  98. package/scripts/install-standalone-recovery.ts +24 -0
  99. package/scripts/lib/project-init-lib.sh +15 -44
  100. package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
  101. package/scripts/migrate-project-template.sh +23 -1179
  102. package/scripts/new-plan.sh +1 -1
  103. package/scripts/plan-to-todo.sh +11 -36
  104. package/scripts/run-governed-gate.ts +2 -1
  105. package/scripts/ship-worktrees.sh +2 -29
  106. package/scripts/smoke-ios-physical-device.ts +4 -2
  107. package/scripts/smoke-runtime-recovery.ts +1 -35
  108. package/scripts/smoke-schedule-engine.ts +1 -20
  109. package/scripts/sprint-backlog.sh +1 -1
  110. package/scripts/verify-contract.sh +1 -111
  111. package/scripts/verify-forge-runtime.sh +0 -1
  112. package/scripts/verify-sprint.sh +3 -180
  113. package/scripts/workflow-contract.ts +1 -5
  114. package/src/cli/commands/chatgpt.ts +31 -2
  115. package/src/cli/commands/init-hook.ts +3 -3
  116. package/src/cli/commands/mcp.ts +9 -5
  117. package/src/cli/commands/plugin.ts +37 -10
  118. package/src/cli/commands/recovery.ts +25 -3
  119. package/src/cli/commands/runtime.ts +55 -3
  120. package/src/cli/commands/security.ts +12 -1
  121. package/src/cli/commands/setup-profile.ts +31 -21
  122. package/src/cli/controller/check-runner.ts +106 -3
  123. package/src/cli/controller/composite-result.ts +1 -1
  124. package/src/cli/controller/context/focus.ts +51 -0
  125. package/src/cli/controller/context/instruction-context.ts +114 -0
  126. package/src/cli/controller/context/known-paths.ts +76 -0
  127. package/src/cli/controller/context/query-planning.ts +110 -0
  128. package/src/cli/controller/context/rule-resolution.ts +532 -0
  129. package/src/cli/controller/context/source-materializer.ts +278 -0
  130. package/src/cli/controller/context/structural.ts +16 -0
  131. package/src/cli/controller/context/types.ts +286 -0
  132. package/src/cli/controller/context-pack.ts +898 -430
  133. package/src/cli/controller/launch-agents.ts +77 -2
  134. package/src/cli/controller/operational-plan.ts +2 -2
  135. package/src/cli/controller/work-mode.ts +6 -4
  136. package/src/cli/editing/edit-session.ts +60 -7
  137. package/src/cli/github/github.ts +133 -0
  138. package/src/cli/hook/prompt-guard-decision.ts +17 -18
  139. package/src/cli/hook/prompt-intents.ts +1 -1
  140. package/src/cli/installer/managed-entries.ts +15 -1
  141. package/src/cli/local-bridge/console-automations.ts +225 -67
  142. package/src/cli/local-bridge/console-view-models.ts +3 -35
  143. package/src/cli/local-bridge/facade-api.ts +2 -217
  144. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  145. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +153 -3
  146. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  147. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +183 -3
  148. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
  149. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
  150. package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -11
  151. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  152. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
  153. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +4 -3
  154. package/src/cli/local-bridge/frontend/src/styles.css +27 -11
  155. package/src/cli/local-bridge/frontend/src/types.ts +8 -7
  156. package/src/cli/local-bridge/job-store.ts +1 -490
  157. package/src/cli/local-bridge/server.ts +29 -532
  158. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  159. package/src/cli/local-bridge/ui-dist/app.js +5101 -4967
  160. package/src/cli/mcp/auth.ts +5 -5
  161. package/src/cli/mcp/instructions.ts +3 -2
  162. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  163. package/src/cli/mcp/multi-repository.ts +16 -5
  164. package/src/cli/mcp/oauth.ts +175 -26
  165. package/src/cli/mcp/paths.ts +8 -2
  166. package/src/cli/mcp/repository-tools.ts +356 -101
  167. package/src/cli/mcp/server.ts +625 -54
  168. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  169. package/src/cli/mcp/toolset-selection.ts +1 -1
  170. package/src/cli/mcp/toolset.ts +42 -5
  171. package/src/cli/mcp/transports/http.ts +125 -15
  172. package/src/cli/mcp/transports/session-registry.ts +67 -5
  173. package/src/cli/mcp/types.ts +3 -3
  174. package/src/cli/repositories/command-classifier.ts +209 -14
  175. package/src/cli/repositories/command-executor.ts +55 -521
  176. package/src/cli/repositories/command-process.ts +108 -0
  177. package/src/cli/repositories/command-scope.ts +99 -41
  178. package/src/cli/repositories/controller-home.ts +7 -8
  179. package/src/cli/repositories/git-command-executor.ts +2 -0
  180. package/src/cli/repositories/locks.ts +53 -16
  181. package/src/cli/repositories/repository-snapshot.ts +121 -0
  182. package/src/cli/repositories/safe-patch.ts +6 -4
  183. package/src/cli/repositories/selected-path-actions.ts +17 -9
  184. package/src/cli/repositories/structured-git.ts +110 -10
  185. package/src/cli/repository/inspector.ts +565 -62
  186. package/src/cli/repository/session-cache.ts +58 -17
  187. package/src/cli/v81-entry.ts +1 -4
  188. package/src/effects/fs-transaction.ts +85 -17
  189. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  190. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  191. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  192. package/src/runtime/context/lsp-client.ts +283 -0
  193. package/src/runtime/context/semantic-navigation.ts +275 -0
  194. package/src/runtime/context/swift-navigation.ts +565 -0
  195. package/src/runtime/context/typescript-navigation.ts +226 -0
  196. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  197. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +196 -2
  198. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  199. package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
  200. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  201. package/src/runtime/control-plane/execution/validation.ts +15 -2
  202. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  203. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  204. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  205. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  206. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  207. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +175 -8
  208. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -21
  209. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +87 -9
  210. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  211. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  212. package/src/runtime/control-plane/facade/controller-session-store.ts +144 -14
  213. package/src/runtime/control-plane/facade/goal-workloop-access.ts +9 -2
  214. package/src/runtime/control-plane/facade/goal-workloop.ts +885 -105
  215. package/src/runtime/control-plane/facade/index.ts +1 -0
  216. package/src/runtime/control-plane/facade/plan-contract-store.ts +211 -26
  217. package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
  218. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  219. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  220. package/src/runtime/control-plane/facade/types.ts +55 -11
  221. package/src/runtime/control-plane/facade/work-contract-store.ts +204 -73
  222. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  223. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  224. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  225. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  226. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  227. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  228. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  229. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  230. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  231. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  232. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  233. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  234. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  235. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  236. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  237. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  238. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  239. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  240. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  241. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +642 -51
  242. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  243. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  244. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  245. package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
  246. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  247. package/src/runtime/control-plane/release-retention.ts +62 -2
  248. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  249. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  250. package/src/runtime/diagnostics/mcp-timing.ts +19 -0
  251. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  252. package/src/runtime/diagnostics/performance.ts +24 -1
  253. package/src/runtime/evidence/event-ledger.ts +1 -1
  254. package/src/runtime/execution/jobs/store.ts +58 -4
  255. package/src/runtime/execution/jobs/types.ts +1 -1
  256. package/src/runtime/execution/managed-workspace.ts +203 -1
  257. package/src/runtime/execution/process-runtime/check-facade.ts +52 -14
  258. package/src/runtime/execution/process-runtime/check-result.ts +63 -2
  259. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  260. package/src/runtime/execution/process-runtime/command-facade.ts +346 -40
  261. package/src/runtime/execution/process-runtime/gc.ts +98 -12
  262. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  263. package/src/runtime/execution/process-runtime/persisted-check.ts +57 -6
  264. package/src/runtime/execution/process-runtime/resource-claims.ts +97 -8
  265. package/src/runtime/execution/process-runtime/runtime.ts +59 -4
  266. package/src/runtime/execution/process-runtime/store.ts +7 -0
  267. package/src/runtime/execution/process-runtime/types.ts +2 -0
  268. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  269. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  270. package/src/runtime/execution/workers/executor.ts +0 -48
  271. package/src/runtime/gateway/mcp/execution-tools.ts +1219 -126
  272. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  273. package/src/runtime/gateway/mcp/router.ts +185 -83
  274. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  275. package/src/runtime/gateway/mcp/runtime-tools.ts +2555 -1616
  276. package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
  277. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  278. package/src/runtime/plugins/browser-adapter.ts +2925 -318
  279. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  280. package/src/runtime/plugins/browser-handoff-host.ts +6 -31
  281. package/src/runtime/plugins/browser-handoff.ts +92 -5
  282. package/src/runtime/plugins/browser-macos-bridge.ts +586 -45
  283. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  284. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  285. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  286. package/src/runtime/plugins/browser-runtime.ts +183 -0
  287. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  288. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  289. package/src/runtime/plugins/desktop-operator-registration.ts +15 -12
  290. package/src/runtime/plugins/external-adapter.ts +324 -3
  291. package/src/runtime/plugins/external-registration.ts +84 -5
  292. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  293. package/src/runtime/plugins/first-party-registry.ts +4 -2
  294. package/src/runtime/plugins/github-adapter.ts +125 -0
  295. package/src/runtime/plugins/gmail-adapter.ts +28 -2
  296. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  297. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  298. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  299. package/src/runtime/plugins/ios-adapter.ts +106 -16
  300. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  301. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  302. package/src/runtime/plugins/lightweight-action.ts +153 -0
  303. package/src/runtime/plugins/local-system-adapter.ts +125 -8
  304. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  305. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  306. package/src/runtime/plugins/plugin-management-adapter.ts +181 -0
  307. package/src/runtime/plugins/resend-adapter.ts +1 -1
  308. package/src/runtime/plugins/store.ts +366 -24
  309. package/src/runtime/plugins/types.ts +39 -2
  310. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  311. package/src/runtime/projections/git-status-sampler.ts +16 -16
  312. package/src/runtime/projections/materialized-view.ts +28 -10
  313. package/src/runtime/recovery/actions.ts +0 -10
  314. package/src/runtime/recovery/classifier.ts +0 -9
  315. package/src/runtime/recovery/maintenance-executor.ts +415 -18
  316. package/src/runtime/recovery/probe.ts +0 -1
  317. package/src/runtime/recovery/types.ts +0 -1
  318. package/src/runtime/root/mcp-transport.ts +180 -10
  319. package/src/runtime/root/package-connector-service.ts +186 -8
  320. package/src/runtime/root/package-runtime-release.ts +143 -17
  321. package/src/runtime/root/package-runtime-service.ts +430 -22
  322. package/src/runtime/root/release-manifest.ts +7 -2
  323. package/src/runtime/root/release-materialize.ts +162 -19
  324. package/src/runtime/root/release-store.ts +48 -11
  325. package/src/runtime/root/runtime.ts +23 -5
  326. package/src/runtime/root/service.ts +100 -27
  327. package/src/runtime/root/types.ts +2 -0
  328. package/src/runtime/safe-tooling/index.ts +0 -1
  329. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  330. package/src/runtime/safe-tooling/types.ts +0 -30
  331. package/src/runtime/shared/trusted-executable.ts +62 -0
  332. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  333. package/src/runtime/standalone-recovery/core.ts +702 -94
  334. package/src/runtime/standalone-recovery/entry.ts +99 -23
  335. package/src/runtime/standalone-recovery/installer.ts +19 -3
  336. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  337. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  338. package/src/runtime/workflow/schedules/browser-probe.ts +258 -0
  339. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
  340. package/src/runtime/workflow/schedules/engine.ts +509 -80
  341. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  342. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  343. package/src/runtime/workflow/schedules/store.ts +131 -10
  344. package/src/runtime/workflow/schedules/types.ts +6 -3
  345. package/src/runtime/workflow/schedules/work-continuation.ts +120 -34
  346. package/src/testing/test-governance.ts +8 -40
  347. package/assets/reference-configs/heartbeat-triage.md +0 -57
  348. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  349. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  350. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  351. package/bin/forge-desktop-helper.mjs +0 -204
  352. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  353. package/docs/architecture/current/architecture-invariants.md +0 -331
  354. package/docs/architecture/current/automation-and-schedule-engine.md +0 -453
  355. package/docs/architecture/current/codegraph-context-provider.md +0 -226
  356. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  357. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  358. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -354
  359. package/docs/architecture/current/entity-model.md +0 -592
  360. package/docs/architecture/current/external-plugin-governance.md +0 -396
  361. package/docs/architecture/current/failure-recovery.md +0 -447
  362. package/docs/architecture/current/governance.md +0 -213
  363. package/docs/architecture/current/human-interaction-plane.md +0 -87
  364. package/docs/architecture/current/implementation-status.md +0 -128
  365. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  366. package/docs/architecture/current/legacy-governance.md +0 -58
  367. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  368. package/docs/architecture/current/migration-roadmap.md +0 -105
  369. package/docs/architecture/current/multi-repository-execution.md +0 -371
  370. package/docs/architecture/current/operations-runbook.md +0 -45
  371. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  372. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  373. package/docs/architecture/current/repository-access-modes.md +0 -102
  374. package/docs/architecture/current/route-policy.md +0 -15
  375. package/docs/architecture/current/route-session-concurrency.md +0 -85
  376. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  377. package/docs/architecture/current/runtime-directory-map.md +0 -49
  378. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  379. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  380. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  381. package/docs/architecture/current/system-overview.md +0 -144
  382. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  383. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  384. package/docs/architecture/current/thin-harness-v1.md +0 -311
  385. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  386. package/docs/forge-autonomous-goal-loop.md +0 -9
  387. package/docs/forge-provider-configuration.md +0 -7
  388. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -83
  389. package/docs/operations/controller-desktop-plugin.md +0 -112
  390. package/docs/operations/gmail-assistant-routines.md +0 -186
  391. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  392. package/docs/operations/provider-configuration.md +0 -31
  393. package/scripts/harness-trace-grade.sh +0 -150
  394. package/scripts/heartbeat-triage.sh +0 -361
  395. package/scripts/maintenance-triage.sh +0 -167
  396. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  397. package/src/cli/repositories/goal-registry.ts +0 -239
  398. package/src/runtime/assistant/action-proposals.ts +0 -279
  399. package/src/runtime/assistant/intent.ts +0 -395
  400. package/src/runtime/assistant/model-provider.ts +0 -460
  401. package/src/runtime/assistant/openapi.ts +0 -223
  402. package/src/runtime/assistant/policy.ts +0 -89
  403. package/src/runtime/assistant/readiness.ts +0 -258
  404. package/src/runtime/assistant/routine-runtime.ts +0 -477
  405. package/src/runtime/assistant/schedule-binding.ts +0 -171
  406. package/src/runtime/assistant/standing-grants.ts +0 -375
  407. package/src/runtime/assistant/store.ts +0 -243
  408. package/src/runtime/assistant/types.ts +0 -135
  409. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  410. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  411. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  412. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  413. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  414. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  415. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  416. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  417. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  418. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  419. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  420. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  421. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  422. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  423. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  424. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  425. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  426. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  427. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  428. package/src/runtime/plugins/desktop-adapter.ts +0 -374
  429. package/src/runtime/safe-tooling/browser-safe.ts +0 -133
  430. package/src/runtime/workflow/findings/store.ts +0 -204
  431. package/src/runtime/workflow/findings/types.ts +0 -58
  432. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  433. package/src/runtime/workflow/portfolio/store.ts +0 -185
  434. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -13,7 +13,7 @@ export interface ToolDefinitionCacheOptions {
13
13
  maxEntriesPerSession?: number;
14
14
  }
15
15
 
16
- const CORE_TOOL_NAMES = new Set(['rh_context', 'rh_access', 'work_submit', 'work_get', 'work_list', 'controller_ready']);
16
+ const CORE_TOOL_NAMES = new Set(['rh_context', 'rh_access', 'rh_work', 'rh_status', 'rh_inbox']);
17
17
 
18
18
  export function controllerCapabilityGroupForTool(name: string): ControllerCapabilityGroup {
19
19
  if (CORE_TOOL_NAMES.has(name)) return 'core';
@@ -8,7 +8,7 @@ export interface ControllerToolsetSelection {
8
8
  }
9
9
 
10
10
  export function normalizeConfiguredMcpToolset(value: unknown): McpToolset | undefined {
11
- return value === 'core' || value === 'advanced' || value === 'full' ? value : undefined;
11
+ return value === 'facade' || value === 'core' || value === 'advanced' || value === 'full' ? value : undefined;
12
12
  }
13
13
 
14
14
  /** Persisted Core and Advanced are compatibility-equivalent stable profiles. */
@@ -17,7 +17,7 @@ import type { McpToolset } from './types';
17
17
  import { forgeToolSurfaceFingerprint } from '../controller/runtime-config';
18
18
 
19
19
  export type ToolExposureClass = 'facade' | 'advanced' | 'internal' | 'compatibility';
20
- export type ControllerToolProfile = 'core' | 'advanced' | 'full';
20
+ export type ControllerToolProfile = 'facade' | 'core' | 'advanced' | 'full';
21
21
 
22
22
  export interface ControllerToolInventoryEntry {
23
23
  name: string;
@@ -48,6 +48,22 @@ export interface ControllerExposureSnapshot {
48
48
  ready: boolean;
49
49
  }
50
50
 
51
+ /**
52
+ * Cached schema/readiness projection for hot paths that do not need access
53
+ * policy or a per-tool inventory. Status uses this rather than rebuilding a
54
+ * complete controller exposure response on every read.
55
+ */
56
+ export interface ControllerToolSurfaceStatus {
57
+ expectedToolNames: string[];
58
+ actualToolNames: string[];
59
+ missingToolNames: string[];
60
+ unexpectedToolNames: string[];
61
+ duplicateToolNames: string[];
62
+ fingerprint: string;
63
+ schemaStableAcrossAccessModes: true;
64
+ ready: boolean;
65
+ }
66
+
51
67
  /**
52
68
  * Core and Advanced expose the same bounded default ChatGPT surface
53
69
  * (DEFAULT_CONTROLLER_TOOL_NAMES). Full remains the exhaustive compatibility
@@ -84,7 +100,7 @@ function pruneStaticExposureCache(): void {
84
100
  }
85
101
 
86
102
  export function normalizeMcpToolset(value: unknown): McpToolset {
87
- if (value === 'full' || value === 'advanced' || value === 'core') return value;
103
+ if (value === 'full' || value === 'advanced' || value === 'core' || value === 'facade') return value;
88
104
  return 'advanced';
89
105
  }
90
106
 
@@ -97,6 +113,7 @@ export function controllerToolNamesForToolset(
97
113
  _ctx?: MultiRepositoryMcpToolContext,
98
114
  ): readonly string[] | null {
99
115
  if (toolset === 'full') return null;
116
+ if (toolset === 'facade') return PREFERRED_FACADE_TOOL_NAMES;
100
117
  return DEFAULT_CONTROLLER_TOOL_NAMES;
101
118
  }
102
119
 
@@ -158,13 +175,19 @@ function staticExposureCacheKey(ctx: MultiRepositoryMcpToolContext): string {
158
175
  function buildStaticControllerExposureSnapshot(
159
176
  ctx: MultiRepositoryMcpToolContext,
160
177
  ): StaticControllerExposureSnapshot {
161
- const rawDefinitions = runtimeToolDefinitions.concat(
178
+ const currentDefinitions = runtimeToolDefinitions.concat(
162
179
  executionToolDefinitions,
163
180
  processToolDefinitions,
164
181
  accessToolDefinitions,
165
182
  repositoryToolDefinitions,
166
- buildMultiRepositoryToolDefinitions(ctx),
167
183
  );
184
+ const rawDefinitions = ctx.toolset === 'full'
185
+ ? currentDefinitions.concat(
186
+ buildMultiRepositoryToolDefinitions(ctx).filter(
187
+ (legacy) => !currentDefinitions.some((current) => current.name === legacy.name),
188
+ ),
189
+ )
190
+ : currentDefinitions;
168
191
  const unique = uniqueDefinitions(rawDefinitions);
169
192
  const allowed = controllerToolNamesForToolset(ctx.toolset, ctx);
170
193
  const expectedToolNames = allowed === null
@@ -226,7 +249,7 @@ function capabilityForToolName(name: string): string {
226
249
  }
227
250
 
228
251
  function profileForToolset(toolset: McpToolset): ControllerToolProfile {
229
- return toolset === 'full' ? 'full' : toolset === 'core' ? 'core' : 'advanced';
252
+ return toolset === 'full' ? 'full' : toolset === 'facade' ? 'facade' : toolset === 'core' ? 'core' : 'advanced';
230
253
  }
231
254
 
232
255
  export function controllerToolInventory(
@@ -263,6 +286,20 @@ export function controllerExposureSnapshot(ctx: MultiRepositoryMcpToolContext):
263
286
  };
264
287
  }
265
288
 
289
+ export function controllerToolSurfaceStatus(ctx: MultiRepositoryMcpToolContext): ControllerToolSurfaceStatus {
290
+ const snapshot = staticControllerExposureSnapshot(ctx);
291
+ return {
292
+ expectedToolNames: snapshot.expectedToolNames,
293
+ actualToolNames: snapshot.actualToolNames,
294
+ missingToolNames: snapshot.missingToolNames,
295
+ unexpectedToolNames: snapshot.unexpectedToolNames,
296
+ duplicateToolNames: snapshot.duplicateToolNames,
297
+ fingerprint: snapshot.fingerprint,
298
+ schemaStableAcrossAccessModes: snapshot.schemaStableAcrossAccessModes,
299
+ ready: snapshot.ready,
300
+ };
301
+ }
302
+
266
303
  export function controllerExposedToolNames(ctx: MultiRepositoryMcpToolContext): string[] {
267
304
  return controllerExposureSnapshot(ctx).actualToolNames;
268
305
  }
@@ -1,4 +1,6 @@
1
1
  import { randomUUID, timingSafeEqual } from 'crypto';
2
+ import { existsSync, watch } from 'fs';
3
+ import { dirname } from 'path';
2
4
  import express, { type Request, type Response, type NextFunction } from 'express';
3
5
  import { tokenHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/token.js';
4
6
  import { revocationHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/revoke.js';
@@ -9,9 +11,11 @@ import type { OAuthClientInformationFull } from '@modelcontextprotocol/sdk/share
9
11
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
10
12
  import {
11
13
  buildMultiRepositoryToolDefinitions,
14
+ createCanonicalRuntimeProxy,
12
15
  createMcpToolContext,
13
16
  createForgeMcpServerFromContext,
14
17
  readCanonicalRuntimeToolSchema,
18
+ type CanonicalRuntimeProxy,
15
19
  type CanonicalRuntimeToolSchema,
16
20
  type McpServerOptions,
17
21
  } from '../server';
@@ -34,10 +38,11 @@ import {
34
38
  controllerExposureSnapshot,
35
39
  } from '../toolset';
36
40
  import { readForgeRuntimeStatus } from '../../../runtime/control-plane/runtime-status-client';
41
+ import { invalidateExecutionSession } from '../../../runtime/control-plane/execution/session-store';
37
42
  import { runtimeIdentitySnapshot } from '../../../runtime/gateway/mcp/runtime-tools';
38
43
  import { projectionBlocksReadiness, readRepositoryProjectionSnapshot } from '../../../runtime/projections/materialized-view';
39
44
  import { readRuntimeGeneration } from '../../../runtime/control-plane/runtime-generation';
40
- import { readRuntimeStatusSnapshot } from '../../../runtime/root/status';
45
+ import { readRuntimeStatusSnapshot, runtimeStatusPath } from '../../../runtime/root/status';
41
46
  import { getRepository, listRepositories } from '../../repositories/registry';
42
47
  import { buildControllerTaskLedgerProjection } from '../../controller/task-ledger';
43
48
  import { legacyIssueAuthorityRetired } from '../../controller/legacy-issue-cutover';
@@ -192,6 +197,14 @@ export async function resolveMcpSessionCurrentFingerprint(
192
197
  return await loadRuntimeFingerprint?.();
193
198
  }
194
199
 
200
+ export function isMcpToolsListRequest(body: unknown): boolean {
201
+ const messages = Array.isArray(body) ? body : [body];
202
+ return messages.length > 0 && messages.every((message) => {
203
+ if (!message || typeof message !== 'object') return false;
204
+ return (message as { method?: unknown }).method === 'tools/list';
205
+ });
206
+ }
207
+
195
208
  function toolCallOutsideSessionSchema(body: unknown, toolNames: readonly string[] | undefined): boolean {
196
209
  if (!toolNames) return false;
197
210
  const messages = Array.isArray(body) ? body : [body];
@@ -618,6 +631,11 @@ function requireMcpHttpAuth(
618
631
  resourcePath = '/mcp',
619
632
  ) {
620
633
  return (req: Request, res: Response, next: NextFunction) => {
634
+ if (mode === 'none') {
635
+ next();
636
+ return;
637
+ }
638
+
621
639
  if (mode === 'bearer') {
622
640
  if (!isAuthorizedMcpHttpRequest(req, bearerToken)) {
623
641
  sendBearerUnauthorized(res, bearerToken ? 'Missing or invalid Authorization header' : 'Bearer token is not configured', Boolean(bearerToken));
@@ -664,7 +682,7 @@ function positiveIntegerEnv(name: string, fallback: number): number {
664
682
  }
665
683
 
666
684
  const MAX_MCP_SESSIONS = positiveIntegerEnv('FORGE_MCP_MAX_SESSIONS', 64);
667
- const MAX_MCP_SESSIONS_PER_PRINCIPAL = positiveIntegerEnv('FORGE_MCP_MAX_SESSIONS_PER_PRINCIPAL', 8);
685
+ const MAX_MCP_SESSIONS_PER_PRINCIPAL = positiveIntegerEnv('FORGE_MCP_MAX_SESSIONS_PER_PRINCIPAL', MAX_MCP_SESSIONS);
668
686
  const MAX_INITIALIZING_SESSIONS = positiveIntegerEnv('FORGE_MCP_MAX_INITIALIZING_SESSIONS', 8);
669
687
  const MAX_POSTS_PER_SESSION = positiveIntegerEnv('FORGE_MCP_MAX_POSTS_PER_SESSION', 4);
670
688
  const MAX_ACTIVE_POSTS = positiveIntegerEnv('FORGE_MCP_MAX_ACTIVE_POSTS', 32);
@@ -685,6 +703,7 @@ async function handleMcpPost(
685
703
  route: McpSessionRoute,
686
704
  currentToolSurfaceFingerprint: () => string | undefined,
687
705
  resolveRuntimeSchema?: (context: McpToolContext) => Promise<CanonicalRuntimeToolSchema | undefined>,
706
+ sharedRuntimeProxy?: CanonicalRuntimeProxy,
688
707
  ): Promise<void> {
689
708
  let body: unknown;
690
709
  try {
@@ -711,7 +730,15 @@ async function handleMcpPost(
711
730
  if (stats.initializing >= MAX_INITIALIZING_SESSIONS || stats.activePosts >= MAX_ACTIVE_POSTS) {
712
731
  stats.rejectedOverload += 1;
713
732
  res.setHeader('retry-after', '1');
714
- res.status(503).json({ error: 'server_busy', message: 'Too many MCP sessions are initializing; retry shortly' });
733
+ res.status(503).json({
734
+ error: 'server_busy',
735
+ code: 'MCP_SERVER_BUSY',
736
+ message: 'Too many MCP sessions are initializing; retry shortly',
737
+ recoverable: true,
738
+ retryable: true,
739
+ sessionPreserved: true,
740
+ action: 'retry',
741
+ });
715
742
  return;
716
743
  }
717
744
  stats.initializing += 1;
@@ -731,7 +758,15 @@ async function handleMcpPost(
731
758
  if (!reservationId) {
732
759
  stats.rejectedOverload += 1;
733
760
  res.setHeader('retry-after', '1');
734
- res.status(503).json({ error: 'session_capacity', message: 'All MCP sessions are executing active work; retry shortly' });
761
+ res.status(503).json({
762
+ error: 'session_capacity',
763
+ code: 'MCP_SESSION_CAPACITY',
764
+ message: 'All MCP sessions are executing active requests; retry shortly',
765
+ recoverable: true,
766
+ retryable: true,
767
+ sessionPreserved: true,
768
+ action: 'retry',
769
+ });
735
770
  return;
736
771
  }
737
772
  const sessionContext = createMcpToolContext({
@@ -739,7 +774,8 @@ async function handleMcpPost(
739
774
  sessionId: `mcp_${randomUUID().replace(/-/g, '')}`,
740
775
  principalId,
741
776
  });
742
- const runtimeSchema = await resolveRuntimeSchema?.(sessionContext);
777
+ let runtimeSchema = await resolveRuntimeSchema?.(sessionContext);
778
+ let server: ReturnType<typeof createForgeMcpServerFromContext> | undefined;
743
779
  transport = new StreamableHTTPServerTransport({
744
780
  sessionIdGenerator: () => randomUUID(),
745
781
  onsessioninitialized: (newSessionId: string): void => {
@@ -752,6 +788,22 @@ async function handleMcpPost(
752
788
  clientIdentity,
753
789
  toolSurfaceFingerprint: runtimeSchema?.fingerprint ?? currentToolSurfaceFingerprint(),
754
790
  toolNames: runtimeSchema?.toolNames,
791
+ notifyToolListChanged: async () => { await server?.sendToolListChanged(); },
792
+ ...(runtimeSchema && resolveRuntimeSchema
793
+ ? {
794
+ refreshToolSurface: async () => {
795
+ const refreshed = await resolveRuntimeSchema(sessionContext);
796
+ if (!refreshed || !runtimeSchema) return undefined;
797
+ runtimeSchema.definitions = refreshed.definitions;
798
+ runtimeSchema.toolNames = refreshed.toolNames;
799
+ runtimeSchema.fingerprint = refreshed.fingerprint;
800
+ return {
801
+ toolSurfaceFingerprint: refreshed.fingerprint,
802
+ toolNames: refreshed.toolNames,
803
+ };
804
+ },
805
+ }
806
+ : {}),
755
807
  });
756
808
  initializedSessionId = newSessionId;
757
809
  },
@@ -759,7 +811,7 @@ async function handleMcpPost(
759
811
  transport.onclose = () => {
760
812
  if (transport?.sessionId) registry.detach(transport.sessionId);
761
813
  };
762
- const server = createForgeMcpServerFromContext(sessionContext, runtimeSchema);
814
+ server = createForgeMcpServerFromContext(sessionContext, runtimeSchema, sharedRuntimeProxy);
763
815
  await server.connect(transport);
764
816
  await transport.handleRequest(req, res, body);
765
817
  } finally {
@@ -790,6 +842,17 @@ async function handleMcpPost(
790
842
  : undefined,
791
843
  );
792
844
  if (!mcpSessionToolSurfaceFingerprintIsCurrent(managed.toolSurfaceFingerprint, currentFingerprint)) {
845
+ // Standard MCP hot refresh: allow an explicitly requested tools/list
846
+ // to replace the live session's mutable Runtime schema snapshot. Hosts
847
+ // that ignore list_changed and call directly still hit the reset fence.
848
+ if (isMcpToolsListRequest(body)) {
849
+ const refreshed = await registry.refreshToolSurface(sessionId);
850
+ if (refreshed
851
+ && (!currentFingerprint || refreshed.toolSurfaceFingerprint === currentFingerprint)) {
852
+ await managed.transport.handleRequest(req, res, body);
853
+ return;
854
+ }
855
+ }
793
856
  // Keep the transport/session alive long enough for the host to observe
794
857
  // the recoverable reset and issue a replacement initialize request.
795
858
  // The initialize path explicitly supersedes this session afterward.
@@ -873,10 +936,15 @@ async function handleMcpDelete(req: Request, res: Response, registry: HttpSessio
873
936
  export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
874
937
  const host = opts.host ?? '127.0.0.1';
875
938
  const port = opts.port ?? 8765;
876
- const repoRoot = resolveMcpRepoRoot(opts.repo ?? '.');
877
939
  const controllerHome = resolveControllerHome(opts.controllerHome);
878
- const serviceConfig = loadMcpServiceLocalConfig(controllerHome, repoRoot);
940
+ const serviceConfig = loadMcpServiceLocalConfig(controllerHome);
879
941
  const profile = opts.profile ?? serviceConfig?.profile ?? 'controller';
942
+ // A controller Gateway can serve registered repositories without selecting
943
+ // one at startup. Do not turn its launchd working directory into an
944
+ // implicit repository, especially for package Runtime snapshots.
945
+ const repoRoot = profile === 'controller' && !opts.repo?.trim()
946
+ ? undefined
947
+ : resolveMcpRepoRoot(opts.repo ?? '.');
880
948
  const authMode = parseMcpHttpAuthMode(opts.auth ?? serviceConfig?.auth?.mode);
881
949
  const authToken = opts.authToken ?? readMcpServiceBearerToken(controllerHome, repoRoot);
882
950
  const oauthPassphrase = authMode === 'oauth'
@@ -898,6 +966,17 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
898
966
  streamLeaseMs: MCP_STREAM_LEASE_MS,
899
967
  absoluteLifetimeMs: MCP_SESSION_ABSOLUTE_LIFETIME_MS,
900
968
  activePostStallMs: MCP_ACTIVE_POST_STALL_MS,
969
+ onSessionClosed: (session, reason) => {
970
+ const context = session.toolContext;
971
+ if (!('controllerHome' in context) || typeof context.controllerHome !== 'string') return;
972
+ const executionSessionId = typeof context.sessionId === 'string' ? context.sessionId.trim() : '';
973
+ if (!executionSessionId) return;
974
+ invalidateExecutionSession(
975
+ context.controllerHome,
976
+ executionSessionId,
977
+ `mcp_transport_${reason}`,
978
+ );
979
+ },
901
980
  });
902
981
  const runtimeStats: McpRuntimeStats = { initializing: 0, activePosts: 0, rejectedOverload: 0 };
903
982
  const toolContext = createMcpToolContext({ ...opts, repo: repoRoot, controllerHome, profile });
@@ -913,14 +992,34 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
913
992
  devRunnerMaxTimeoutMs: opts.devRunnerMaxTimeoutMs,
914
993
  };
915
994
  const runtimeControllerHome = 'controllerHome' in toolContext ? toolContext.controllerHome : undefined;
995
+ const sharedRuntimeProxy = runtimeControllerHome && 'controllerHome' in toolContext && !toolContext.runtimeSourceRoot
996
+ ? createCanonicalRuntimeProxy(toolContext)
997
+ : undefined;
916
998
  const currentRuntimeGeneration = () => runtimeControllerHome ? readRuntimeGeneration(runtimeControllerHome) : undefined;
917
999
  const currentRuntimeToolSurfaceFingerprint = () => runtimeControllerHome
918
1000
  ? readRuntimeStatusSnapshot(runtimeControllerHome)?.toolSurfaceFingerprint
919
1001
  : undefined;
1002
+ let observedRuntimeToolSurfaceFingerprint = currentRuntimeToolSurfaceFingerprint();
1003
+ let toolSurfaceNotificationTimer: ReturnType<typeof setTimeout> | undefined;
1004
+ const runtimeStatusDirectory = runtimeControllerHome ? dirname(runtimeStatusPath(runtimeControllerHome)) : undefined;
1005
+ const runtimeStatusWatcher = runtimeStatusDirectory && existsSync(runtimeStatusDirectory)
1006
+ ? watch(runtimeStatusDirectory, { persistent: false }, (_event, filename) => {
1007
+ if (String(filename ?? '') !== 'status.json') return;
1008
+ if (toolSurfaceNotificationTimer) clearTimeout(toolSurfaceNotificationTimer);
1009
+ toolSurfaceNotificationTimer = setTimeout(() => {
1010
+ const currentFingerprint = currentRuntimeToolSurfaceFingerprint();
1011
+ if (!currentFingerprint || currentFingerprint === observedRuntimeToolSurfaceFingerprint) return;
1012
+ observedRuntimeToolSurfaceFingerprint = currentFingerprint;
1013
+ void sessionRegistry.notifyToolListChanged(currentFingerprint);
1014
+ }, 25);
1015
+ toolSurfaceNotificationTimer.unref();
1016
+ })
1017
+ : undefined;
1018
+ runtimeStatusWatcher?.on('error', () => undefined);
920
1019
  const resolveRuntimeSchema = runtimeControllerHome
921
1020
  ? async (context: McpToolContext): Promise<CanonicalRuntimeToolSchema | undefined> => {
922
1021
  if (!('controllerHome' in context)) return undefined;
923
- return await readCanonicalRuntimeToolSchema(context);
1022
+ return await readCanonicalRuntimeToolSchema(context, sharedRuntimeProxy);
924
1023
  }
925
1024
  : undefined;
926
1025
  const localControllerConfig = {
@@ -932,7 +1031,7 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
932
1031
  const toolSurface = toolContext.policy.profile === 'controller' ? FORGE_TOOL_SURFACE : `${toolContext.policy.profile}-legacy-v1`;
933
1032
  const toolSurfaceSchemaVersion = toolContext.policy.profile === 'controller' ? FORGE_MCP_SCHEMA_VERSION : 1;
934
1033
  const forgeVersion = FORGE_VERSION;
935
- const repoId = toolContext.policy.profile === 'controller' ? undefined : repositoryIdentity(repoRoot);
1034
+ const repoId = toolContext.policy.profile === 'controller' || !repoRoot ? undefined : repositoryIdentity(repoRoot);
936
1035
  const startedAt = new Date().toISOString();
937
1036
  const localOrigin = `http://${host === '::' || host === '0.0.0.0' ? '127.0.0.1' : host}:${port}`;
938
1037
  const advertisedOrigin = configuredPublicOrigin ?? localOrigin;
@@ -1006,7 +1105,11 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
1006
1105
  defaultTimeoutMs: toolContext.policy.execution.runnerTimeoutMs,
1007
1106
  maxTimeoutMs: toolContext.policy.execution.runnerMaxTimeoutMs,
1008
1107
  },
1009
- auth: authMode === 'oauth' ? (oauthPassphrase ? 'oauth' : 'missing') : (authToken ? 'required' : 'missing'),
1108
+ auth: authMode === 'oauth'
1109
+ ? (oauthPassphrase ? 'oauth' : 'missing')
1110
+ : authMode === 'bearer'
1111
+ ? (authToken ? 'required' : 'missing')
1112
+ : 'none',
1010
1113
  mcpEndpoint: `${advertisedOrigin}/mcp`,
1011
1114
  // Grok now completes standard OAuth dynamic registration + PKCE on the
1012
1115
  // canonical MCP resource. Keep /mcp-grok below only as a legacy alias.
@@ -1195,7 +1298,7 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
1195
1298
  // Primary MCP path: OAuth (or bearer when --auth bearer). Unchanged for ChatGPT.
1196
1299
  app.use('/mcp', setMcpResponseHeaders);
1197
1300
  app.post('/mcp', requireMcpHttpAuth(authMode, authToken, oauthProvider, configuredPublicOrigin), express.raw({ type: '*/*', limit: '1mb' }), (req, res) => {
1198
- handleMcpPost(req, res, baseOptions, sessionRegistry, runtimeStats, '/mcp', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema).catch((error: unknown) => {
1301
+ handleMcpPost(req, res, baseOptions, sessionRegistry, runtimeStats, '/mcp', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema, sharedRuntimeProxy).catch((error: unknown) => {
1199
1302
  if (!res.headersSent) sendMcpRequestError(res, error);
1200
1303
  });
1201
1304
  });
@@ -1213,7 +1316,7 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
1213
1316
  // Legacy Grok OAuth resource. New Grok connectors should use canonical /mcp.
1214
1317
  app.use('/mcp-grok', setMcpResponseHeaders);
1215
1318
  app.post('/mcp-grok', requireMcpHttpAuth(authMode, authToken, oauthProvider, configuredPublicOrigin, '/mcp-grok'), express.raw({ type: '*/*', limit: '1mb' }), (req, res) => {
1216
- handleMcpPost(req, res, baseOptions, sessionRegistry, runtimeStats, '/mcp-grok', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema).catch((error: unknown) => {
1319
+ handleMcpPost(req, res, baseOptions, sessionRegistry, runtimeStats, '/mcp-grok', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema, sharedRuntimeProxy).catch((error: unknown) => {
1217
1320
  if (!res.headersSent) sendMcpRequestError(res, error);
1218
1321
  });
1219
1322
  });
@@ -1231,7 +1334,7 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
1231
1334
  // Bearer-only MCP path for clients that can send Authorization headers. Never advertises OAuth resource_metadata.
1232
1335
  app.use('/mcp-bearer', setMcpResponseHeaders);
1233
1336
  app.post('/mcp-bearer', requireMcpHttpAuth('bearer', authToken, null, configuredPublicOrigin), express.raw({ type: '*/*', limit: '1mb' }), (req, res) => {
1234
- handleMcpPost(req, res, baseOptions, sessionRegistry, runtimeStats, '/mcp-bearer', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema).catch((error: unknown) => {
1337
+ handleMcpPost(req, res, baseOptions, sessionRegistry, runtimeStats, '/mcp-bearer', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema, sharedRuntimeProxy).catch((error: unknown) => {
1235
1338
  if (!res.headersSent) sendMcpRequestError(res, error);
1236
1339
  });
1237
1340
  });
@@ -1260,13 +1363,20 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
1260
1363
 
1261
1364
  httpServer.on('close', () => {
1262
1365
  clearInterval(cleanupTimer);
1366
+ if (toolSurfaceNotificationTimer) clearTimeout(toolSurfaceNotificationTimer);
1367
+ runtimeStatusWatcher?.close();
1263
1368
  void sessionRegistry.closeAll('shutdown');
1369
+ void sharedRuntimeProxy?.close();
1264
1370
  });
1265
1371
 
1266
1372
  await new Promise<void>((resolve) => {
1267
1373
  httpServer.once('listening', resolve);
1268
1374
  });
1269
- const authLabel = authMode === 'oauth' ? (oauthPassphrase ? 'oauth' : 'oauth-missing') : (authToken ? 'bearer' : 'missing');
1375
+ const authLabel = authMode === 'oauth'
1376
+ ? (oauthPassphrase ? 'oauth' : 'oauth-missing')
1377
+ : authMode === 'bearer'
1378
+ ? (authToken ? 'bearer' : 'missing')
1379
+ : 'none';
1270
1380
  console.error(
1271
1381
  `forge mcp http listening on http://${host}:${port}/mcp (auth: ${authLabel}), http://${host}:${port}/mcp-grok (auth: ${authLabel}), and http://${host}:${port}/mcp-bearer (auth: bearer)`,
1272
1382
  );
@@ -18,6 +18,11 @@ export interface ClosableMcpTransport {
18
18
  close(): Promise<void> | void;
19
19
  }
20
20
 
21
+ export interface McpToolSurfaceRefresh {
22
+ toolSurfaceFingerprint: string;
23
+ toolNames?: string[];
24
+ }
25
+
21
26
  export interface ManagedMcpSession<
22
27
  TTransport extends ClosableMcpTransport = ClosableMcpTransport,
23
28
  TContext = unknown,
@@ -31,6 +36,11 @@ export interface ManagedMcpSession<
31
36
  toolSurfaceFingerprint?: string;
32
37
  /** Exact tool names returned by the Canonical Runtime at initialize time. */
33
38
  toolNames?: string[];
39
+ /** Standard MCP tools/list_changed notification bound to this session's connected Server. */
40
+ notifyToolListChanged?: () => Promise<void> | void;
41
+ /** Refreshes the mutable Runtime schema snapshot backing this live MCP session. */
42
+ refreshToolSurface?: () => Promise<McpToolSurfaceRefresh | undefined>;
43
+ lastNotifiedToolSurfaceFingerprint?: string;
34
44
  createdAt: number;
35
45
  lastActivityAt: number;
36
46
  streamOpenedAt?: number;
@@ -69,7 +79,10 @@ export interface McpSessionSnapshot {
69
79
  closed: McpSessionCloseCounters;
70
80
  }
71
81
 
72
- export interface McpSessionRegistryOptions {
82
+ export interface McpSessionRegistryOptions<
83
+ TTransport extends ClosableMcpTransport = ClosableMcpTransport,
84
+ TContext = unknown,
85
+ > {
73
86
  maximumSessions?: number;
74
87
  maximumSessionsPerPrincipal?: number;
75
88
  idleTtlMs?: number;
@@ -77,6 +90,7 @@ export interface McpSessionRegistryOptions {
77
90
  absoluteLifetimeMs?: number;
78
91
  activePostStallMs?: number;
79
92
  now?: () => number;
93
+ onSessionClosed?: (session: ManagedMcpSession<TTransport, TContext>, reason: McpSessionCloseReason) => void;
80
94
  }
81
95
 
82
96
  interface RegisterMcpSession<TTransport extends ClosableMcpTransport, TContext> {
@@ -88,6 +102,8 @@ interface RegisterMcpSession<TTransport extends ClosableMcpTransport, TContext>
88
102
  clientIdentity: string;
89
103
  toolSurfaceFingerprint?: string;
90
104
  toolNames?: string[];
105
+ notifyToolListChanged?: () => Promise<void> | void;
106
+ refreshToolSurface?: () => Promise<McpToolSurfaceRefresh | undefined>;
91
107
  initialPost?: boolean;
92
108
  }
93
109
 
@@ -157,12 +173,13 @@ export class McpSessionRegistry<
157
173
  private readonly absoluteLifetimeMs: number;
158
174
  private readonly activePostStallMs: number;
159
175
  private readonly now: () => number;
176
+ private readonly onSessionClosed?: (session: ManagedMcpSession<TTransport, TContext>, reason: McpSessionCloseReason) => void;
160
177
  private readonly closeCounters = emptyCloseCounters();
161
178
  private readonly initializeReservations = new Map<string, InitializeReservation>();
162
179
  private admissionQueue: Promise<void> = Promise.resolve();
163
180
  private nextReservationId = 0;
164
181
 
165
- constructor(options: McpSessionRegistryOptions = {}) {
182
+ constructor(options: McpSessionRegistryOptions<TTransport, TContext> = {}) {
166
183
  this.maximumSessions = positiveInteger(options.maximumSessions, DEFAULT_MAXIMUM_SESSIONS);
167
184
  this.maximumSessionsPerPrincipal = Math.min(
168
185
  this.maximumSessions,
@@ -173,6 +190,7 @@ export class McpSessionRegistry<
173
190
  this.absoluteLifetimeMs = positiveInteger(options.absoluteLifetimeMs, DEFAULT_ABSOLUTE_LIFETIME_MS);
174
191
  this.activePostStallMs = positiveInteger(options.activePostStallMs, DEFAULT_ACTIVE_POST_STALL_MS);
175
192
  this.now = options.now ?? Date.now;
193
+ this.onSessionClosed = options.onSessionClosed;
176
194
  }
177
195
 
178
196
  get size(): number {
@@ -206,6 +224,38 @@ export class McpSessionRegistry<
206
224
  if (session) session.lastActivityAt = this.now();
207
225
  }
208
226
 
227
+ async notifyToolListChanged(currentFingerprint: string | undefined): Promise<number> {
228
+ if (!currentFingerprint) return 0;
229
+ let notified = 0;
230
+ await Promise.all(Array.from(this.sessions.values()).map(async (session) => {
231
+ if (!session.toolSurfaceFingerprint
232
+ || session.toolSurfaceFingerprint === currentFingerprint
233
+ || session.lastNotifiedToolSurfaceFingerprint === currentFingerprint
234
+ || !session.notifyToolListChanged) return;
235
+ try {
236
+ await session.notifyToolListChanged();
237
+ session.lastNotifiedToolSurfaceFingerprint = currentFingerprint;
238
+ notified += 1;
239
+ } catch {
240
+ // Fingerprint fencing remains authoritative. A broken notification
241
+ // stream must not mark a stale session as refreshed or close active work.
242
+ }
243
+ }));
244
+ return notified;
245
+ }
246
+
247
+ async refreshToolSurface(sessionId: string): Promise<McpToolSurfaceRefresh | undefined> {
248
+ const session = this.sessions.get(sessionId);
249
+ if (!session?.refreshToolSurface) return undefined;
250
+ const refreshed = await session.refreshToolSurface();
251
+ if (!refreshed?.toolSurfaceFingerprint) return undefined;
252
+ session.toolSurfaceFingerprint = refreshed.toolSurfaceFingerprint;
253
+ session.toolNames = refreshed.toolNames;
254
+ session.lastNotifiedToolSurfaceFingerprint = refreshed.toolSurfaceFingerprint;
255
+ session.lastActivityAt = this.now();
256
+ return refreshed;
257
+ }
258
+
209
259
  beginPost(sessionId: string): ManagedMcpSession<TTransport, TContext> | undefined {
210
260
  const session = this.sessions.get(sessionId);
211
261
  if (!session) return undefined;
@@ -250,9 +300,11 @@ export class McpSessionRegistry<
250
300
  detach(sessionId: string, reason: McpSessionCloseReason = 'transport_close'): boolean {
251
301
  const session = this.sessions.get(sessionId);
252
302
  if (!session) return false;
303
+ const closeReason = session.pendingCloseReason ?? reason;
253
304
  this.sessions.delete(sessionId);
254
305
  clearSessionCachesForSession(sessionId);
255
- this.incrementCloseCounter(session.pendingCloseReason ?? reason);
306
+ this.incrementCloseCounter(closeReason);
307
+ this.observeSessionClosed(session, closeReason);
256
308
  return true;
257
309
  }
258
310
 
@@ -263,6 +315,7 @@ export class McpSessionRegistry<
263
315
  clearSessionCachesForSession(sessionId);
264
316
  session.pendingCloseReason = reason;
265
317
  this.incrementCloseCounter(reason);
318
+ this.observeSessionClosed(session, reason);
266
319
  try {
267
320
  await session.transport.close();
268
321
  } catch {
@@ -272,6 +325,15 @@ export class McpSessionRegistry<
272
325
  return true;
273
326
  }
274
327
 
328
+ private observeSessionClosed(session: ManagedMcpSession<TTransport, TContext>, reason: McpSessionCloseReason): void {
329
+ try {
330
+ this.onSessionClosed?.(session, reason);
331
+ } catch {
332
+ // Session close must remain available even if liveness evidence cannot be recorded.
333
+ // The controller lease then expires normally instead of being released unsafely.
334
+ }
335
+ }
336
+
275
337
  async reserveForInitialize(request: InitializeCapacityRequest): Promise<string | undefined> {
276
338
  return await this.withAdmissionLock(async () => {
277
339
  await this.prune();
@@ -358,7 +420,7 @@ export class McpSessionRegistry<
358
420
  const reservations = [...this.initializeReservations.values()];
359
421
  const activePosts = sessions.reduce((count, session) => count + session.inFlightPosts, 0) + reservations.length;
360
422
  const activeStreams = sessions.reduce((count, session) => count + session.inFlightGets, 0);
361
- const evictable = sessions.filter((session) => session.inFlightPosts === 0).length;
423
+ const evictable = sessions.filter((session) => session.inFlightPosts === 0 && session.inFlightGets === 0).length;
362
424
  const reserved = reservations.length;
363
425
  const protectedCount = sessions.length - evictable + reserved;
364
426
  const capacityAvailable = Math.max(0, this.maximumSessions - sessions.length - reserved);
@@ -413,7 +475,7 @@ export class McpSessionRegistry<
413
475
  predicate: (session: ManagedMcpSession<TTransport, TContext>) => boolean = () => true,
414
476
  ): ManagedMcpSession<TTransport, TContext>[] {
415
477
  return [...this.sessions.values()]
416
- .filter((session) => session.inFlightPosts === 0 && predicate(session))
478
+ .filter((session) => session.inFlightPosts === 0 && session.inFlightGets === 0 && predicate(session))
417
479
  .sort((left, right) => {
418
480
  const leftAge = left.streamOpenedAt ?? left.lastActivityAt;
419
481
  const rightAge = right.streamOpenedAt ?? right.lastActivityAt;
@@ -2,11 +2,11 @@ export type McpProfileName = 'planner' | 'executor' | 'orchestrator' | 'controll
2
2
  export type McpPathIntent = 'read' | 'write';
3
3
  export type McpAgentRunnerName = 'codex' | 'claude';
4
4
  /** Controller MCP tools/list exposure profile.
5
- * - core (default): facade + repository bootstrap/selection only
6
- * - advanced: supervised controller surface (former large core set)
5
+ * - facade: explicit five-tool ChatGPT orchestration surface
6
+ * - core/advanced (default compatibility labels): bounded stable controller surface
7
7
  * - full: compatibility mode — all legacy + runtime tools
8
8
  */
9
- export type McpToolset = 'core' | 'advanced' | 'full';
9
+ export type McpToolset = 'facade' | 'core' | 'advanced' | 'full';
10
10
 
11
11
  export interface McpPolicy {
12
12
  profile: McpProfileName;