@moretea-labs/forge 1.6.0 → 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 (396) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  17. package/assets/plugin-registry.v1.json +1 -1
  18. package/assets/reference-configs/document-generation.md +1 -1
  19. package/assets/reference-configs/harness-overview.md +1 -1
  20. package/assets/reference-configs/sprint-contracts.md +3 -3
  21. package/assets/skill-version.json +2 -2
  22. package/assets/templates/contract.template.md +2 -7
  23. package/assets/templates/helpers/capture-plan.sh +2 -2
  24. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  25. package/assets/templates/helpers/contract-worktree.sh +1 -21
  26. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  27. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  28. package/assets/templates/helpers/new-plan.sh +1 -1
  29. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  30. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  31. package/assets/templates/helpers/verify-contract.sh +1 -111
  32. package/assets/templates/helpers/verify-sprint.sh +3 -180
  33. package/assets/templates/plan.template.md +1 -1
  34. package/assets/templates/review.template.md +9 -34
  35. package/assets/workflow-contract.v1.json +10 -19
  36. package/docs/README.md +10 -10
  37. package/docs/architecture/CURRENT.md +170 -0
  38. package/docs/architecture/EVOLUTION.md +42 -0
  39. package/docs/architecture/current/README.md +4 -156
  40. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  41. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  42. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  43. package/docs/architecture/history/README.md +7 -0
  44. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  45. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  46. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  47. package/docs/architecture/history.md +3 -34
  48. package/docs/architecture/index.md +15 -98
  49. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  50. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  51. package/docs/architecture/versions/1.6.md +38 -0
  52. package/docs/architecture/versions/1.7.md +41 -0
  53. package/docs/forge-chatgpt-browser-engine.md +2 -2
  54. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  55. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  56. package/docs/forge-github-issue-launcher.md +1 -1
  57. package/docs/forge-runtime-self-healing-loop.md +1 -1
  58. package/docs/operations/controller-browser-plugin.md +26 -6
  59. package/docs/operations/controller-performance-and-502.md +124 -0
  60. package/docs/operations/controller-reliability-runbook.md +1 -1
  61. package/docs/operations/features.md +2 -2
  62. package/docs/operations/github-repository.md +1 -1
  63. package/docs/operations/homebrew.md +2 -2
  64. package/docs/operations/mcp-tool-exposure.md +5 -3
  65. package/docs/wiki/Architecture.md +6 -6
  66. package/docs/wiki/Implementation.md +2 -2
  67. package/docs/wiki/Integrations.md +2 -2
  68. package/docs/wiki/Work-Lifecycle.md +1 -1
  69. package/package.json +29 -10
  70. package/scripts/activate-source-baseline.ts +17 -13
  71. package/scripts/benchmark-background-check-overlap.ts +228 -0
  72. package/scripts/benchmark-mcp-host.sh +117 -0
  73. package/scripts/benchmark-mcp-transport.ts +170 -0
  74. package/scripts/benchmark-quality-harness.ts +146 -0
  75. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  76. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  77. package/scripts/benchmark-typescript-navigation.ts +186 -0
  78. package/scripts/benchmark-work-efficiency.ts +334 -0
  79. package/scripts/capture-plan.sh +3 -3
  80. package/scripts/check-agent-tooling.sh +8 -35
  81. package/scripts/check-architecture-sync.sh +17 -86
  82. package/scripts/check-mcp-compatibility.ts +14 -0
  83. package/scripts/check-runtime-architecture.mjs +107 -64
  84. package/scripts/check-stable-baseline.ts +92 -0
  85. package/scripts/check-task-workflow.sh +4 -4
  86. package/scripts/contract-worktree.sh +1 -21
  87. package/scripts/ensure-task-workflow.sh +22 -55
  88. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  89. package/scripts/install-standalone-recovery.ts +24 -0
  90. package/scripts/lib/project-init-lib.sh +15 -44
  91. package/scripts/migrate-project-template.sh +23 -1179
  92. package/scripts/new-plan.sh +1 -1
  93. package/scripts/plan-to-todo.sh +11 -36
  94. package/scripts/run-governed-gate.ts +2 -1
  95. package/scripts/ship-worktrees.sh +2 -29
  96. package/scripts/smoke-ios-physical-device.ts +4 -2
  97. package/scripts/smoke-runtime-recovery.ts +1 -35
  98. package/scripts/smoke-schedule-engine.ts +1 -20
  99. package/scripts/verify-contract.sh +1 -111
  100. package/scripts/verify-forge-runtime.sh +0 -1
  101. package/scripts/verify-sprint.sh +3 -180
  102. package/scripts/workflow-contract.ts +1 -5
  103. package/src/cli/commands/init-hook.ts +3 -3
  104. package/src/cli/commands/mcp.ts +9 -5
  105. package/src/cli/commands/plugin.ts +1 -1
  106. package/src/cli/commands/recovery.ts +25 -3
  107. package/src/cli/commands/runtime.ts +55 -3
  108. package/src/cli/commands/security.ts +12 -1
  109. package/src/cli/commands/setup-profile.ts +31 -21
  110. package/src/cli/controller/check-runner.ts +106 -3
  111. package/src/cli/controller/composite-result.ts +1 -1
  112. package/src/cli/controller/context/focus.ts +51 -0
  113. package/src/cli/controller/context/instruction-context.ts +114 -0
  114. package/src/cli/controller/context/known-paths.ts +76 -0
  115. package/src/cli/controller/context/query-planning.ts +110 -0
  116. package/src/cli/controller/context/rule-resolution.ts +532 -0
  117. package/src/cli/controller/context/source-materializer.ts +278 -0
  118. package/src/cli/controller/context/structural.ts +16 -0
  119. package/src/cli/controller/context/types.ts +286 -0
  120. package/src/cli/controller/context-pack.ts +785 -469
  121. package/src/cli/controller/launch-agents.ts +77 -2
  122. package/src/cli/controller/operational-plan.ts +2 -2
  123. package/src/cli/controller/work-mode.ts +6 -4
  124. package/src/cli/editing/edit-session.ts +60 -7
  125. package/src/cli/github/github.ts +133 -0
  126. package/src/cli/installer/managed-entries.ts +15 -1
  127. package/src/cli/local-bridge/console-automations.ts +54 -78
  128. package/src/cli/local-bridge/console-view-models.ts +1 -35
  129. package/src/cli/local-bridge/facade-api.ts +0 -217
  130. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  131. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  132. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  133. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  134. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  135. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  136. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  137. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  138. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  139. package/src/cli/local-bridge/job-store.ts +1 -490
  140. package/src/cli/local-bridge/server.ts +0 -513
  141. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  142. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  143. package/src/cli/mcp/auth.ts +5 -5
  144. package/src/cli/mcp/instructions.ts +3 -2
  145. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  146. package/src/cli/mcp/multi-repository.ts +16 -5
  147. package/src/cli/mcp/oauth.ts +175 -26
  148. package/src/cli/mcp/repository-tools.ts +247 -107
  149. package/src/cli/mcp/server.ts +537 -111
  150. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  151. package/src/cli/mcp/toolset-selection.ts +1 -1
  152. package/src/cli/mcp/toolset.ts +42 -5
  153. package/src/cli/mcp/transports/http.ts +101 -14
  154. package/src/cli/mcp/transports/session-registry.ts +43 -5
  155. package/src/cli/mcp/types.ts +3 -3
  156. package/src/cli/repositories/command-classifier.ts +101 -19
  157. package/src/cli/repositories/command-executor.ts +55 -521
  158. package/src/cli/repositories/command-process.ts +108 -0
  159. package/src/cli/repositories/command-scope.ts +99 -41
  160. package/src/cli/repositories/controller-home.ts +7 -8
  161. package/src/cli/repositories/git-command-executor.ts +2 -0
  162. package/src/cli/repositories/locks.ts +53 -16
  163. package/src/cli/repositories/repository-snapshot.ts +121 -0
  164. package/src/cli/repositories/selected-path-actions.ts +17 -9
  165. package/src/cli/repositories/structured-git.ts +61 -3
  166. package/src/cli/repository/inspector.ts +553 -63
  167. package/src/cli/repository/session-cache.ts +58 -17
  168. package/src/cli/v81-entry.ts +1 -4
  169. package/src/effects/fs-transaction.ts +85 -17
  170. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  171. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  172. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  173. package/src/runtime/context/lsp-client.ts +283 -0
  174. package/src/runtime/context/semantic-navigation.ts +275 -0
  175. package/src/runtime/context/swift-navigation.ts +565 -0
  176. package/src/runtime/context/typescript-navigation.ts +226 -0
  177. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  178. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +47 -8
  179. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  180. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  181. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  182. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  183. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  184. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  185. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  186. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +143 -8
  187. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  188. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
  189. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  190. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  191. package/src/runtime/control-plane/facade/controller-session-store.ts +132 -1
  192. package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
  193. package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
  194. package/src/runtime/control-plane/facade/index.ts +1 -0
  195. package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
  196. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  197. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  198. package/src/runtime/control-plane/facade/types.ts +22 -5
  199. package/src/runtime/control-plane/facade/work-contract-store.ts +175 -73
  200. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  201. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  202. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  203. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  204. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  205. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  206. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  207. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  208. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  209. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  210. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  211. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  212. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  213. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  214. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  215. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  216. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  217. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  218. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  219. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +355 -116
  220. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  221. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  222. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  223. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  224. package/src/runtime/control-plane/release-retention.ts +62 -2
  225. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  226. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  227. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  228. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  229. package/src/runtime/diagnostics/performance.ts +24 -1
  230. package/src/runtime/evidence/event-ledger.ts +1 -1
  231. package/src/runtime/execution/jobs/store.ts +58 -4
  232. package/src/runtime/execution/jobs/types.ts +1 -1
  233. package/src/runtime/execution/managed-workspace.ts +203 -1
  234. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  235. package/src/runtime/execution/process-runtime/check-result.ts +53 -0
  236. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  237. package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
  238. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  239. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  240. package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
  241. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  242. package/src/runtime/execution/process-runtime/runtime.ts +43 -3
  243. package/src/runtime/execution/process-runtime/store.ts +1 -0
  244. package/src/runtime/execution/process-runtime/types.ts +2 -0
  245. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  246. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  247. package/src/runtime/execution/workers/executor.ts +0 -48
  248. package/src/runtime/gateway/mcp/execution-tools.ts +1192 -114
  249. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  250. package/src/runtime/gateway/mcp/router.ts +184 -82
  251. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  252. package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
  253. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  254. package/src/runtime/plugins/browser-adapter.ts +2901 -241
  255. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  256. package/src/runtime/plugins/browser-handoff.ts +92 -2
  257. package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
  258. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  259. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  260. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  261. package/src/runtime/plugins/browser-runtime.ts +183 -0
  262. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  263. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  264. package/src/runtime/plugins/desktop-operator-registration.ts +8 -2
  265. package/src/runtime/plugins/external-adapter.ts +324 -3
  266. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  267. package/src/runtime/plugins/first-party-registry.ts +2 -0
  268. package/src/runtime/plugins/github-adapter.ts +125 -0
  269. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  270. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  271. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  272. package/src/runtime/plugins/ios-adapter.ts +106 -16
  273. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  274. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  275. package/src/runtime/plugins/lightweight-action.ts +153 -0
  276. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  277. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  278. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  279. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  280. package/src/runtime/plugins/resend-adapter.ts +1 -1
  281. package/src/runtime/plugins/store.ts +269 -29
  282. package/src/runtime/plugins/types.ts +35 -2
  283. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  284. package/src/runtime/projections/git-status-sampler.ts +16 -16
  285. package/src/runtime/projections/materialized-view.ts +28 -10
  286. package/src/runtime/recovery/maintenance-executor.ts +393 -28
  287. package/src/runtime/root/mcp-transport.ts +180 -10
  288. package/src/runtime/root/package-connector-service.ts +186 -8
  289. package/src/runtime/root/package-runtime-release.ts +143 -17
  290. package/src/runtime/root/package-runtime-service.ts +430 -22
  291. package/src/runtime/root/release-manifest.ts +7 -2
  292. package/src/runtime/root/release-materialize.ts +19 -0
  293. package/src/runtime/root/release-store.ts +48 -11
  294. package/src/runtime/root/runtime.ts +23 -5
  295. package/src/runtime/root/service.ts +100 -29
  296. package/src/runtime/root/types.ts +2 -0
  297. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  298. package/src/runtime/shared/trusted-executable.ts +62 -0
  299. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  300. package/src/runtime/standalone-recovery/core.ts +700 -92
  301. package/src/runtime/standalone-recovery/entry.ts +99 -23
  302. package/src/runtime/standalone-recovery/installer.ts +19 -3
  303. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  304. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  305. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  306. package/src/runtime/workflow/schedules/engine.ts +287 -151
  307. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  308. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  309. package/src/runtime/workflow/schedules/store.ts +131 -10
  310. package/src/runtime/workflow/schedules/types.ts +2 -4
  311. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  312. package/src/testing/test-governance.ts +8 -40
  313. package/assets/reference-configs/heartbeat-triage.md +0 -57
  314. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  315. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  316. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  317. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  318. package/docs/architecture/current/architecture-invariants.md +0 -331
  319. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  320. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  321. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  322. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  323. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  324. package/docs/architecture/current/entity-model.md +0 -612
  325. package/docs/architecture/current/external-plugin-governance.md +0 -396
  326. package/docs/architecture/current/failure-recovery.md +0 -447
  327. package/docs/architecture/current/governance.md +0 -213
  328. package/docs/architecture/current/human-interaction-plane.md +0 -87
  329. package/docs/architecture/current/implementation-status.md +0 -128
  330. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  331. package/docs/architecture/current/legacy-governance.md +0 -58
  332. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  333. package/docs/architecture/current/migration-roadmap.md +0 -105
  334. package/docs/architecture/current/multi-repository-execution.md +0 -371
  335. package/docs/architecture/current/operations-runbook.md +0 -45
  336. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  337. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  338. package/docs/architecture/current/repository-access-modes.md +0 -102
  339. package/docs/architecture/current/route-policy.md +0 -15
  340. package/docs/architecture/current/route-session-concurrency.md +0 -85
  341. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  342. package/docs/architecture/current/runtime-directory-map.md +0 -49
  343. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  344. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  345. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  346. package/docs/architecture/current/system-overview.md +0 -169
  347. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  348. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  349. package/docs/architecture/current/thin-harness-v1.md +0 -321
  350. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  351. package/docs/forge-autonomous-goal-loop.md +0 -9
  352. package/docs/forge-provider-configuration.md +0 -7
  353. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  354. package/docs/operations/gmail-assistant-routines.md +0 -186
  355. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  356. package/docs/operations/provider-configuration.md +0 -31
  357. package/scripts/harness-trace-grade.sh +0 -150
  358. package/scripts/heartbeat-triage.sh +0 -361
  359. package/scripts/maintenance-triage.sh +0 -167
  360. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  361. package/src/cli/repositories/goal-registry.ts +0 -239
  362. package/src/runtime/assistant/action-proposals.ts +0 -279
  363. package/src/runtime/assistant/intent.ts +0 -395
  364. package/src/runtime/assistant/model-provider.ts +0 -460
  365. package/src/runtime/assistant/openapi.ts +0 -223
  366. package/src/runtime/assistant/policy.ts +0 -89
  367. package/src/runtime/assistant/readiness.ts +0 -258
  368. package/src/runtime/assistant/routine-runtime.ts +0 -477
  369. package/src/runtime/assistant/schedule-binding.ts +0 -171
  370. package/src/runtime/assistant/standing-grants.ts +0 -375
  371. package/src/runtime/assistant/store.ts +0 -243
  372. package/src/runtime/assistant/types.ts +0 -135
  373. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  374. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  375. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  376. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  377. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  378. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  379. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  380. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  381. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  382. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  383. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  384. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  385. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  386. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  387. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  388. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  389. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  390. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  391. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  392. package/src/runtime/workflow/findings/store.ts +0 -204
  393. package/src/runtime/workflow/findings/types.ts +0 -58
  394. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  395. package/src/runtime/workflow/portfolio/store.ts +0 -185
  396. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -25,7 +25,7 @@ export interface McpLocalConfig {
25
25
  localEndpoint?: string;
26
26
  };
27
27
  profile?: string;
28
- toolset?: 'core' | 'advanced' | 'full';
28
+ toolset?: 'facade' | 'core' | 'advanced' | 'full';
29
29
  /** Compatibility marker retained for previously persisted toolset choices. */
30
30
  toolsetExplicit?: boolean;
31
31
  accessMode?: AccessMode;
@@ -88,7 +88,7 @@ export interface McpRuntimeState {
88
88
  forgeVersion?: string;
89
89
  toolSurfaceFingerprint?: string;
90
90
  runtimeToolSurfaceFingerprint?: string;
91
- toolset?: 'core' | 'advanced' | 'full';
91
+ toolset?: 'facade' | 'core' | 'advanced' | 'full';
92
92
  configuredAccessMode?: AccessMode;
93
93
  effectiveAccessMode?: AccessMode;
94
94
  effectiveToolset?: McpToolset;
@@ -131,7 +131,7 @@ export interface McpRuntimeState {
131
131
  }>;
132
132
  }
133
133
 
134
- export type McpHttpAuthMode = 'oauth' | 'bearer';
134
+ export type McpHttpAuthMode = 'oauth' | 'bearer' | 'none';
135
135
 
136
136
  export function mcpLocalConfigPath(repoRoot: string): string {
137
137
  return join(repoRoot, '.forge', 'mcp.local.json');
@@ -356,8 +356,8 @@ export function syncMcpControllerHomeBearerToken(
356
356
 
357
357
  export function parseMcpHttpAuthMode(value: string | undefined): McpHttpAuthMode {
358
358
  const mode = (value ?? 'oauth').trim().toLowerCase();
359
- if (mode === 'oauth' || mode === 'bearer') return mode;
360
- throw new Error(`invalid --auth "${value}" (expected: oauth, bearer)`);
359
+ if (mode === 'oauth' || mode === 'bearer' || mode === 'none') return mode;
360
+ throw new Error(`invalid --auth "${value}" (expected: oauth, bearer, none)`);
361
361
  }
362
362
 
363
363
  export function readMcpOAuthPassphrase(repoRoot: string): string | null {
@@ -10,10 +10,11 @@ export function mcpServerInstructions(profile: McpProfileName): string {
10
10
  return [
11
11
  ...common,
12
12
  'Use the shortest valid execution path. Investigation or parallelism alone must not create durable Work, Issues, Plans, Agents, isolation, or worktrees; escalate only for actual recovery, dependency, risk, scope, external-effect, or long-running requirements.',
13
- 'For bounded implementation, use rh_context(operation=search, retrieval_mode=implementation) once as the default code retrieval router when the exact path is unknown. Its current raw snippets and file SHA identities are valid implementation evidence; ChatGPT decides semantic sufficiency and should request wider exact ranges only when impact ambiguity, truncation, drift, or an expansion signal can materially change the edit. For cross-cutting state/time/schema/cache/API/event/concurrency changes, ChatGPT should select relevant impact_domains in that first search so Forge expands those evidence dimensions mechanically in the same call; Forge never infers that the selected domains are semantically complete. Use retrieval_mode=plan|debug|review when the task intentionally needs a broader evidence surface. Do not fall back to shell grep unless retrieval is unavailable or raw Git semantics are explicitly needed. The low-level search_repository handler is compatibility/internal surface, not a second default search path. Prefer repository_command_execute for ordinary local Git and shell operations, repository_safe_patch_apply for bounded source changes, and rh_work only when the task genuinely requires durable orchestration.',
13
+ 'For bounded implementation, start with rh_context(operation=search, retrieval_mode=implementation) as the default code retrieval router when the exact path is unknown, then repeat it with exact paths, symbols, tests, relationships, or impact domains whenever more evidence can materially improve correctness. Its current raw snippets and file SHA identities are valid implementation evidence; ChatGPT alone decides semantic sufficiency. For cross-cutting state/time/schema/cache/API/event/concurrency changes, ChatGPT should select relevant impact_domains so Forge expands those evidence dimensions mechanically; Forge never infers that the selected domains are semantically complete. Use retrieval_mode=plan|debug|review when the task intentionally needs a broader evidence surface. Do not fall back to shell grep unless retrieval is unavailable or raw Git semantics are explicitly needed. The low-level search_repository handler is compatibility/internal surface, not a second default search path. Prefer repository_command_execute for ordinary local Git and shell operations, repository_safe_patch_apply for bounded source changes, and rh_work only when the task genuinely requires durable orchestration.',
14
14
  'Do not preflight unrelated Git history, prior Work, plugins, permissions, TCC, browser mode, maintenance, or recovery state. Inspect them only when the requested operation depends on them or a real failure points there.',
15
+ 'Minimize mechanically redundant MCP round trips. For an ongoing Forge-controlled task, prefer one rh_status(summary) as the initial controller snapshot: it includes bounded readiness, repository identity, top active Work, active Plans, and pending handoffs. Do not immediately follow it with rh_work(plan_list) or rh_context(list/get) unless the next decision needs details that the snapshot does not contain. Use rh_context(work_id=..., detail_level=summary) for one Work, and request detail/raw or capability hydration only when that extra evidence can change the decision.',
15
16
  'Run independent reads, searches, diagnostics, checks, repositories, checkouts, and tasks concurrently by default. Serialize only on a real dependency or Resource Claim conflict, and reuse existing projections, process handles, and check evidence instead of repeating successful observations.',
16
- 'Validation must be proportional to the change: cheap patch-safety checks belong to every coherent edit batch, focused tests run after that batch is stable, and expensive full-suite checks belong at candidate/release or explicitly high-risk boundaries. When a Direct Edit batch is stable, repository_safe_patch_apply may receive explicit check_ids to start revision-bound validation; omit check_ids during intermediate edits. Long checks return managed Processes: continue independent read/review work, then use validation_only with the returned edit-session/validation ids as the synchronization join instead of polling repeatedly. Do not perform maintenance or deep runtime diagnosis before ordinary work unless execution is actually blocked.',
17
+ 'Validation must be proportional to the change: cheap patch-safety checks belong to every coherent edit batch, focused tests run after that batch is stable, and expensive full-suite checks belong at candidate/release or explicitly high-risk boundaries. When a Direct Edit batch is stable, repository_safe_patch_apply may receive explicit check_ids to start revision-bound validation; omit check_ids during intermediate edits. Long checks return managed Processes: continue independent read/review work. Do not call process_wait while useful independent work remains. Use process_get only when a non-blocking observation can change the next decision; when the exact check result becomes a dependency, attach with process_wait (or validation_only with the returned edit-session/validation ids). An attach is transport-bounded: if it returns a running handle, resume useful work and attach again only at a later real dependency boundary, never as periodic polling. Do not perform maintenance or deep runtime diagnosis before ordinary work unless execution is actually blocked.',
17
18
  'repository_safe_patch_apply returns bounded edit-session diff evidence for semantic review. Review that evidence directly instead of issuing a mandatory follow-up diff/status call; expand it only when truncated or semantically ambiguous. After a bounded successful change, finish the requested delivery directly. Avoid additional investigation whose result cannot change implementation or acceptance, and do not invoke hidden atomic compatibility tools merely because they exist in the full profile.',
18
19
  ].join(' ');
19
20
  }
@@ -113,7 +113,6 @@ import type {
113
113
  import {
114
114
  gitDiff,
115
115
  gitSnapshot,
116
- readRepositoryRange,
117
116
  searchRepository,
118
117
  } from "../repository/inspector";
119
118
  import {
@@ -1634,29 +1633,6 @@ export function buildMcpToolDefinitions(
1634
1633
  },
1635
1634
  annotations: readOnly,
1636
1635
  },
1637
- {
1638
- name: "controller_context_pack",
1639
- description:
1640
- "Build a bounded code context pack for a task: relevant files, search-hit line ranges, and raw snippets. This is for scoped investigation, not blind implementation.",
1641
- inputSchema: {
1642
- type: "object",
1643
- properties: {
1644
- description: { type: "string" },
1645
- issue_id: { type: "string" },
1646
- task_id: { type: "string" },
1647
- known_paths: { type: "array", items: { type: "string" } },
1648
- include_globs: { type: "array", items: { type: "string" } },
1649
- exclude_globs: { type: "array", items: { type: "string" } },
1650
- search_terms: { type: "array", items: { type: "string" } },
1651
- max_files: { type: "number" },
1652
- max_snippets: { type: "number" },
1653
- max_chars_per_snippet: { type: "number" },
1654
- structural_context: { type: "string", enum: ["off", "auto", "required"], description: "Optional CodeGraph structural context. Defaults to off so ordinary Direct work pays no graph startup cost." },
1655
- },
1656
- additionalProperties: false,
1657
- },
1658
- annotations: readOnly,
1659
- },
1660
1636
  {
1661
1637
  name: "quick_agent_session",
1662
1638
  description:
@@ -1807,22 +1783,6 @@ export function buildMcpToolDefinitions(
1807
1783
  },
1808
1784
  annotations: readOnly,
1809
1785
  },
1810
- {
1811
- name: "read_repository_file",
1812
- description:
1813
- "Read a line range from one policy-readable repository file.",
1814
- inputSchema: {
1815
- type: "object",
1816
- properties: {
1817
- path: { type: "string" },
1818
- start_line: { type: "number" },
1819
- end_line: { type: "number" },
1820
- },
1821
- required: ["path"],
1822
- additionalProperties: false,
1823
- },
1824
- annotations: readOnly,
1825
- },
1826
1786
  {
1827
1787
  name: "get_git_diff",
1828
1788
  description:
@@ -1844,25 +1804,6 @@ export function buildMcpToolDefinitions(
1844
1804
  inputSchema: EMPTY_SCHEMA,
1845
1805
  annotations: readOnly,
1846
1806
  },
1847
- {
1848
- name: "run_check",
1849
- description:
1850
- "Start one named focused repository check as a Local Job and return immediately; inspect it with get_local_job.",
1851
- inputSchema: {
1852
- type: "object",
1853
- properties: {
1854
- check_id: { type: "string" },
1855
- timeout_ms: { type: "number" },
1856
- },
1857
- required: ["check_id"],
1858
- additionalProperties: false,
1859
- },
1860
- annotations: {
1861
- readOnlyHint: false,
1862
- openWorldHint: false,
1863
- destructiveHint: false,
1864
- },
1865
- },
1866
1807
  {
1867
1808
  name: "list_issues",
1868
1809
  description: "List controller-managed issues and task summaries.",
@@ -3306,28 +3247,6 @@ export async function callMcpTool(
3306
3247
  audit(ctx, name, "ok", args);
3307
3248
  return textResult(payload);
3308
3249
  }
3309
- case "controller_context_pack": {
3310
- if (ctx.policy.profile !== "controller")
3311
- return errorResult(
3312
- "TOOL_DISABLED",
3313
- "controller_context_pack requires the controller profile",
3314
- );
3315
- const pack = buildControllerContextPack(ctx.repoRoot, ctx.policy, {
3316
- description: typeof args.description === "string" ? args.description : undefined,
3317
- issueId: typeof args.issue_id === "string" ? args.issue_id : undefined,
3318
- taskId: typeof args.task_id === "string" ? args.task_id : undefined,
3319
- knownPaths: stringList(args.known_paths),
3320
- includeGlobs: stringList(args.include_globs),
3321
- excludeGlobs: stringList(args.exclude_globs),
3322
- searchTerms: stringList(args.search_terms),
3323
- maxFiles: typeof args.max_files === "number" ? args.max_files : undefined,
3324
- maxSnippets: typeof args.max_snippets === "number" ? args.max_snippets : undefined,
3325
- maxCharsPerSnippet: typeof args.max_chars_per_snippet === "number" ? args.max_chars_per_snippet : undefined,
3326
- structuralContext: args.structural_context === "auto" || args.structural_context === "required" ? args.structural_context : "off",
3327
- });
3328
- audit(ctx, name, "ok", args);
3329
- return textResult(pack);
3330
- }
3331
3250
  case "controller_context": {
3332
3251
  if (ctx.policy.profile !== "controller")
3333
3252
  return errorResult(
@@ -3563,36 +3482,6 @@ export async function callMcpTool(
3563
3482
  audit(ctx, name, "ok", args);
3564
3483
  return textResult(result);
3565
3484
  }
3566
- case "read_repository_file": {
3567
- if (ctx.policy.profile !== "controller")
3568
- return errorResult(
3569
- "TOOL_DISABLED",
3570
- "read_repository_file requires the controller profile",
3571
- );
3572
- const path = String(args.path ?? "");
3573
- const session = ctx.sessionId && ctx.repoId
3574
- ? {
3575
- sessionId: ctx.sessionId,
3576
- repoId: ctx.repoId,
3577
- checkoutId: ctx.checkoutId ?? ctx.repoId,
3578
- }
3579
- : undefined;
3580
- const result = readRepositoryRange(
3581
- ctx.repoRoot,
3582
- ctx.policy,
3583
- path,
3584
- typeof args.start_line === "number" ? args.start_line : 1,
3585
- typeof args.end_line === "number" ? args.end_line : 200,
3586
- session,
3587
- );
3588
- const redacted = redactMcpText(result.content);
3589
- audit(ctx, name, "ok", args, result.path);
3590
- return textResult({
3591
- ...result,
3592
- content: redacted.text,
3593
- redactions: redacted.redactions,
3594
- });
3595
- }
3596
3485
  case "get_git_diff": {
3597
3486
  if (ctx.policy.profile !== "controller")
3598
3487
  return errorResult(
@@ -3640,84 +3529,6 @@ export async function callMcpTool(
3640
3529
  audit(ctx, name, "ok", args);
3641
3530
  return textResult({ checks });
3642
3531
  }
3643
- case "run_check": {
3644
- if (ctx.policy.profile !== "controller")
3645
- return errorResult(
3646
- "TOOL_DISABLED",
3647
- "run_check requires the controller profile",
3648
- );
3649
- // Prefer Unified Process Runtime for ordinary checks. Multi-phase and
3650
- // release checks require an explicit external Controller instead.
3651
- const checkId = String(args.check_id ?? "").trim();
3652
- const forceDurable = args.force_durable === true
3653
- || args.apply_mode === "async"
3654
- || args.mode === "durable";
3655
- if (!forceDurable && checkId) {
3656
- try {
3657
- const { checkRequiresDurableWorkflow, runCheckViaProcessRuntime } = await import(
3658
- "../../runtime/execution/process-runtime"
3659
- );
3660
- const { resolveRepoPreferredControllerHome } = await import("../repositories/controller-home");
3661
- const { resolveRepositorySelection } = await import("../repositories/registry");
3662
- if (!checkRequiresDurableWorkflow(checkId)) {
3663
- const controllerHome = resolveRepoPreferredControllerHome(ctx.repoRoot);
3664
- const repository = resolveRepositorySelection({
3665
- explicitPath: ctx.repoRoot,
3666
- controllerHome,
3667
- allowSoleRepository: true,
3668
- });
3669
- if (repository) {
3670
- const facade = await runCheckViaProcessRuntime({
3671
- controllerHome,
3672
- repoId: repository.repoId,
3673
- checkoutId: repository.activeCheckoutId,
3674
- repoRoot: repository.canonicalRoot,
3675
- checkId,
3676
- timeoutMs: typeof args.timeout_ms === "number" ? args.timeout_ms : undefined,
3677
- requestId: typeof args.request_id === "string" ? args.request_id.trim() || undefined : undefined,
3678
- executionIdentity: {
3679
- schemaVersion: 1,
3680
- repositoryId: repository.repoId,
3681
- checkoutId: repository.activeCheckoutId,
3682
- canonicalRoot: repository.canonicalRoot,
3683
- },
3684
- });
3685
- if (facade.mode !== "durable" && facade.process) {
3686
- audit(ctx, name, "ok", args);
3687
- return textResult({
3688
- mode: facade.mode,
3689
- path: facade.mode === "direct" ? "process_direct" : "process_managed",
3690
- checkId: facade.checkId,
3691
- processId: facade.process.processId,
3692
- status: facade.process.status,
3693
- completed: facade.process.completed === true,
3694
- ok: facade.process.ok,
3695
- exitCode: facade.process.exitCode,
3696
- timedOut: facade.process.timedOut,
3697
- stdout: facade.process.stdout,
3698
- stderr: facade.process.stderr,
3699
- durableSideEffects: facade.durableSideEffects,
3700
- next: facade.process.completed
3701
- ? "Check finished on Process Runtime without LocalBridgeJob / ExecutionJob / Worker."
3702
- : `Managed process ${facade.process.processId} still running; poll process_get / process_wait / process_logs.`,
3703
- });
3704
- }
3705
- }
3706
- }
3707
- } catch (error) {
3708
- const message = error instanceof Error ? error.message : String(error);
3709
- const code = message.includes(":") ? message.slice(0, message.indexOf(":")) : "PROCESS_RUNTIME_FAILED";
3710
- audit(ctx, name, "failed", args, undefined, message);
3711
- return errorResult(code, message, { checkId });
3712
- }
3713
- }
3714
- audit(ctx, name, "blocked", args);
3715
- return errorResult(
3716
- "PROCESS_RUNTIME_REQUIRED",
3717
- "This check cannot create a Local Bridge Job. Run a registered ordinary check through Process Runtime or use external Controller handling for durable checks.",
3718
- { checkId },
3719
- );
3720
- }
3721
3532
  case "list_issues": {
3722
3533
  if (ctx.policy.profile !== "controller")
3723
3534
  return errorResult(
@@ -87,8 +87,8 @@ export function parseMcpToolset(value: unknown, profile: string): McpToolset {
87
87
  if (profile !== 'controller') return 'full';
88
88
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
89
89
  if (!normalized) return 'advanced';
90
- if (normalized === 'core' || normalized === 'advanced' || normalized === 'full') return normalized;
91
- throw new Error(`invalid MCP toolset "${String(value)}" (expected: core, advanced, or full)`);
90
+ if (normalized === 'facade' || normalized === 'core' || normalized === 'advanced' || normalized === 'full') return normalized;
91
+ throw new Error(`invalid MCP toolset "${String(value)}" (expected: facade, core, advanced, or full)`);
92
92
  }
93
93
 
94
94
  function repositoryForExplicitPath(path: string, controllerHome: string): RepositoryRecord | undefined {
@@ -295,13 +295,24 @@ export function repositoryScopedToolArgs(
295
295
  input: Record<string, unknown>,
296
296
  repository: RepositoryRecord,
297
297
  ): Record<string, unknown> {
298
- // Preserve resolved repository/checkout identity for every downstream call.
299
- // Deleting these fields forced re-resolution and allowed silent route drift.
298
+ // Preserve resolved repository identity for every downstream call. Exact
299
+ // claimed Work calls are the one exception for checkout injection: when the
300
+ // caller omitted checkout_id, repository tools must resolve the Work-owned
301
+ // checkout after validating the authenticated principal instead of inheriting
302
+ // the repository's currently active checkout.
303
+ const workOwnedCheckoutResolution = [
304
+ 'repository_command_preview',
305
+ 'repository_command_execute',
306
+ 'repository_safe_patch_apply',
307
+ ].includes(name)
308
+ && typeof input.work_id === 'string'
309
+ && input.work_id.trim().length > 0
310
+ && !(typeof input.checkout_id === 'string' && input.checkout_id.trim().length > 0);
300
311
  const args: Record<string, unknown> = {
301
312
  ...input,
302
313
  repo_id: repository.repoId,
303
- checkout_id: repository.activeCheckoutId,
304
314
  };
315
+ if (!workOwnedCheckoutResolution) args.checkout_id = repository.activeCheckoutId;
305
316
 
306
317
  const github = repository.github;
307
318
  const githubRepo = github ? `${github.owner}/${github.repo}` : undefined;
@@ -1,5 +1,16 @@
1
1
  import { randomUUID } from 'crypto';
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from 'fs';
2
+ import {
3
+ chmodSync,
4
+ closeSync,
5
+ existsSync,
6
+ fsyncSync,
7
+ mkdirSync,
8
+ openSync,
9
+ readFileSync,
10
+ renameSync,
11
+ rmSync,
12
+ writeFileSync,
13
+ } from 'fs';
3
14
  import { dirname } from 'path';
4
15
  import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js';
5
16
  import { InvalidGrantError, InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
@@ -25,6 +36,20 @@ function issueToken(): string {
25
36
  return randomUUID();
26
37
  }
27
38
 
39
+ function fsyncFile(path: string): void {
40
+ const fd = openSync(path, 'r');
41
+ try { fsyncSync(fd); } finally { closeSync(fd); }
42
+ }
43
+
44
+ function fsyncDirectory(path: string): void {
45
+ try {
46
+ const fd = openSync(path, 'r');
47
+ try { fsyncSync(fd); } finally { closeSync(fd); }
48
+ } catch {
49
+ // Directory fsync is unavailable on some supported platforms/filesystems.
50
+ }
51
+ }
52
+
28
53
  export class McpOAuthTokenStore implements OAuthRegisteredClientsStore {
29
54
  private accessTokens = new Map<string, AuthInfo>();
30
55
  private refreshTokens = new Map<string, string>();
@@ -33,8 +58,12 @@ export class McpOAuthTokenStore implements OAuthRegisteredClientsStore {
33
58
  constructor(private readonly path: string, private readonly fallbackPaths: string[] = []) {}
34
59
 
35
60
  load(): void {
61
+ this.accessTokens.clear();
62
+ this.refreshTokens.clear();
63
+ this.clients.clear();
36
64
  let loaded = false;
37
65
  let mergedFallback = false;
66
+ let primaryCorrupt = false;
38
67
  for (const [index, path] of [this.path, ...this.fallbackPaths].entries()) {
39
68
  if (!existsSync(path)) continue;
40
69
  try {
@@ -59,22 +88,35 @@ export class McpOAuthTokenStore implements OAuthRegisteredClientsStore {
59
88
  }
60
89
  loaded = true;
61
90
  } catch (_error) {
62
- // Corrupt local auth state should not prevent starting the server.
91
+ if (index === 0) primaryCorrupt = true;
63
92
  }
64
93
  }
65
- if (loaded && mergedFallback) this.flush();
94
+ if (primaryCorrupt && !loaded) {
95
+ throw new Error('MCP_OAUTH_STORE_CORRUPT: primary OAuth state is unreadable and no valid fallback is available');
96
+ }
97
+ if (loaded && (mergedFallback || primaryCorrupt)) this.flush();
66
98
  }
67
99
 
68
100
  flush(): void {
69
- mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
70
- chmodSync(dirname(this.path), 0o700);
101
+ const directory = dirname(this.path);
102
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
103
+ chmodSync(directory, 0o700);
71
104
  const data: TokenData = {
72
105
  accessTokens: Object.fromEntries(this.accessTokens),
73
106
  refreshTokens: Object.fromEntries(this.refreshTokens),
74
107
  clients: Object.fromEntries(this.clients),
75
108
  };
76
- writeFileSync(this.path, `${JSON.stringify(data, null, 2)}\n`, { encoding: 'utf-8', mode: 0o600 });
77
- chmodSync(this.path, 0o600);
109
+ const temporary = `${this.path}.${process.pid}.${randomUUID()}.tmp`;
110
+ try {
111
+ writeFileSync(temporary, `${JSON.stringify(data, null, 2)}\n`, { encoding: 'utf-8', mode: 0o600 });
112
+ chmodSync(temporary, 0o600);
113
+ fsyncFile(temporary);
114
+ renameSync(temporary, this.path);
115
+ chmodSync(this.path, 0o600);
116
+ fsyncDirectory(directory);
117
+ } finally {
118
+ rmSync(temporary, { force: true });
119
+ }
78
120
  }
79
121
 
80
122
  getClient(clientId: string): OAuthClientInformationFull | undefined {
@@ -127,10 +169,115 @@ export class McpOAuthTokenStore implements OAuthRegisteredClientsStore {
127
169
  }
128
170
  return undefined;
129
171
  }
172
+
173
+ setTokenPair(accessToken: string, info: AuthInfo, refreshToken: string): void {
174
+ this.accessTokens.set(accessToken, info);
175
+ this.refreshTokens.set(refreshToken, accessToken);
176
+ this.flush();
177
+ }
178
+
179
+ rotateRefreshToken(
180
+ previousRefreshToken: string,
181
+ nextRefreshToken: string,
182
+ accessToken: string,
183
+ info: AuthInfo,
184
+ ): void {
185
+ this.refreshTokens.delete(previousRefreshToken);
186
+ this.accessTokens.set(accessToken, info);
187
+ this.refreshTokens.set(nextRefreshToken, accessToken);
188
+ this.flush();
189
+ }
190
+
191
+ revokeToken(token: string): void {
192
+ const linkedAccessToken = this.refreshTokens.get(token);
193
+ if (linkedAccessToken) {
194
+ this.refreshTokens.delete(token);
195
+ this.accessTokens.delete(linkedAccessToken);
196
+ for (const [refreshToken, accessToken] of this.refreshTokens) {
197
+ if (accessToken === linkedAccessToken) this.refreshTokens.delete(refreshToken);
198
+ }
199
+ this.flush();
200
+ return;
201
+ }
202
+
203
+ this.accessTokens.delete(token);
204
+ for (const [refreshToken, accessToken] of this.refreshTokens) {
205
+ if (accessToken === token) this.refreshTokens.delete(refreshToken);
206
+ }
207
+ this.flush();
208
+ }
130
209
  }
131
210
 
132
- export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerProvider {
133
- const authCodes = new Map<string, { challenge: string; clientId: string }>();
211
+ interface PendingAuthorizationCode {
212
+ challenge: string;
213
+ clientId: string;
214
+ issuedAt: number;
215
+ expiresAt: number;
216
+ }
217
+
218
+ export interface McpOAuthProviderOptions {
219
+ now?: () => number;
220
+ authorizationCodeTtlSeconds?: number;
221
+ maxPendingAuthorizationCodes?: number;
222
+ maxPendingAuthorizationCodesPerClient?: number;
223
+ }
224
+
225
+ const DEFAULT_AUTHORIZATION_CODE_TTL_SECONDS = 10 * 60;
226
+ const DEFAULT_MAX_PENDING_AUTHORIZATION_CODES = 512;
227
+ const DEFAULT_MAX_PENDING_AUTHORIZATION_CODES_PER_CLIENT = 32;
228
+
229
+ export function createMcpOAuthProvider(
230
+ store: McpOAuthTokenStore,
231
+ options: McpOAuthProviderOptions = {},
232
+ ): OAuthServerProvider {
233
+ const authCodes = new Map<string, PendingAuthorizationCode>();
234
+ const currentTime = options.now ?? nowSeconds;
235
+ const ttlSeconds = Math.max(30, Math.trunc(options.authorizationCodeTtlSeconds ?? DEFAULT_AUTHORIZATION_CODE_TTL_SECONDS));
236
+ const globalLimit = Math.max(8, Math.trunc(options.maxPendingAuthorizationCodes ?? DEFAULT_MAX_PENDING_AUTHORIZATION_CODES));
237
+ const clientLimit = Math.max(2, Math.min(globalLimit, Math.trunc(
238
+ options.maxPendingAuthorizationCodesPerClient ?? DEFAULT_MAX_PENDING_AUTHORIZATION_CODES_PER_CLIENT,
239
+ )));
240
+
241
+ const purgeExpiredCodes = (now = currentTime()): void => {
242
+ for (const [code, pending] of authCodes) {
243
+ if (pending.expiresAt <= now) authCodes.delete(code);
244
+ }
245
+ };
246
+
247
+ const evictOldest = (predicate: (entry: PendingAuthorizationCode) => boolean): boolean => {
248
+ let oldest: { code: string; issuedAt: number } | undefined;
249
+ for (const [code, pending] of authCodes) {
250
+ if (!predicate(pending)) continue;
251
+ if (!oldest || pending.issuedAt < oldest.issuedAt) oldest = { code, issuedAt: pending.issuedAt };
252
+ }
253
+ if (!oldest) return false;
254
+ authCodes.delete(oldest.code);
255
+ return true;
256
+ };
257
+
258
+ const reserveAuthorizationCodeCapacity = (clientId: string, now: number): void => {
259
+ purgeExpiredCodes(now);
260
+ while ([...authCodes.values()].filter((pending) => pending.clientId === clientId).length >= clientLimit) {
261
+ if (!evictOldest((pending) => pending.clientId === clientId)) break;
262
+ }
263
+ while (authCodes.size >= globalLimit) {
264
+ if (!evictOldest(() => true)) break;
265
+ }
266
+ };
267
+
268
+ const pendingAuthorizationCode = (
269
+ client: OAuthClientInformationFull,
270
+ authorizationCode: string,
271
+ ): PendingAuthorizationCode => {
272
+ const now = currentTime();
273
+ purgeExpiredCodes(now);
274
+ const stored = authCodes.get(authorizationCode);
275
+ if (!stored || stored.clientId !== client.client_id || stored.expiresAt <= now) {
276
+ if (stored?.expiresAt !== undefined && stored.expiresAt <= now) authCodes.delete(authorizationCode);
277
+ throw new InvalidGrantError('Invalid authorization code');
278
+ }
279
+ return stored;
280
+ };
134
281
 
135
282
  return {
136
283
  get clientsStore(): OAuthRegisteredClientsStore {
@@ -138,10 +285,14 @@ export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerPr
138
285
  },
139
286
 
140
287
  async authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res): Promise<void> {
288
+ const now = currentTime();
289
+ reserveAuthorizationCodeCapacity(client.client_id, now);
141
290
  const code = issueToken();
142
291
  authCodes.set(code, {
143
292
  challenge: params.codeChallenge,
144
293
  clientId: client.client_id,
294
+ issuedAt: now,
295
+ expiresAt: now + ttlSeconds,
145
296
  });
146
297
  const redirectUrl = new URL(params.redirectUri);
147
298
  redirectUrl.searchParams.set('code', code);
@@ -149,10 +300,8 @@ export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerPr
149
300
  res.redirect(302, redirectUrl.toString());
150
301
  },
151
302
 
152
- async challengeForAuthorizationCode(_client: OAuthClientInformationFull, authorizationCode: string): Promise<string> {
153
- const stored = authCodes.get(authorizationCode);
154
- if (!stored) throw new InvalidGrantError('Invalid authorization code');
155
- return stored.challenge;
303
+ async challengeForAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string): Promise<string> {
304
+ return pendingAuthorizationCode(client, authorizationCode).challenge;
156
305
  },
157
306
 
158
307
  async exchangeAuthorizationCode(
@@ -160,22 +309,21 @@ export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerPr
160
309
  authorizationCode: string,
161
310
  _codeVerifier?: string,
162
311
  ): Promise<OAuthTokens> {
163
- const stored = authCodes.get(authorizationCode);
164
- if (!stored || stored.clientId !== client.client_id) {
165
- throw new InvalidGrantError('Invalid authorization code');
166
- }
312
+ pendingAuthorizationCode(client, authorizationCode);
313
+ // Delete before issuing credentials. A failed downstream persistence is
314
+ // retried as a new OAuth flow rather than making an authorization code
315
+ // replayable after a partial token response.
167
316
  authCodes.delete(authorizationCode);
168
317
  const accessToken = issueToken();
169
318
  const refreshToken = issueToken();
170
319
  const expiresIn = 30 * 24 * 60 * 60;
171
320
  const expiresAt = nowSeconds() + expiresIn;
172
- store.setAccessToken(accessToken, {
321
+ store.setTokenPair(accessToken, {
173
322
  token: accessToken,
174
323
  clientId: client.client_id,
175
324
  scopes: ['forge'],
176
325
  expiresAt,
177
- });
178
- store.setRefreshToken(refreshToken, accessToken);
326
+ }, refreshToken);
179
327
  return {
180
328
  access_token: accessToken,
181
329
  token_type: 'Bearer',
@@ -191,11 +339,14 @@ export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerPr
191
339
  if (!accessToken || !existing || existing.clientId !== client.client_id) {
192
340
  throw new InvalidGrantError('Invalid refresh token');
193
341
  }
194
- store.deleteRefreshToken(refreshToken);
195
342
  const nextRefreshToken = issueToken();
196
343
  const expiresIn = 30 * 24 * 60 * 60;
197
- store.setAccessToken(accessToken, { ...existing, expiresAt: nowSeconds() + expiresIn });
198
- store.setRefreshToken(nextRefreshToken, accessToken);
344
+ store.rotateRefreshToken(
345
+ refreshToken,
346
+ nextRefreshToken,
347
+ accessToken,
348
+ { ...existing, expiresAt: nowSeconds() + expiresIn },
349
+ );
199
350
  return {
200
351
  access_token: accessToken,
201
352
  token_type: 'Bearer',
@@ -218,9 +369,7 @@ export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerPr
218
369
  },
219
370
 
220
371
  async revokeToken(_client: OAuthClientInformationFull, request: OAuthTokenRevocationRequest): Promise<void> {
221
- store.deleteAccessToken(request.token);
222
- const refreshToken = store.findRefreshTokenByAccessToken(request.token);
223
- if (refreshToken) store.deleteRefreshToken(refreshToken);
372
+ store.revokeToken(request.token);
224
373
  },
225
374
  };
226
375
  }