@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
@@ -106,6 +106,60 @@ function releasePathFromAuthorityRecord(
106
106
  return canonical(releasePath);
107
107
  }
108
108
 
109
+ function loadPackageConnectorReleaseProtection(controllerHome: string, releasesRoot: string): string | undefined {
110
+ const connectorRoot = join(controllerHome, 'runtime', 'connector-service');
111
+ if (!existsSync(connectorRoot)) return undefined;
112
+ const authorityPath = join(connectorRoot, 'authority.json');
113
+ if (!existsSync(authorityPath)) throw new Error('package connector release authority is missing');
114
+
115
+ const parsed = JSON.parse(readFileSync(authorityPath, 'utf8')) as Record<string, unknown>;
116
+ const releaseId = typeof parsed.releaseId === 'string' ? parsed.releaseId.trim() : '';
117
+ const releaseRootValue = typeof parsed.releaseRoot === 'string' ? parsed.releaseRoot.trim() : '';
118
+ const packageRoot = typeof parsed.packageRoot === 'string' ? parsed.packageRoot.trim() : '';
119
+ const endpoint = typeof parsed.endpoint === 'string' ? parsed.endpoint.trim() : '';
120
+ if (parsed.schemaVersion !== 1 || !releaseId || !releaseRootValue || !packageRoot || !endpoint) {
121
+ throw new Error('package connector release authority is invalid');
122
+ }
123
+ const releaseRoot = resolve(releaseRootValue);
124
+ if (basename(releaseRoot) !== releaseId || !directChild(releasesRoot, releaseRoot) || !existsSync(releaseRoot)) {
125
+ throw new Error('package connector release is outside runtime release authority or missing');
126
+ }
127
+ return canonical(releaseRoot);
128
+ }
129
+
130
+ function loadRecoveryKnownGoodReleaseProtection(controllerHome: string, releasesRoot: string): Set<string> {
131
+ const knownGoodPath = join(controllerHome, 'recovery', 'state', 'known-good.json');
132
+ const protectedPaths = new Set<string>();
133
+ if (!existsSync(knownGoodPath)) return protectedPaths;
134
+ const parsed = JSON.parse(readFileSync(knownGoodPath, 'utf8')) as Record<string, unknown>;
135
+ if (parsed.schemaVersion !== 1 || !Array.isArray(parsed.releases)) {
136
+ throw new Error('standalone recovery known-good authority is invalid');
137
+ }
138
+ for (const raw of parsed.releases) {
139
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('standalone recovery known-good release record is invalid');
140
+ const record = raw as Record<string, unknown>;
141
+ const releaseId = typeof record.revision === 'string' ? record.revision.trim() : '';
142
+ const manifestPathValue = typeof record.path === 'string' ? record.path.trim() : '';
143
+ if (!releaseId || !manifestPathValue) throw new Error('standalone recovery known-good release identity is incomplete');
144
+ const manifestPath = resolve(manifestPathValue);
145
+ const releaseRoot = dirname(manifestPath);
146
+ if (
147
+ basename(manifestPath) !== 'manifest.json'
148
+ || basename(releaseRoot) !== releaseId
149
+ || !directChild(releasesRoot, releaseRoot)
150
+ ) {
151
+ throw new Error('standalone recovery known-good release is outside runtime releases');
152
+ }
153
+ // Known-good history is append-only recovery evidence. A historical record
154
+ // may legitimately outlive the retained release directory after a bounded
155
+ // cleanup pass. Missing historical artifacts therefore protect nothing and
156
+ // must not disable retention for the remaining authoritative releases.
157
+ if (!existsSync(manifestPath) || !existsSync(releaseRoot)) continue;
158
+ protectedPaths.add(canonical(releaseRoot));
159
+ }
160
+ return protectedPaths;
161
+ }
162
+
109
163
  function loadRuntimeProtection(controllerHome: string): RuntimeProtection | undefined {
110
164
  const releasesRoot = join(controllerHome, 'runtime', 'releases');
111
165
  if (!existsSync(releasesRoot)) return undefined;
@@ -124,6 +178,11 @@ function loadRuntimeProtection(controllerHome: string): RuntimeProtection | unde
124
178
  if (previous !== undefined) {
125
179
  releasePaths.add(releasePathFromAuthorityRecord(releasesRoot, previous, 'previous'));
126
180
  }
181
+ const connectorRelease = loadPackageConnectorReleaseProtection(controllerHome, releasesRoot);
182
+ if (connectorRelease) releasePaths.add(connectorRelease);
183
+ for (const knownGoodRelease of loadRecoveryKnownGoodReleaseProtection(controllerHome, releasesRoot)) {
184
+ releasePaths.add(knownGoodRelease);
185
+ }
127
186
 
128
187
  const backupPaths = new Set<string>();
129
188
  let backupAuthoritySafe = true;
@@ -409,8 +468,9 @@ function scanLinkedReleaseFamily(
409
468
  /**
410
469
  * Reclaim immutable Controller release history while preserving the exact
411
470
  * rollback authorities. Runtime keeps active + previous + the previous DB
412
- * backup; Recovery/Supervisor keep current + previous. Missing or malformed
413
- * authority always fails closed.
471
+ * backup plus the immutable release backing the persistent public MCP Gateway;
472
+ * Recovery/Supervisor keep current + previous. Missing or malformed authority
473
+ * always fails closed.
414
474
  */
415
475
  export function cleanupControllerReleaseHistory(
416
476
  controllerHome: string,
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'crypto';
2
2
 
3
- export const ROUTE_POLICY_VERSION = 'route-policy-v3' as const;
3
+ export const ROUTE_POLICY_VERSION = 'route-policy-v4' as const;
4
4
 
5
5
  export type RouteExecutionMode = 'direct_control' | 'goal_workloop' | 'handoff_only';
6
6
  export type RouteWorkMode = 'direct_edit' | 'bounded_work' | 'quick_agent' | 'issue_task';
@@ -238,8 +238,6 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
238
238
  ? false
239
239
  : input.recovery.isolationRequired === true || explicitParallelMode;
240
240
  const requiresRecovery = input.recovery.required === true
241
- || input.intent.requiresLongRunningChecks === true
242
- || explicitMode === 'debug'
243
241
  || explicitMode === 'release'
244
242
  || explicitMode === 'scale';
245
243
  const coordinationRequired = explicitParallelMode || input.intent.requiresIndependentDeliverables === true
@@ -289,26 +287,21 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
289
287
  }
290
288
 
291
289
  if (input.workspace.dirty && mutation) {
292
- reasons.push({ code: 'dirty_workspace_requires_explicit_adoption', message: 'Mutation against a dirty workspace requires an exact reviewed adoption or a clean isolated checkout.' });
293
- return {
294
- executionMode: 'handoff_only', executorKind: 'handoff_only', selectedProviderId: null,
295
- workMode: 'direct_edit', executionPath: 'durable', requiresWork: true, requiresApproval: false,
296
- requiresIsolation: true, requiresRecovery: true, createHandoff: true, waitForUser: false,
297
- approvalState: 'approval_not_required', alternatives: [], ...decisionBase(input, reasons),
298
- };
290
+ reasons.push({
291
+ code: 'dirty_workspace_preserve_existing_changes',
292
+ message: 'The checkout is already dirty; preserve unrelated changes and treat inspected/actual scope as evidence instead of creating Work solely for adoption.',
293
+ });
299
294
  }
300
295
  if (explicitMode) reasons.push({ code: `explicit_${explicitMode}`, message: `Explicit -${explicitMode} mode overrides automatic work topology while policy gates remain authoritative.` });
301
296
  if (protectedPath) reasons.push({ code: 'protected_path', message: 'The predicted scope includes a protected or release-sensitive path.' });
302
297
  if (requiresRecovery) reasons.push({ code: 'recovery_required', message: 'The operation needs resumable Work and bounded recovery.' });
303
298
  if (requiresIsolation) reasons.push({ code: 'isolation_required', message: 'The operation requires an isolated checkout or serialized lane.' });
304
- if (input.intent.requiresLongRunningChecks) reasons.push({ code: 'long_checks', message: 'Long-running checks require durable continuation.' });
299
+ if (input.intent.requiresLongRunningChecks) reasons.push({ code: 'long_checks', message: 'Long-running checks may use a lightweight handle; duration alone does not require durable Work.' });
305
300
  if (input.intent.requiresInvestigation) reasons.push({ code: 'investigation', message: 'Investigation is required before or during implementation.' });
306
301
  if (input.intent.needsDependencies) reasons.push({ code: 'dependencies', message: 'Dependency ordering requires durable Work.' });
307
302
  if (coordinationRequired) reasons.push({ code: 'independent_deliverables', message: 'Multiple independent deliverables require durable PlanContract/Work coordination.' });
308
303
 
309
304
  const explicitBoundedMode = explicitMode === 'plan'
310
- || explicitMode === 'debug'
311
- || explicitMode === 'review'
312
305
  || explicitMode === 'release';
313
306
  const complex = explicitMode === 'direct'
314
307
  ? false
@@ -320,11 +313,9 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
320
313
  || input.capabilities.requiresWorker === true
321
314
  || input.capabilities.requiresExternalEffect === true
322
315
  || input.intent.needsDependencies === true
323
- || protectedPath
324
316
  || destructive
325
317
  || remoteWrite
326
- || secretAccess
327
- || (mutation && (expectedFiles > 4 || expectedChangedLines > 200));
318
+ || secretAccess;
328
319
  const executionMode: RouteExecutionMode = complex ? 'goal_workloop' : 'direct_control';
329
320
  // Work topology is independent from executor/provider choice. Independent or
330
321
  // parallel deliverables stay in the durable Goal Workloop and are decomposed by
@@ -1,6 +1,5 @@
1
1
  import { resolveControllerHome } from '../../cli/repositories/controller-home';
2
2
  import { readSchedulerHealthSnapshot } from './global-scheduler/scheduler';
3
- import type { ControllerStartupRecoveryResult } from './startup-recovery';
4
3
  import type { RuntimeSourceIdentity } from './runtime-generation';
5
4
  import { readRuntimeOwner } from '../root/ownership';
6
5
  import { observeRuntimeStatus } from '../root/status';
@@ -26,7 +25,6 @@ export interface ForgeRuntimeStatus {
26
25
  gatewaySeparated?: boolean;
27
26
  workerIsolation?: boolean;
28
27
  degraded?: boolean;
29
- recovery?: ControllerStartupRecoveryResult;
30
28
  generation?: string;
31
29
  source?: RuntimeSourceIdentity;
32
30
  restartRequired?: boolean;
@@ -7,6 +7,10 @@ export interface McpTimingTrace {
7
7
  traceId?: string;
8
8
  requestId?: string;
9
9
  rpcId?: string | number;
10
+ /** Public Connector ingress versus the authoritative loopback Runtime span. */
11
+ layer?: 'public_gateway' | 'canonical_runtime';
12
+ /** Wall-clock start for joining nested spans and later client-side timing when available. */
13
+ startedAt?: string;
10
14
  outcome?: 'ok' | 'error' | 'exception';
11
15
  errorCode?: string;
12
16
  sessionResolutionMs?: number;
@@ -17,10 +21,25 @@ export interface McpTimingTrace {
17
21
  commandExecutionMs?: number;
18
22
  resultSerializationMs?: number;
19
23
  resultPersistenceMs?: number;
24
+ /** Public Gateway only: resolve current Canonical Runtime identity/token/endpoint. */
25
+ gatewayProxyResolveMs?: number;
26
+ /** Public Gateway only: establish or await a Canonical Runtime MCP client connection. */
27
+ gatewayProxyConnectMs?: number;
28
+ /** Public Gateway only: MCP call duration after a client is acquired, including Canonical Runtime work. */
29
+ gatewayProxyCallMs?: number;
30
+ /** Public Gateway only: time from forwarding the tool call until the Canonical Runtime handler starts. */
31
+ gatewayProxyCanonicalDispatchLagMs?: number;
32
+ /** Canonical Runtime server duration projected back through the proxy response. */
33
+ gatewayProxyCanonicalDurationMs?: number;
34
+ /** Public Gateway only: response/transport tail after canonical handler completion. */
35
+ gatewayProxyReturnMs?: number;
36
+ gatewayProxyConnectionState?: 'reused' | 'cold_connect' | 'coalesced_connect' | 'identity_reconnect';
20
37
  totalToolDurationMs: number;
21
38
  sessionId?: string;
22
39
  repoId?: string;
23
40
  workId?: string;
41
+ processId?: string;
42
+ route?: string;
24
43
  }
25
44
 
26
45
  export interface McpIncident {
@@ -0,0 +1,129 @@
1
+ export type McpTransportTimingScope = 'tool_call' | 'connect_and_tool_call';
2
+
3
+ export interface McpTransportLatencySample {
4
+ label: string;
5
+ tool: string;
6
+ timingScope?: McpTransportTimingScope;
7
+ clientTotalMs: number;
8
+ serverDurationMs: number;
9
+ traceId?: string;
10
+ requestId?: string;
11
+ observedAt?: string;
12
+ }
13
+
14
+ export interface McpTransportLatencyStats {
15
+ p50: number;
16
+ p95: number;
17
+ max: number;
18
+ }
19
+
20
+ export interface McpTransportLatencyGroup {
21
+ label: string;
22
+ tool: string;
23
+ timingScope: McpTransportTimingScope;
24
+ sampleCount: number;
25
+ clientTotalMs: McpTransportLatencyStats;
26
+ serverDurationMs: McpTransportLatencyStats;
27
+ externalResidualMs: McpTransportLatencyStats;
28
+ externalSharePct: McpTransportLatencyStats;
29
+ serverExceedsClientCount: number;
30
+ }
31
+
32
+ export interface McpTransportLatencyReport {
33
+ schemaVersion: 1;
34
+ sampleCount: number;
35
+ groups: McpTransportLatencyGroup[];
36
+ }
37
+
38
+ function round(value: number): number {
39
+ return Math.round(value * 100) / 100;
40
+ }
41
+
42
+ function percentile(values: number[], fraction: number): number {
43
+ if (values.length === 0) return 0;
44
+ const sorted = [...values].sort((left, right) => left - right);
45
+ const index = Math.max(0, Math.min(sorted.length - 1, Math.ceil(sorted.length * fraction) - 1));
46
+ return round(sorted[index]!);
47
+ }
48
+
49
+ function stats(values: number[]): McpTransportLatencyStats {
50
+ return {
51
+ p50: percentile(values, 0.5),
52
+ p95: percentile(values, 0.95),
53
+ max: round(Math.max(...values, 0)),
54
+ };
55
+ }
56
+
57
+ function validDuration(value: unknown): value is number {
58
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
59
+ }
60
+
61
+ export function normalizeMcpTransportLatencySample(value: unknown): McpTransportLatencySample {
62
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
63
+ throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: sample must be an object');
64
+ }
65
+ const record = value as Record<string, unknown>;
66
+ const label = typeof record.label === 'string' ? record.label.trim() : '';
67
+ const tool = typeof record.tool === 'string' ? record.tool.trim() : '';
68
+ const timingScope = record.timingScope === undefined || record.timingScope === 'tool_call'
69
+ ? 'tool_call'
70
+ : record.timingScope === 'connect_and_tool_call'
71
+ ? 'connect_and_tool_call'
72
+ : undefined;
73
+ if (!label) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: label is required');
74
+ if (!tool) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: tool is required');
75
+ if (!timingScope) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: timingScope must be tool_call or connect_and_tool_call');
76
+ if (!validDuration(record.clientTotalMs)) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: clientTotalMs must be a non-negative finite number');
77
+ if (!validDuration(record.serverDurationMs)) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: serverDurationMs must be a non-negative finite number');
78
+ return {
79
+ label,
80
+ tool,
81
+ timingScope,
82
+ clientTotalMs: record.clientTotalMs,
83
+ serverDurationMs: record.serverDurationMs,
84
+ ...(typeof record.traceId === 'string' && record.traceId.trim() ? { traceId: record.traceId.trim() } : {}),
85
+ ...(typeof record.requestId === 'string' && record.requestId.trim() ? { requestId: record.requestId.trim() } : {}),
86
+ ...(typeof record.observedAt === 'string' && record.observedAt.trim() ? { observedAt: record.observedAt.trim() } : {}),
87
+ };
88
+ }
89
+
90
+ export function extractForgeServerDuration(result: unknown): number | undefined {
91
+ if (!result || typeof result !== 'object' || Array.isArray(result)) return undefined;
92
+ const structured = (result as Record<string, unknown>).structuredContent;
93
+ if (!structured || typeof structured !== 'object' || Array.isArray(structured)) return undefined;
94
+ const responseMeta = (structured as Record<string, unknown>).responseMeta;
95
+ if (!responseMeta || typeof responseMeta !== 'object' || Array.isArray(responseMeta)) return undefined;
96
+ const duration = (responseMeta as Record<string, unknown>).serverDurationMs;
97
+ return validDuration(duration) ? duration : undefined;
98
+ }
99
+
100
+ export function aggregateMcpTransportLatencySamples(values: unknown[]): McpTransportLatencyReport {
101
+ const samples = values.map(normalizeMcpTransportLatencySample);
102
+ const grouped = new Map<string, McpTransportLatencySample[]>();
103
+ for (const sample of samples) {
104
+ const key = `${sample.label}\u0000${sample.tool}\u0000${sample.timingScope ?? 'tool_call'}`;
105
+ const entries = grouped.get(key) ?? [];
106
+ entries.push(sample);
107
+ grouped.set(key, entries);
108
+ }
109
+ const groups = [...grouped.values()].map((entries): McpTransportLatencyGroup => {
110
+ const client = entries.map((sample) => sample.clientTotalMs);
111
+ const server = entries.map((sample) => sample.serverDurationMs);
112
+ const residual = entries.map((sample) => Math.max(0, sample.clientTotalMs - sample.serverDurationMs));
113
+ const share = entries.map((sample, index) => client[index]! > 0 ? residual[index]! / client[index]! * 100 : 0);
114
+ return {
115
+ label: entries[0]!.label,
116
+ tool: entries[0]!.tool,
117
+ timingScope: entries[0]!.timingScope ?? 'tool_call',
118
+ sampleCount: entries.length,
119
+ clientTotalMs: stats(client),
120
+ serverDurationMs: stats(server),
121
+ externalResidualMs: stats(residual),
122
+ externalSharePct: stats(share),
123
+ serverExceedsClientCount: entries.filter((sample) => sample.serverDurationMs > sample.clientTotalMs).length,
124
+ };
125
+ }).sort((left, right) => left.label.localeCompare(right.label)
126
+ || left.tool.localeCompare(right.tool)
127
+ || left.timingScope.localeCompare(right.timingScope));
128
+ return { schemaVersion: 1, sampleCount: samples.length, groups };
129
+ }
@@ -1,5 +1,5 @@
1
1
  import { execFileSync } from 'child_process';
2
- import { existsSync, lstatSync, readdirSync, rmSync } from 'fs';
2
+ import { existsSync, lstatSync, readdirSync, rmSync, statfsSync } from 'fs';
3
3
  import { tmpdir } from 'os';
4
4
  import { basename, dirname, join, resolve } from 'path';
5
5
 
@@ -46,6 +46,7 @@ export interface RuntimePerformanceDiagnostics {
46
46
  };
47
47
  processes: RuntimeProcessSample[];
48
48
  temp: { scannedRoots: string[]; totalEntries: number; cleanupCandidates: number; entries: RuntimeTempEntry[] };
49
+ storage: { availableBytes?: number; availableGiB?: number; pressure: 'normal' | 'warning' | 'critical' | 'unknown'; managedCacheRoots: string[] };
49
50
  cleanupPreview?: { safeToTerminate: RuntimeProcessSample[]; safeToRemoveTempEntries: RuntimeTempEntry[]; requiresConfirmation: true };
50
51
  truncated: {
51
52
  summary: boolean;
@@ -322,7 +323,27 @@ export function collectRuntimePerformanceDiagnostics(input: DiagnosticsInput): R
322
323
  const temp = input.includeTempDirs === false
323
324
  ? { roots: [] as string[], entries: [] as RuntimeTempEntry[] }
324
325
  : scanRuntimeTempEntries(processes);
326
+ let availableBytes: number | undefined;
327
+ try {
328
+ const fs = statfsSync(input.repoRoot);
329
+ availableBytes = Number(fs.bavail) * Number(fs.bsize);
330
+ } catch {}
331
+ const availableGiB = availableBytes === undefined ? undefined : Math.round((availableBytes / (1024 ** 3)) * 10) / 10;
332
+ const storagePressure: RuntimePerformanceDiagnostics['storage']['pressure'] = availableGiB === undefined
333
+ ? 'unknown'
334
+ : availableGiB < 30
335
+ ? 'critical'
336
+ : availableGiB < 50
337
+ ? 'warning'
338
+ : 'normal';
339
+ const managedCacheRoots = [
340
+ join(input.repoRoot, '.forge', 'ios', 'DerivedData'),
341
+ join(input.repoRoot, '.ai', 'harness'),
342
+ join(input.repoRoot, '.repo-harness'),
343
+ ].filter((path) => existsSync(path));
325
344
  const findings: RuntimePerformanceDiagnostics['findings'] = [];
345
+ if (storagePressure === 'critical') findings.push({ severity: 'critical', code: 'LOW_DISK_SPACE', message: `Only ${availableGiB} GiB of repository volume space is available.` });
346
+ else if (storagePressure === 'warning') findings.push({ severity: 'warning', code: 'LOW_DISK_SPACE', message: `Repository volume free space is ${availableGiB} GiB.` });
326
347
  if (orphanWorkers.length > 0) findings.push({ severity: orphanWorkers.length >= 3 ? 'critical' : 'warning', code: 'ORPHAN_WORKERS', message: 'Detected ' + orphanWorkers.length + ' orphan worker process(es).' });
327
348
  const totalCpu = Math.round(repoProcesses.reduce((sum, process) => sum + process.cpu, 0) * 10) / 10;
328
349
  if (totalCpu >= 100 || highCpu.length >= 3) findings.push({ severity: 'warning', code: 'HIGH_FORGE_CPU', message: 'Forge-related CPU is ' + totalCpu + '%.' });
@@ -341,6 +362,7 @@ export function collectRuntimePerformanceDiagnostics(input: DiagnosticsInput): R
341
362
  ...(orphanWorkers.length ? ['Terminate orphan workers only after reviewing cleanupPreview.safeToTerminate.'] : []),
342
363
  ...(tempCleanupCandidates.length ? ['Remove stale temp entries through runtime_maintenance_apply after reviewing cleanupPreview.safeToRemoveTempEntries.'] : []),
343
364
  ...(highCpu.length ? ['Inspect highCpuProcesses before restarting the controller stack.'] : []),
365
+ ...(storagePressure === 'critical' ? ['Allow Forge generated-cache retention to reclaim stale DerivedData before starting another heavy iOS verification.'] : []),
344
366
  ];
345
367
  return {
346
368
  schemaVersion: 1,
@@ -358,6 +380,7 @@ export function collectRuntimePerformanceDiagnostics(input: DiagnosticsInput): R
358
380
  cleanupCandidates: tempCleanupCandidates.length,
359
381
  entries: temp.entries.slice(0, MAX_DIAGNOSTIC_TEMP_ENTRIES),
360
382
  },
383
+ storage: { availableBytes, availableGiB, pressure: storagePressure, managedCacheRoots },
361
384
  cleanupPreview: input.cleanupPreview === true
362
385
  ? {
363
386
  safeToTerminate: rawCleanupProcesses,
@@ -18,7 +18,7 @@ export interface RuntimeEntityEvent {
18
18
  eventId: string;
19
19
  eventType: string;
20
20
  repoId: string;
21
- entityType: 'job' | 'plugin' | 'schedule' | 'occurrence' | 'portfolio' | 'candidate-finding' | 'release' | 'lease' | 'schedule-decision' | 'assistant-action-proposal' | 'assistant-standing-grant';
21
+ entityType: 'job' | 'plugin' | 'schedule' | 'occurrence' | 'portfolio' | 'release' | 'lease' | 'schedule-decision';
22
22
  entityId: string;
23
23
  correlationId?: string;
24
24
  causationId?: string;
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from 'crypto';
2
2
  import { existsSync, readdirSync } from 'fs';
3
3
  import { join } from 'path';
4
4
  import { ensureControllerHome, ensureRepositoryControllerLayout, repositoryControllerRoot } from '../../../cli/repositories/controller-home';
5
- import { withControllerLock } from '../../../cli/repositories/locks';
5
+ import { releaseControllerLock, tryAcquireControllerLock, withControllerLock } from '../../../cli/repositories/locks';
6
6
  import { appendJobEvent } from '../../evidence/event-ledger';
7
7
  import { writeExecutionArtifact } from '../../evidence/artifact-store';
8
8
  import { markRepositoryProjectionDirty } from '../../projections/invalidation';
@@ -206,7 +206,9 @@ function writeActiveIndex(controllerHome: string, index: ActiveJobIndex): void {
206
206
  writeJsonAtomic(activeIndexPath(controllerHome), {
207
207
  schemaVersion: 1,
208
208
  updatedAt: now(),
209
- jobs: [...deduped.values()].sort((a, b) => a.queuedAt.localeCompare(b.queuedAt)).slice(-5000),
209
+ // Active work is correctness state, not a presentation window. Dropping an
210
+ // entry here makes Scheduler/recovery reads forget a durable non-terminal Job.
211
+ jobs: [...deduped.values()].sort((a, b) => a.queuedAt.localeCompare(b.queuedAt)),
210
212
  } satisfies ActiveJobIndex);
211
213
  }
212
214
 
@@ -258,7 +260,9 @@ export function rebuildExecutionJobIndexes(controllerHome: string, repoIds: stri
258
260
  for (const repoId of targetRepos) {
259
261
  const records = join(executionJobRoot(controllerHome, repoId), 'records');
260
262
  let names: string[] = [];
261
- try { names = readdirSync(records).filter((name) => name.endsWith('.json')).slice(0, 5000); } catch { continue; }
263
+ // Startup repair must rebuild every active and request authority. Only the
264
+ // recent-history projection below is intentionally bounded.
265
+ try { names = readdirSync(records).filter((name) => name.endsWith('.json')); } catch { continue; }
262
266
  for (const name of names) {
263
267
  try {
264
268
  const job = readJsonFile<ExecutionJob>(join(records, name));
@@ -648,11 +652,61 @@ export function listActiveExecutionJobs(controllerHome: string, repoId?: string)
648
652
  try {
649
653
  const job = getExecutionJob(controllerHome, entry.repoId, entry.jobId);
650
654
  if (ACTIVE_JOB_STATUSES.has(job.status)) output.push(job);
651
- } catch { /* stale index repaired below */ }
655
+ } catch { /* maintenance reconciliation owns stale-index repair */ }
652
656
  }
653
657
  return output;
654
658
  }
655
659
 
660
+ /**
661
+ * Maintenance-only active-index convergence. The active index itself is the
662
+ * bounded candidate set; this never scans historical Job directories. Ordinary
663
+ * read APIs remain side-effect free, while the Scheduler can remove terminal or
664
+ * missing entries once and make subsequent idle reads genuinely cheap.
665
+ */
666
+ export function reconcileActiveExecutionJobIndex(
667
+ controllerHome: string,
668
+ repoId?: string,
669
+ ): ExecutionJob[] {
670
+ const key = { scope: 'global' as const, resource: 'execution-index' };
671
+ const owner = `execution-index-active-reconcile:${repoId ?? 'all'}`;
672
+ const acquired = tryAcquireControllerLock(controllerHome, key, owner);
673
+ if (!acquired.acquired) return listActiveExecutionJobs(controllerHome, repoId);
674
+ try {
675
+ const index = readActiveIndex(controllerHome);
676
+ const jobs: ExecutionJob[] = [];
677
+ const retained: ActiveJobIndex['jobs'] = [];
678
+ let removed = 0;
679
+ for (const entry of index.jobs) {
680
+ if (repoId && entry.repoId !== repoId) {
681
+ retained.push(entry);
682
+ continue;
683
+ }
684
+ try {
685
+ const job = getExecutionJob(controllerHome, entry.repoId, entry.jobId);
686
+ if (!ACTIVE_JOB_STATUSES.has(job.status)) {
687
+ removed += 1;
688
+ continue;
689
+ }
690
+ jobs.push(job);
691
+ retained.push({
692
+ jobId: job.jobId,
693
+ repoId: job.repoId,
694
+ status: job.status,
695
+ priority: job.priority,
696
+ queuedAt: job.queuedAt,
697
+ updatedAt: job.updatedAt,
698
+ });
699
+ } catch {
700
+ removed += 1;
701
+ }
702
+ }
703
+ if (removed > 0) writeActiveIndex(controllerHome, { ...index, jobs: retained });
704
+ return jobs;
705
+ } finally {
706
+ releaseControllerLock(controllerHome, key, acquired.lock.lockId);
707
+ }
708
+ }
709
+
656
710
  function executionJobApprovalRequestId(job: ExecutionJob): string | undefined {
657
711
  if (typeof job.result?.approvalRequestId === 'string') return job.result.approvalRequestId;
658
712
  const authorization = job.result?.authorization;
@@ -45,7 +45,7 @@ export interface ResourceClaimSpec {
45
45
  }
46
46
 
47
47
  export interface ExecutionJobOrigin {
48
- surface: 'mcp' | 'local-ui' | 'mobile-intent' | 'chatgpt-action' | 'assistant-routine' | 'standing-grant' | 'cli' | 'schedule' | 'reconciliation' | 'system';
48
+ surface: 'mcp' | 'local-ui' | 'mobile-intent' | 'chatgpt-action' | 'cli' | 'schedule' | 'reconciliation' | 'system';
49
49
  actor?: string;
50
50
  correlationId?: string;
51
51
  causationId?: string;