@moretea-labs/forge 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +39 -0
  3. package/CLAUDE.md +4 -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/02-operating-mode.partial.md +2 -0
  17. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  18. package/assets/plugin-registry.v1.json +3 -3
  19. package/assets/reference-configs/document-generation.md +1 -1
  20. package/assets/reference-configs/harness-overview.md +1 -1
  21. package/assets/reference-configs/sprint-contracts.md +3 -3
  22. package/assets/skill-version.json +2 -2
  23. package/assets/templates/contract.template.md +2 -7
  24. package/assets/templates/helpers/capture-plan.sh +2 -2
  25. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  26. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  27. package/assets/templates/helpers/contract-worktree.sh +1 -21
  28. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  29. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  30. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  31. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  32. package/assets/templates/helpers/new-plan.sh +1 -1
  33. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  34. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  35. package/assets/templates/helpers/verify-contract.sh +1 -111
  36. package/assets/templates/helpers/verify-sprint.sh +3 -180
  37. package/assets/templates/helpers/workflow-contract.ts +5 -1
  38. package/assets/templates/plan.template.md +1 -1
  39. package/assets/templates/review.template.md +9 -34
  40. package/assets/workflow-contract.v1.json +10 -19
  41. package/docs/README.md +10 -10
  42. package/docs/architecture/CURRENT.md +178 -0
  43. package/docs/architecture/EVOLUTION.md +42 -0
  44. package/docs/architecture/current/README.md +4 -156
  45. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  46. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  47. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  48. package/docs/architecture/history/README.md +7 -0
  49. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  50. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  51. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  52. package/docs/architecture/history.md +3 -34
  53. package/docs/architecture/index.md +15 -98
  54. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  55. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  56. package/docs/architecture/versions/1.6.md +38 -0
  57. package/docs/architecture/versions/1.7.md +41 -0
  58. package/docs/forge-chatgpt-browser-engine.md +2 -2
  59. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  60. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  61. package/docs/forge-github-issue-launcher.md +1 -1
  62. package/docs/forge-plugin-management.md +1 -1
  63. package/docs/forge-runtime-self-healing-loop.md +1 -1
  64. package/docs/operations/controller-browser-plugin.md +26 -6
  65. package/docs/operations/controller-performance-and-502.md +143 -0
  66. package/docs/operations/controller-reliability-runbook.md +1 -1
  67. package/docs/operations/features.md +2 -2
  68. package/docs/operations/github-repository.md +1 -1
  69. package/docs/operations/homebrew.md +2 -2
  70. package/docs/operations/mcp-tool-exposure.md +5 -3
  71. package/docs/wiki/Architecture.md +6 -6
  72. package/docs/wiki/Implementation.md +2 -2
  73. package/docs/wiki/Integrations.md +2 -2
  74. package/docs/wiki/Work-Lifecycle.md +1 -1
  75. package/package.json +33 -11
  76. package/scripts/activate-source-baseline.ts +17 -13
  77. package/scripts/architecture-event.ts +2 -820
  78. package/scripts/architecture-queue.sh +2 -645
  79. package/scripts/archive-architecture-request.sh +2 -242
  80. package/scripts/archive-workflow.sh +1 -291
  81. package/scripts/benchmark-background-check-overlap.ts +228 -0
  82. package/scripts/benchmark-mcp-host.sh +117 -0
  83. package/scripts/benchmark-mcp-transport.ts +170 -0
  84. package/scripts/benchmark-quality-harness.ts +146 -0
  85. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  86. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  87. package/scripts/benchmark-typescript-navigation.ts +186 -0
  88. package/scripts/benchmark-work-efficiency.ts +334 -0
  89. package/scripts/capability-config.ts +2 -392
  90. package/scripts/capability-resolver.ts +2 -503
  91. package/scripts/capture-plan.sh +1 -332
  92. package/scripts/check-agent-tooling.sh +2 -1558
  93. package/scripts/check-architecture-sync.sh +2 -371
  94. package/scripts/check-brain-manifest.sh +2 -253
  95. package/scripts/check-context-files.sh +2 -47
  96. package/scripts/check-deploy-sql-order.sh +2 -115
  97. package/scripts/check-mcp-compatibility.ts +14 -0
  98. package/scripts/check-runtime-architecture.mjs +349 -79
  99. package/scripts/check-skill-version.ts +3 -191
  100. package/scripts/check-source-duplication.mjs +61 -0
  101. package/scripts/check-stable-baseline.ts +92 -0
  102. package/scripts/check-task-sync.sh +2 -63
  103. package/scripts/check-task-workflow.sh +2 -1000
  104. package/scripts/codex-handoff-resume.sh +2 -267
  105. package/scripts/context-contract-sync.sh +2 -514
  106. package/scripts/contract-run.ts +2 -463
  107. package/scripts/contract-worktree.sh +1 -722
  108. package/scripts/ensure-task-workflow.sh +1 -1225
  109. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  110. package/scripts/inspect-project-state.ts +4 -279
  111. package/scripts/install-standalone-recovery.ts +24 -0
  112. package/scripts/lib/project-init-lib.sh +22 -46
  113. package/scripts/lib/run-forge-helper.sh +19 -0
  114. package/scripts/lib/run-forge-helper.ts +26 -0
  115. package/scripts/migrate-project-template.sh +1 -1189
  116. package/scripts/migrate-workflow-docs.ts +4 -412
  117. package/scripts/new-plan.sh +1 -180
  118. package/scripts/new-spec.sh +1 -47
  119. package/scripts/new-sprint.sh +1 -46
  120. package/scripts/plan-to-todo.sh +1 -796
  121. package/scripts/prepare-codex-handoff.sh +2 -201
  122. package/scripts/prepare-handoff.sh +1 -78
  123. package/scripts/refresh-current-status.sh +2 -467
  124. package/scripts/run-governed-gate.ts +5 -1
  125. package/scripts/select-agent-context-blocks.sh +2 -64
  126. package/scripts/ship-worktrees.sh +1 -558
  127. package/scripts/smoke-ios-physical-device.ts +4 -2
  128. package/scripts/smoke-runtime-recovery.ts +1 -35
  129. package/scripts/smoke-schedule-engine.ts +1 -20
  130. package/scripts/sprint-backlog.sh +1 -834
  131. package/scripts/stage-runtime-release.ts +2 -1
  132. package/scripts/summarize-failures.sh +2 -123
  133. package/scripts/switch-plan.sh +1 -150
  134. package/scripts/sync-brain-docs.sh +2 -379
  135. package/scripts/sync-generated-authority.mjs +175 -0
  136. package/scripts/verify-contract.sh +2 -703
  137. package/scripts/verify-forge-runtime.sh +0 -1
  138. package/scripts/verify-sprint.sh +1 -677
  139. package/scripts/workflow-contract.ts +4 -205
  140. package/scripts/workstream-sync.sh +2 -261
  141. package/src/cli/agent-jobs/executor-health.ts +1 -1
  142. package/src/cli/commands/init-hook.ts +3 -3
  143. package/src/cli/commands/mcp.ts +9 -5
  144. package/src/cli/commands/plugin.ts +12 -3
  145. package/src/cli/commands/recovery.ts +25 -3
  146. package/src/cli/commands/runtime.ts +55 -3
  147. package/src/cli/commands/security.ts +12 -1
  148. package/src/cli/commands/setup-profile.ts +31 -21
  149. package/src/cli/controller/check-runner.ts +113 -5
  150. package/src/cli/controller/composite-result.ts +1 -1
  151. package/src/cli/controller/context/focus.ts +51 -0
  152. package/src/cli/controller/context/instruction-context.ts +114 -0
  153. package/src/cli/controller/context/known-paths.ts +76 -0
  154. package/src/cli/controller/context/query-planning.ts +110 -0
  155. package/src/cli/controller/context/rule-resolution.ts +532 -0
  156. package/src/cli/controller/context/source-materializer.ts +278 -0
  157. package/src/cli/controller/context/structural.ts +16 -0
  158. package/src/cli/controller/context/types.ts +286 -0
  159. package/src/cli/controller/context-pack.ts +828 -487
  160. package/src/cli/controller/launch-agents.ts +77 -2
  161. package/src/cli/controller/operational-plan.ts +2 -2
  162. package/src/cli/controller/work-mode.ts +6 -4
  163. package/src/cli/editing/edit-session.ts +60 -7
  164. package/src/cli/github/contracts.ts +16 -0
  165. package/src/cli/github/github.ts +135 -16
  166. package/src/cli/github/session.ts +2 -1
  167. package/src/cli/installer/managed-entries.ts +15 -1
  168. package/src/cli/local-bridge/console-automations.ts +54 -78
  169. package/src/cli/local-bridge/console-view-models.ts +1 -35
  170. package/src/cli/local-bridge/facade-api.ts +37 -264
  171. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  172. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  173. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  174. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  175. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  176. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  177. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  178. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  179. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  180. package/src/cli/local-bridge/job-store.ts +1 -490
  181. package/src/cli/local-bridge/server.ts +2 -515
  182. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  183. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  184. package/src/cli/mcp/auth.ts +5 -5
  185. package/src/cli/mcp/instructions.ts +3 -2
  186. package/src/cli/mcp/legacy-tool-service.ts +10 -222
  187. package/src/cli/mcp/multi-repository.ts +16 -5
  188. package/src/cli/mcp/oauth.ts +175 -26
  189. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  190. package/src/cli/mcp/repository-tools.ts +282 -146
  191. package/src/cli/mcp/server.ts +586 -116
  192. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  193. package/src/cli/mcp/tool-contract.ts +14 -0
  194. package/src/cli/mcp/toolset-selection.ts +1 -1
  195. package/src/cli/mcp/toolset.ts +44 -7
  196. package/src/cli/mcp/transports/http.ts +124 -14
  197. package/src/cli/mcp/transports/session-registry.ts +43 -5
  198. package/src/cli/mcp/types.ts +3 -3
  199. package/src/cli/repositories/command-classifier.ts +103 -21
  200. package/src/cli/repositories/command-executor.ts +78 -526
  201. package/src/cli/repositories/command-process.ts +108 -0
  202. package/src/cli/repositories/command-scope.ts +99 -41
  203. package/src/cli/repositories/controller-home.ts +7 -8
  204. package/src/cli/repositories/git-command-executor.ts +2 -0
  205. package/src/cli/repositories/locks.ts +53 -16
  206. package/src/cli/repositories/repository-snapshot.ts +124 -0
  207. package/src/cli/repositories/selected-path-actions.ts +17 -9
  208. package/src/cli/repositories/structured-git.ts +118 -6
  209. package/src/cli/repository/inspector.ts +563 -65
  210. package/src/cli/repository/session-cache.ts +58 -17
  211. package/src/cli/v81-entry.ts +1 -4
  212. package/src/effects/fs-transaction.ts +85 -17
  213. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  214. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  215. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  216. package/src/runtime/context/lsp-client.ts +283 -0
  217. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  218. package/src/runtime/context/semantic-navigation.ts +223 -0
  219. package/src/runtime/context/swift-navigation.ts +565 -0
  220. package/src/runtime/context/typescript-navigation.ts +226 -0
  221. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  222. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +48 -16
  223. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  224. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  225. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  226. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  227. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  228. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  229. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  230. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  231. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  232. package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
  233. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  234. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  235. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  236. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  237. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  238. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  239. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  240. package/src/runtime/control-plane/execution/work-task-receipt.ts +22 -18
  241. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +156 -11
  242. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  243. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  244. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  245. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +90 -9
  246. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  247. package/src/runtime/control-plane/facade/controller-round-relay.ts +1118 -0
  248. package/src/runtime/control-plane/facade/controller-session-store.ts +302 -12
  249. package/src/runtime/control-plane/facade/goal-workloop-access.ts +17 -1
  250. package/src/runtime/control-plane/facade/goal-workloop.ts +928 -190
  251. package/src/runtime/control-plane/facade/index.ts +3 -0
  252. package/src/runtime/control-plane/facade/plan-contract-store.ts +275 -42
  253. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  254. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  255. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  256. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  257. package/src/runtime/control-plane/facade/types.ts +90 -10
  258. package/src/runtime/control-plane/facade/work-contract-store.ts +157 -261
  259. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  260. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  261. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  262. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  263. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  264. package/src/runtime/control-plane/global-scheduler/maintenance.ts +152 -0
  265. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  266. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  267. package/src/runtime/control-plane/global-scheduler/scheduler.ts +278 -543
  268. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  269. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  270. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  271. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  272. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  273. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  274. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  275. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  276. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  277. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  278. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +63 -0
  279. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +365 -117
  280. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +89 -0
  281. package/src/runtime/control-plane/launcher/thin-launcher.ts +244 -11
  282. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  283. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  284. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  285. package/src/runtime/control-plane/release-retention.ts +62 -2
  286. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  287. package/src/runtime/control-plane/routing/route-policy.ts +24 -21
  288. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  289. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  290. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  291. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  292. package/src/runtime/diagnostics/performance.ts +24 -1
  293. package/src/runtime/evidence/event-ledger.ts +1 -1
  294. package/src/runtime/evidence/process-check-execution.ts +18 -0
  295. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  296. package/src/runtime/execution/jobs/store.ts +58 -4
  297. package/src/runtime/execution/jobs/types.ts +1 -1
  298. package/src/runtime/execution/managed-workspace.ts +203 -1
  299. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  300. package/src/runtime/execution/process-runtime/check-result.ts +88 -0
  301. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  302. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  303. package/src/runtime/execution/process-runtime/command-facade.ts +367 -40
  304. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  305. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1201 -0
  306. package/src/runtime/execution/process-runtime/persisted-check.ts +51 -5
  307. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  308. package/src/runtime/execution/process-runtime/runtime.ts +53 -3
  309. package/src/runtime/execution/process-runtime/store.ts +1 -0
  310. package/src/runtime/execution/process-runtime/types.ts +4 -12
  311. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  312. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  313. package/src/runtime/execution/work-activity.ts +21 -0
  314. package/src/runtime/execution/workers/executor.ts +18 -61
  315. package/src/runtime/gateway/mcp/execution-tools.ts +35 -1852
  316. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  317. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  318. package/src/runtime/gateway/mcp/router.ts +186 -84
  319. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +565 -0
  320. package/src/runtime/gateway/mcp/runtime-tools.ts +2378 -1947
  321. package/src/runtime/plugins/app-store-connect-adapter.ts +216 -47
  322. package/src/runtime/plugins/browser-adapter.ts +2662 -829
  323. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  324. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  325. package/src/runtime/plugins/browser-handoff.ts +110 -5
  326. package/src/runtime/plugins/browser-macos-bridge.ts +701 -63
  327. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  328. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  329. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  330. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  331. package/src/runtime/plugins/browser-runtime-contract.ts +154 -0
  332. package/src/runtime/plugins/browser-runtime.ts +193 -0
  333. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  334. package/src/runtime/plugins/browser-session-store.ts +121 -0
  335. package/src/runtime/plugins/browser-session-types.ts +98 -0
  336. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  337. package/src/runtime/plugins/desktop-operator-registration.ts +10 -4
  338. package/src/runtime/plugins/external-adapter.ts +358 -3
  339. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  340. package/src/runtime/plugins/first-party-registry.ts +2 -0
  341. package/src/runtime/plugins/github-adapter.ts +125 -0
  342. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  343. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  344. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  345. package/src/runtime/plugins/ios-adapter.ts +106 -16
  346. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  347. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  348. package/src/runtime/plugins/lightweight-action.ts +153 -0
  349. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  350. package/src/runtime/plugins/macos-capability-broker.ts +47 -9
  351. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  352. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  353. package/src/runtime/plugins/resend-adapter.ts +1 -1
  354. package/src/runtime/plugins/store.ts +330 -33
  355. package/src/runtime/plugins/types.ts +38 -3
  356. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  357. package/src/runtime/projections/git-status-sampler.ts +16 -16
  358. package/src/runtime/projections/materialized-view.ts +28 -10
  359. package/src/runtime/recovery/maintenance-executor.ts +426 -25
  360. package/src/runtime/root/mcp-transport.ts +180 -10
  361. package/src/runtime/root/package-connector-service.ts +230 -10
  362. package/src/runtime/root/package-runtime-release.ts +143 -17
  363. package/src/runtime/root/package-runtime-service.ts +461 -22
  364. package/src/runtime/root/release-manifest.ts +144 -18
  365. package/src/runtime/root/release-materialize.ts +125 -32
  366. package/src/runtime/root/release-store.ts +48 -11
  367. package/src/runtime/root/runtime.ts +23 -5
  368. package/src/runtime/root/service.ts +100 -29
  369. package/src/runtime/root/types.ts +12 -0
  370. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  371. package/src/runtime/shared/trusted-executable.ts +62 -0
  372. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  373. package/src/runtime/standalone-recovery/core.ts +1079 -240
  374. package/src/runtime/standalone-recovery/entry.ts +100 -24
  375. package/src/runtime/standalone-recovery/installer.ts +20 -4
  376. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  377. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  378. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  379. package/src/runtime/workflow/schedules/engine.ts +364 -202
  380. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  381. package/src/runtime/workflow/schedules/settlement.ts +64 -17
  382. package/src/runtime/workflow/schedules/store.ts +209 -19
  383. package/src/runtime/workflow/schedules/types.ts +2 -4
  384. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  385. package/src/testing/test-governance.ts +8 -40
  386. package/assets/reference-configs/heartbeat-triage.md +0 -57
  387. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  388. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  389. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  390. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  391. package/docs/architecture/current/architecture-invariants.md +0 -331
  392. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  393. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  394. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  395. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  396. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  397. package/docs/architecture/current/entity-model.md +0 -612
  398. package/docs/architecture/current/external-plugin-governance.md +0 -396
  399. package/docs/architecture/current/failure-recovery.md +0 -447
  400. package/docs/architecture/current/governance.md +0 -213
  401. package/docs/architecture/current/human-interaction-plane.md +0 -87
  402. package/docs/architecture/current/implementation-status.md +0 -128
  403. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  404. package/docs/architecture/current/legacy-governance.md +0 -58
  405. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  406. package/docs/architecture/current/migration-roadmap.md +0 -105
  407. package/docs/architecture/current/multi-repository-execution.md +0 -371
  408. package/docs/architecture/current/operations-runbook.md +0 -45
  409. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  410. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  411. package/docs/architecture/current/repository-access-modes.md +0 -102
  412. package/docs/architecture/current/route-policy.md +0 -15
  413. package/docs/architecture/current/route-session-concurrency.md +0 -85
  414. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  415. package/docs/architecture/current/runtime-directory-map.md +0 -49
  416. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  417. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  418. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  419. package/docs/architecture/current/system-overview.md +0 -169
  420. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  421. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  422. package/docs/architecture/current/thin-harness-v1.md +0 -321
  423. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  424. package/docs/forge-autonomous-goal-loop.md +0 -9
  425. package/docs/forge-provider-configuration.md +0 -7
  426. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  427. package/docs/operations/gmail-assistant-routines.md +0 -186
  428. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  429. package/docs/operations/provider-configuration.md +0 -31
  430. package/scripts/harness-trace-grade.sh +0 -150
  431. package/scripts/heartbeat-triage.sh +0 -361
  432. package/scripts/maintenance-triage.sh +0 -167
  433. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  434. package/src/cli/repositories/goal-registry.ts +0 -239
  435. package/src/runtime/assistant/action-proposals.ts +0 -279
  436. package/src/runtime/assistant/intent.ts +0 -395
  437. package/src/runtime/assistant/model-provider.ts +0 -460
  438. package/src/runtime/assistant/openapi.ts +0 -223
  439. package/src/runtime/assistant/policy.ts +0 -89
  440. package/src/runtime/assistant/readiness.ts +0 -258
  441. package/src/runtime/assistant/routine-runtime.ts +0 -477
  442. package/src/runtime/assistant/schedule-binding.ts +0 -171
  443. package/src/runtime/assistant/standing-grants.ts +0 -375
  444. package/src/runtime/assistant/store.ts +0 -243
  445. package/src/runtime/assistant/types.ts +0 -135
  446. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  447. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  448. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  449. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  450. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  451. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  452. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  453. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  454. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  455. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  456. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  457. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  458. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  459. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  460. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  461. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  462. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  463. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  464. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  465. package/src/runtime/workflow/findings/store.ts +0 -204
  466. package/src/runtime/workflow/findings/types.ts +0 -58
  467. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  468. package/src/runtime/workflow/portfolio/store.ts +0 -185
  469. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -1,19 +1,26 @@
1
1
  /**
2
- * repository_command_execute → Unified Process Runtime.
2
+ * repository_command_execute → three explicit execution lanes.
3
3
  *
4
- * Short readonly / focused commands: Direct (wait briefly, return result).
5
- * Longer local build/test: Managed (same spawn, return handle).
6
- * release / remote / non-idempotent: Durable (caller keeps ExecutionJob path).
4
+ * - Ephemeral Direct: bounded ordinary reads and commands that finish inside
5
+ * the interactive budget.
6
+ * - Lightweight Managed: the same local command remains alive in memory and
7
+ * returns a bounded handle, without SQLite, Lease, or recovery membership.
8
+ * - Durable: explicit Work/external/release boundaries only.
7
9
  */
8
10
 
9
11
  import type { RepositoryRecord } from '../../../cli/repositories/types';
10
- import { classifyRepositoryCommand } from '../../../cli/repositories/command-classifier';
12
+ import { repositoryGitStatus } from '../../../cli/repositories/structured-git';
13
+ import { classifyRepositoryCommand, fixedShellWrapperCommand, shellSegments, shellWordsPreservingQuotes } from '../../../cli/repositories/command-classifier';
11
14
  import {
12
15
  executeRepositoryCommandAsync,
13
16
  executeRepositoryReadOnlyCommandDirect,
14
17
  type RepositoryCommandExecution,
15
18
  } from '../../../cli/repositories/command-executor';
16
19
  import { normalizeRepositoryCommand } from '../../../cli/repositories/command-normalization';
20
+ import {
21
+ assertRepositoryCommandNoPluginExecutionBypass,
22
+ assertRepositoryCommandStableHostIdentity,
23
+ } from '../../../cli/repositories/command-scope';
17
24
  import { claimsForRepositoryCommand, scopeResourceClaims, toProcessClaims } from './resource-claims';
18
25
  import {
19
26
  spawnManagedProcess,
@@ -27,6 +34,15 @@ import { DEFAULT_INTERACTIVE_WAIT_MS } from './types';
27
34
  import { durationAwareInteractiveWaitMs } from './interactive-admission';
28
35
  import { isFocusedCheckCommand } from '../thin-harness/execution-router';
29
36
  import { assertExecutionIdentity, type ResolvedExecutionIdentity } from '../../control-plane/execution/execution-identity';
37
+ import { readWorkHandle, transitionWorkHandle } from '../../control-plane/execution/work-handle-store';
38
+ import {
39
+ cancelLightweightProcess,
40
+ getLightweightProcessHandle,
41
+ isLightweightProcessId,
42
+ readLightweightProcessLogs,
43
+ startLightweightRepositoryCommand,
44
+ waitForLightweightProcess,
45
+ } from './lightweight-managed';
30
46
 
31
47
  export type RepositoryCommandRoute =
32
48
  | 'process_direct'
@@ -76,6 +92,19 @@ export interface RepositoryCommandProcessResult {
76
92
  projectionUpdateCount: number;
77
93
  };
78
94
  suggestedOperation?: string;
95
+ externalEffect?: {
96
+ outcome: 'not_started' | 'outcome_unknown';
97
+ replayPolicy: 'never_auto_retry';
98
+ reconciliation: string;
99
+ };
100
+ executionMetrics?: {
101
+ lane: 'ephemeral_direct' | 'lightweight_managed' | 'durable_process' | 'durable_external';
102
+ preSpawnHarnessMs?: number;
103
+ childDurationMs?: number;
104
+ interactiveReturnMs?: number;
105
+ durableWrites: number;
106
+ leaseOperations: number;
107
+ };
79
108
  }
80
109
 
81
110
  const emptyEffects = {
@@ -85,6 +114,165 @@ const emptyEffects = {
85
114
  projectionUpdateCount: 0,
86
115
  };
87
116
 
117
+ export type WorkHeadSettlementReason =
118
+ | 'settled'
119
+ | 'no_work'
120
+ | 'command_not_successful'
121
+ | 'work_handle_missing'
122
+ | 'identity_mismatch'
123
+ | 'terminal_handle'
124
+ | 'branch_changed'
125
+ | 'head_unavailable'
126
+ | 'head_unchanged'
127
+ | 'concurrent_lifecycle_write';
128
+
129
+ export interface WorkHeadSettlementResult {
130
+ settled: boolean;
131
+ reason: WorkHeadSettlementReason;
132
+ previousHead?: string;
133
+ currentHead?: string;
134
+ }
135
+
136
+ /**
137
+ * Settle the one legitimate post-command Work HEAD transition without weakening
138
+ * the pre-command execution identity fence. The command has already been
139
+ * admitted only if repo/checkout/branch/expectedHead matched the WorkHandle.
140
+ * After success, adopt the exact current HEAD only while the same checkout and
141
+ * branch still own the Work. CAS failure is a bounded no-op: finalization keeps
142
+ * failing closed instead of accepting a concurrent lifecycle writer.
143
+ */
144
+ export function settleWorkHandleExpectedHeadAfterRepositoryCommand(input: {
145
+ controllerHome: string;
146
+ repository: RepositoryRecord;
147
+ executionIdentity: ResolvedExecutionIdentity;
148
+ workId?: string;
149
+ ok?: boolean;
150
+ cancelled?: boolean;
151
+ timedOut?: boolean;
152
+ }): WorkHeadSettlementResult {
153
+ const workId = input.workId?.trim();
154
+ if (!workId) return { settled: false, reason: 'no_work' };
155
+ if (input.ok !== true || input.cancelled === true || input.timedOut === true) {
156
+ return { settled: false, reason: 'command_not_successful' };
157
+ }
158
+ const handle = readWorkHandle(input.controllerHome, input.repository.repoId, workId);
159
+ if (!handle) return { settled: false, reason: 'work_handle_missing' };
160
+ if (
161
+ handle.repositoryId !== input.executionIdentity.repositoryId
162
+ || handle.checkoutId !== input.executionIdentity.checkoutId
163
+ || handle.workId !== input.executionIdentity.workId
164
+ ) return { settled: false, reason: 'identity_mismatch' };
165
+ if (handle.state === 'merged' || handle.state === 'cleaned' || handle.state === 'failed_terminal_cleanup') {
166
+ return { settled: false, reason: 'terminal_handle' };
167
+ }
168
+ const status = repositoryGitStatus(input.repository);
169
+ if (!status.branch || status.branch !== handle.branch) {
170
+ return { settled: false, reason: 'branch_changed', previousHead: handle.expectedHead, currentHead: status.head ?? undefined };
171
+ }
172
+ const currentHead = status.head?.trim();
173
+ if (!currentHead) return { settled: false, reason: 'head_unavailable', previousHead: handle.expectedHead };
174
+ if (currentHead === handle.expectedHead) {
175
+ return { settled: false, reason: 'head_unchanged', previousHead: handle.expectedHead, currentHead };
176
+ }
177
+ try {
178
+ transitionWorkHandle(input.controllerHome, handle, handle.state, { expectedHead: currentHead });
179
+ return { settled: true, reason: 'settled', previousHead: handle.expectedHead, currentHead };
180
+ } catch (error) {
181
+ if (error instanceof Error && error.message.includes('CONTROL_PLANE_REVISION_CONFLICT')) {
182
+ return { settled: false, reason: 'concurrent_lifecycle_write', previousHead: handle.expectedHead, currentHead };
183
+ }
184
+ throw error;
185
+ }
186
+ }
187
+
188
+ const STANDALONE_RECOVERY_LIFECYCLE_COMMANDS = new Set([
189
+ 'restart-runtime',
190
+ 'bootstrap-cutover',
191
+ 'restart-connector',
192
+ 'stage-and-activate-runtime',
193
+ 'recover',
194
+ 'rollback',
195
+ 'restart',
196
+ 'activate-runtime',
197
+ 'install',
198
+ ]);
199
+
200
+ function commandBasename(value: string): string {
201
+ return value.split(/[\\/]/).pop()?.toLowerCase() ?? '';
202
+ }
203
+
204
+ function recoveryLifecycleFromArgv(executable: string, args: readonly string[]): string | undefined {
205
+ const executableName = commandBasename(executable);
206
+ let recoveryArgs: readonly string[];
207
+ if (executableName === 'forge' || executableName === 'forge.mjs') {
208
+ recoveryArgs = args;
209
+ } else if (['bun', 'node', 'nodejs'].includes(executableName)) {
210
+ const scriptIndex = args.findIndex((arg) => {
211
+ const name = commandBasename(arg);
212
+ return name === 'forge' || name === 'forge.mjs';
213
+ });
214
+ if (scriptIndex < 0) return undefined;
215
+ recoveryArgs = args.slice(scriptIndex + 1);
216
+ } else {
217
+ return undefined;
218
+ }
219
+ if (recoveryArgs[0] !== 'recovery') return undefined;
220
+ const subcommand = recoveryArgs[1]?.toLowerCase();
221
+ return subcommand && STANDALONE_RECOVERY_LIFECYCLE_COMMANDS.has(subcommand) ? subcommand : undefined;
222
+ }
223
+
224
+ function recoveryLifecycleFromShell(shellCommand: string): string | undefined {
225
+ const match = /^\s*(?:exec\s+)?(?:(?:\S*[\\/])?(?:bun|node|nodejs)\s+)?(?:\S*[\\/])?forge(?:\.mjs)?\s+recovery\s+([a-z-]+)\b/i.exec(shellCommand);
226
+ const subcommand = match?.[1]?.toLowerCase();
227
+ return subcommand && STANDALONE_RECOVERY_LIFECYCLE_COMMANDS.has(subcommand) ? subcommand : undefined;
228
+ }
229
+
230
+ function repositoryCommandRecoveryLifecycle(command: string | readonly string[]): string | undefined {
231
+ const normalized = normalizeRepositoryCommand(command);
232
+ if (normalized.kind === 'shell') return recoveryLifecycleFromShell(normalized.shellCommand ?? '');
233
+ const direct = recoveryLifecycleFromArgv(normalized.executable ?? '', normalized.args ?? []);
234
+ if (direct) return direct;
235
+ const argv = normalized.value as string[];
236
+ const wrapped = fixedShellWrapperCommand(argv);
237
+ return wrapped ? recoveryLifecycleFromShell(wrapped) : undefined;
238
+ }
239
+
240
+ function gitCommitWordsRequireExplicitPathScope(words: readonly string[]): boolean | undefined {
241
+ const executable = commandBasename(words[0] ?? '');
242
+ if (executable !== 'git') return undefined;
243
+ const args = words.slice(1);
244
+ let index = 0;
245
+ while (index < args.length && args[index]?.startsWith('-')) {
246
+ const option = args[index]!;
247
+ if (['-c', '-C', '--git-dir', '--work-tree', '--namespace'].includes(option)) index += 2;
248
+ else if (['--git-dir=', '--work-tree=', '--namespace='].some((prefix) => option.startsWith(prefix))) index += 1;
249
+ else index += 1;
250
+ }
251
+ if (args[index]?.toLowerCase() !== 'commit') return undefined;
252
+ const commitArgs = args.slice(index + 1);
253
+ const pathSeparator = commitArgs.indexOf('--');
254
+ const paths = pathSeparator >= 0 ? commitArgs.slice(pathSeparator + 1).filter(Boolean) : [];
255
+ const widensScope = commitArgs.some((arg) => ['-a', '--all', '-i', '--include', '--interactive', '-p', '--patch'].includes(arg));
256
+ return paths.length === 0 || widensScope;
257
+ }
258
+
259
+ function rawGitCommitRequiresExplicitPathScope(command: string | readonly string[]): boolean {
260
+ const normalized = normalizeRepositoryCommand(command);
261
+ if (normalized.kind === 'argv') {
262
+ const direct = gitCommitWordsRequireExplicitPathScope([
263
+ normalized.executable ?? '',
264
+ ...(normalized.args ?? []),
265
+ ]);
266
+ if (direct !== undefined) return direct;
267
+ const wrapped = fixedShellWrapperCommand(normalized.value as string[]);
268
+ if (!wrapped) return false;
269
+ return shellSegments(wrapped)
270
+ .some((segment) => gitCommitWordsRequireExplicitPathScope(shellWordsPreservingQuotes(segment)) === true);
271
+ }
272
+ return shellSegments(normalized.shellCommand ?? '')
273
+ .some((segment) => gitCommitWordsRequireExplicitPathScope(shellWordsPreservingQuotes(segment)) === true);
274
+ }
275
+
88
276
  function toProcessCommand(command: string | readonly string[], cwd: string): ProcessCommandSpec {
89
277
  const normalized = normalizeRepositoryCommand(command);
90
278
  if (normalized.kind === 'argv') {
@@ -109,41 +297,64 @@ export function classifyRepositoryCommandRoute(
109
297
  command: string | readonly string[],
110
298
  options: {
111
299
  forceDurable?: boolean;
300
+ workId?: string;
112
301
  defaultBranch?: string;
113
302
  timeoutMs?: number;
114
303
  } = {},
115
304
  ): { route: RepositoryCommandRoute; reason: string } {
305
+ if (repositoryCommandRecoveryLifecycle(command)) {
306
+ return { route: 'reject', reason: 'standalone_recovery_lifecycle_required' };
307
+ }
308
+ if (rawGitCommitRequiresExplicitPathScope(command)) {
309
+ return { route: 'reject', reason: 'git_commit_requires_explicit_path_scope' };
310
+ }
116
311
  if (options.forceDurable) {
117
312
  return { route: 'durable', reason: 'force_durable_or_async' };
118
313
  }
119
314
  const classification = classifyRepositoryCommand(command, options.defaultBranch);
120
- // ExecutionJobs are retired. Authorized local destructive/remote-risk commands
121
- // still execute through Process Runtime; SuperController owns retry/replay policy.
122
- if (classification.risk === 'remote_write' || classification.risk === 'destructive') {
123
- return {
124
- route: 'process_managed',
125
- reason: `risk_${classification.risk}_via_process_runtime`,
126
- };
315
+ // Work-bound effects must survive Controller/Runtime rotation with one durable
316
+ // Process identity. Read-only observations can remain ephemeral because they
317
+ // have no ambiguous side effect to reconcile after a restart.
318
+ if (options.workId?.trim() && classification.risk !== 'readonly') {
319
+ return { route: 'process_managed', reason: 'work_attributed_effectful_durable' };
127
320
  }
128
- // release / rollback style commands
129
321
  const text = Array.isArray(command) ? command.join(' ') : String(command);
130
- if (/\b(?:gh\s+release\s+(?:create|delete|edit|upload)|git\s+push|npm\s+publish)\b/i.test(text)) {
131
- return { route: 'durable', reason: 'release_or_remote_mutation' };
322
+ // Classification chooses scheduling/receipt granularity only; it does not
323
+ // decide whether a scoped repository command is allowed to run. Known remote
324
+ // or broadly effectful commands use managed Process Runtime so existing
325
+ // resource claims and receipts can serialize/reconcile them. Unknown commands
326
+ // still execute instead of requiring a new allow rule.
327
+ if (classification.risk === 'remote_write' || classification.risk === 'destructive') {
328
+ return { route: 'process_managed', reason: 'effectful_command_managed' };
132
329
  }
133
- if (classification.risk === 'readonly') {
134
- // Short readonly → direct. Only an *explicit* long timeout upgrades to managed.
135
- if (typeof options.timeoutMs === 'number' && options.timeoutMs > 30_000) {
136
- return { route: 'process_managed', reason: 'readonly_long_timeout' };
330
+ const argv = Array.isArray(command) ? command : [];
331
+ const executable = argv[0]?.split(/[\\/]/).pop()?.toLowerCase();
332
+ const shellWrapped = Boolean(executable
333
+ && ['bash', 'sh', 'zsh', 'fish', 'dash', 'cmd', 'powershell', 'pwsh'].includes(executable)
334
+ && argv.slice(1).some((arg) => ['-c', '-lc', '/c', '-Command'].includes(arg)));
335
+ const wrappedShellCommand = shellWrapped ? fixedShellWrapperCommand(argv) : undefined;
336
+ const wrapsInlineInterpreter = Boolean(wrappedShellCommand
337
+ && /^\s*(?:exec\s+)?(?:node|nodejs|bun|deno|ruby|perl|python\d*)\b[^\n]*(?:\s-c\b|\s-e\b|\s--eval\b)/i.test(wrappedShellCommand));
338
+ const wrapsObviousExternalIo = Boolean(wrappedShellCommand
339
+ && /\b(?:curl|wget|ssh|scp|sftp|ftp|telnet|nc|ncat)\b|https?:\/\//i.test(wrappedShellCommand));
340
+ if (shellWrapped && classification.risk !== 'readonly') {
341
+ if (wrapsObviousExternalIo && !wrapsInlineInterpreter) {
342
+ return { route: 'process_managed', reason: 'shell_wrapper_requires_managed_boundary' };
137
343
  }
344
+ return { route: 'process_direct', reason: 'lightweight_local_shell_wrapper' };
345
+ }
346
+ if (executable && ['node', 'nodejs', 'bun', 'deno', 'ruby', 'perl', 'python', 'python3'].includes(executable)
347
+ && argv.slice(1).some((arg) => ['-c', '-e', '--eval'].includes(arg))) {
348
+ return { route: 'process_direct', reason: 'lightweight_local_inline_interpreter' };
349
+ }
350
+ if (classification.risk === 'readonly') {
138
351
  return { route: 'process_direct', reason: 'readonly_fast_path' };
139
352
  }
140
353
  // workspace_write / build-test
141
354
  if (isFocusedCheckCommand(command) || /\b(?:test|typecheck|lint|build|check)\b/i.test(text)) {
142
- return { route: 'process_managed', reason: 'local_build_or_test' };
355
+ return { route: 'process_direct', reason: 'ephemeral_local_build_or_test' };
143
356
  }
144
- // Unknown mutating local command remains one local Managed Process. Timeout is
145
- // a process lifecycle budget and must never select a different architecture.
146
- return { route: 'process_managed', reason: 'local_workspace_mutation' };
357
+ return { route: 'process_direct', reason: 'ephemeral_local_workspace_mutation' };
147
358
  }
148
359
 
149
360
  /**
@@ -153,18 +364,37 @@ export function classifyRepositoryCommandRoute(
153
364
  export async function executeRepositoryCommandViaProcessRuntime(
154
365
  input: RepositoryCommandProcessInput,
155
366
  ): Promise<RepositoryCommandProcessResult> {
367
+ // Process Runtime intentionally supports some command forms that the legacy
368
+ // repository executor rejects (for example bounded typed eval). Keep that
369
+ // surface, but fail closed before routing any macOS TCC-sensitive host tool:
370
+ // a release-specific forge-runtime executable must never own those grants.
371
+ assertRepositoryCommandStableHostIdentity(input.command);
372
+ assertRepositoryCommandNoPluginExecutionBypass(input.command);
156
373
  const decision = classifyRepositoryCommandRoute(input.command, {
157
374
  forceDurable: input.forceDurable,
375
+ workId: input.workId,
158
376
  defaultBranch: input.repository.defaultBranch,
159
377
  timeoutMs: input.timeoutMs,
160
378
  });
161
-
162
- if (decision.route === 'durable' || decision.route === 'reject') {
379
+ // Only explicit lifecycle/delivery rejection or a caller-requested durable
380
+ // mode stops here. Ordinary command effects, including remote writes, execute
381
+ // through Process Runtime; classification does not become an authorization gate.
382
+ if (decision.route === 'reject' || decision.route === 'durable') {
163
383
  return {
164
384
  route: decision.route,
165
385
  reason: decision.reason,
166
386
  durableSideEffects: emptyEffects,
167
- suggestedOperation: 'repository_command_execute via Durable Work / Local Job',
387
+ suggestedOperation: decision.reason === 'standalone_recovery_lifecycle_required'
388
+ ? 'Use the standalone Forge Recovery connector for canonical Runtime/connector lifecycle operations.'
389
+ : 'repository_command_execute via Durable Work / Local Job',
390
+ externalEffect: decision.route === 'durable'
391
+ ? {
392
+ outcome: 'not_started',
393
+ replayPolicy: 'never_auto_retry',
394
+ reconciliation: 'If a separate external controller later loses the result after dispatch, report outcome_unknown and inspect remote state before any retry.',
395
+ }
396
+ : undefined,
397
+ executionMetrics: { lane: 'durable_external', durableWrites: 0, leaseOperations: 0 },
168
398
  };
169
399
  }
170
400
 
@@ -191,12 +421,12 @@ export async function executeRepositoryCommandViaProcessRuntime(
191
421
  };
192
422
  }
193
423
 
194
- // A short readonly command owns no durable Process/Lease state. Validate the
195
- // same immutable repository identity first, then execute with the bounded
196
- // non-persistent reader. This keeps reads available while a stale/passive
197
- // Runtime remains correctly fenced from Controller writes.
424
+ // Ordinary local commands own no durable Process/Lease state. Validate the
425
+ // immutable repository identity first, then execute through the authorized
426
+ // non-persistent repository executor. Runtime/release durability is not a
427
+ // command-execution tax.
198
428
  const canonicalCommand = normalizeRepositoryCommand(input.command);
199
- if (decision.route === 'process_direct' && canonicalCommand.kind === 'argv') {
429
+ if (decision.route === 'process_direct') {
200
430
  assertExecutionIdentity({
201
431
  controllerHome: input.controllerHome,
202
432
  identity: executionIdentity,
@@ -204,14 +434,73 @@ export async function executeRepositoryCommandViaProcessRuntime(
204
434
  requestedRepoId: input.repository.repoId,
205
435
  requestedCheckoutId: input.repository.activeCheckoutId,
206
436
  });
207
- const direct = await executeRepositoryReadOnlyCommandDirect(input.repository, {
437
+ const directInput = {
208
438
  command: input.command,
209
439
  cwd: input.cwd,
210
- timeoutMs: Math.min(input.timeoutMs ?? 30_000, 30_000),
440
+ timeoutMs: input.timeoutMs,
211
441
  maxOutputBytes: input.maxOutputBytes,
212
442
  signal: input.signal,
213
443
  allowNonGitWorkspace: executionIdentity.authority === 'ephemeral_workspace',
214
- });
444
+ allowOpaqueLocalScript: decision.reason === 'lightweight_local_shell_wrapper'
445
+ || decision.reason === 'lightweight_local_inline_interpreter',
446
+ };
447
+ const readonly = canonicalCommand.kind === 'argv'
448
+ && classifyRepositoryCommand(input.command, input.repository.defaultBranch).risk === 'readonly';
449
+ if (!readonly) {
450
+ const deferLongPreparation = decision.reason === 'ephemeral_local_build_or_test';
451
+ const interactiveWaitMs = deferLongPreparation
452
+ ? 0
453
+ : durationAwareInteractiveWaitMs(
454
+ input.command,
455
+ input.returnHandleImmediately ? 0 : input.interactiveWaitMs,
456
+ Math.min(DEFAULT_INTERACTIVE_WAIT_MS, 250),
457
+ );
458
+ const timeoutMs = Math.max(
459
+ interactiveWaitMs + 1,
460
+ Math.min(input.timeoutMs ?? 15 * 60_000, 24 * 60 * 60_000),
461
+ );
462
+ const lightweight = await startLightweightRepositoryCommand({
463
+ controllerHome: input.controllerHome,
464
+ repository: input.repository,
465
+ execution: directInput,
466
+ interactiveWaitMs,
467
+ timeoutMs,
468
+ maxOutputBytes: input.maxOutputBytes,
469
+ workId: input.workId,
470
+ commandId: input.commandId ?? input.requestId,
471
+ deferStart: deferLongPreparation,
472
+ reuseActiveEquivalent: deferLongPreparation,
473
+ onCompleted: (result) => {
474
+ // A terminal child exit is sufficient to close the Process, but not to
475
+ // advance Work mutation authority when post-execution repository
476
+ // evidence is unavailable. Finalization must keep failing closed until
477
+ // a later authoritative repository inspection proves the new HEAD.
478
+ if (result.evidenceError) return;
479
+ settleWorkHandleExpectedHeadAfterRepositoryCommand({
480
+ controllerHome: input.controllerHome,
481
+ repository: input.repository,
482
+ executionIdentity,
483
+ workId: input.workId,
484
+ ok: result.ok,
485
+ cancelled: result.cancelled,
486
+ timedOut: result.timedOut,
487
+ });
488
+ },
489
+ });
490
+ const handle = lightweight.handle;
491
+ return {
492
+ route: handle.completed ? 'process_direct' : 'process_managed',
493
+ reason: handle.completed ? decision.reason : 'interactive_budget_exceeded_lightweight_handle',
494
+ process: handle,
495
+ ok: handle.ok,
496
+ exitCode: handle.exitCode,
497
+ stdout: handle.stdout,
498
+ stderr: handle.stderr,
499
+ durableSideEffects: emptyEffects,
500
+ executionMetrics: lightweight.metrics,
501
+ };
502
+ }
503
+ const direct = await executeRepositoryReadOnlyCommandDirect(input.repository, directInput);
215
504
  return {
216
505
  route: 'process_direct',
217
506
  reason: decision.reason,
@@ -224,6 +513,7 @@ export async function executeRepositoryCommandViaProcessRuntime(
224
513
  authorizationDecision: direct.authorizationDecision,
225
514
  approvalRequestId: direct.approvalRequestId,
226
515
  durableSideEffects: emptyEffects,
516
+ executionMetrics: { lane: 'ephemeral_direct', durableWrites: 0, leaseOperations: 0 },
227
517
  };
228
518
  }
229
519
 
@@ -313,12 +603,49 @@ export async function executeRepositoryCommandViaProcessRuntime(
313
603
  stdout: handle.stdout,
314
604
  stderr: handle.stderr,
315
605
  durableSideEffects: handle.durableSideEffects,
606
+ executionMetrics: {
607
+ lane: 'durable_process',
608
+ interactiveReturnMs: interactiveWaitMs,
609
+ durableWrites: 1,
610
+ leaseOperations: claims.length,
611
+ },
316
612
  };
317
613
  }
318
614
 
319
- export {
320
- getProcessHandle as getRepositoryCommandProcess,
321
- waitForProcess as waitRepositoryCommandProcess,
322
- cancelProcess as cancelRepositoryCommandProcess,
323
- readProcessLogs as readRepositoryCommandProcessLogs,
324
- };
615
+ export function getRepositoryCommandProcess(controllerHome: string, repoId: string, processId: string): ProcessHandle | undefined {
616
+ return isLightweightProcessId(processId)
617
+ ? getLightweightProcessHandle(controllerHome, repoId, processId)
618
+ : getProcessHandle(controllerHome, repoId, processId);
619
+ }
620
+
621
+ export function waitRepositoryCommandProcess(
622
+ controllerHome: string,
623
+ repoId: string,
624
+ processId: string,
625
+ options?: Parameters<typeof waitForProcess>[3],
626
+ ): Promise<ProcessHandle> {
627
+ return isLightweightProcessId(processId)
628
+ ? waitForLightweightProcess(controllerHome, repoId, processId, options)
629
+ : waitForProcess(controllerHome, repoId, processId, options);
630
+ }
631
+
632
+ export function cancelRepositoryCommandProcess(
633
+ controllerHome: string,
634
+ repoId: string,
635
+ processId: string,
636
+ ): Promise<ProcessHandle> {
637
+ return isLightweightProcessId(processId)
638
+ ? cancelLightweightProcess(controllerHome, repoId, processId)
639
+ : cancelProcess(controllerHome, repoId, processId);
640
+ }
641
+
642
+ export function readRepositoryCommandProcessLogs(
643
+ controllerHome: string,
644
+ repoId: string,
645
+ processId: string,
646
+ maxBytes?: number,
647
+ ) {
648
+ return isLightweightProcessId(processId)
649
+ ? readLightweightProcessLogs(controllerHome, repoId, processId, maxBytes)
650
+ : readProcessLogs(controllerHome, repoId, processId, maxBytes);
651
+ }
@@ -9,8 +9,9 @@
9
9
  import { existsSync, readFileSync, readdirSync, unlinkSync, statSync } from 'fs';
10
10
  import { join } from 'path';
11
11
  import { ensureRepositoryControllerLayout, repositoryControllerRoot } from '../../../cli/repositories/controller-home';
12
- import { listActiveProcessIds } from './store';
13
- import type { ProcessRuntimeStatus } from './types';
12
+ import { getProcessRecord, listActiveProcessIds } from './store';
13
+ import { reconcileStaleManagedProcessForMaintenance } from './runtime';
14
+ import { isManagedProcessActive, type ProcessRuntimeStatus } from './types';
14
15
  import { assertRuntimeMayWrite } from '../../root/write-fence';
15
16
  import { isProcessAlive } from '../../shared/process-tree';
16
17
 
@@ -21,6 +22,7 @@ const TERMINAL: ReadonlySet<ProcessRuntimeStatus> = new Set([
21
22
  'cancelled',
22
23
  'orphaned',
23
24
  'completed_unknown',
25
+ 'unknown',
24
26
  ]);
25
27
 
26
28
  export interface ProcessGcOptions {
@@ -32,6 +34,10 @@ export interface ProcessGcOptions {
32
34
  maxTerminalRecords?: number;
33
35
  /** Also delete associated log/receipt files. */
34
36
  deleteLogs?: boolean;
37
+ /** Minimum age before an active record is eligible for evidence-based stale reconciliation (default 5m). */
38
+ staleActiveMinAgeMs?: number;
39
+ /** Max stale active records to reconcile in one selected-repository GC pass (default 100). */
40
+ maxStaleReconciliations?: number;
35
41
  }
36
42
 
37
43
  export interface ProcessGcResult {
@@ -39,6 +45,7 @@ export interface ProcessGcResult {
39
45
  removedRecords: number;
40
46
  removedLogs: number;
41
47
  skippedActive: number;
48
+ reconciledStaleActive: number;
42
49
  /** Legacy/corrupt records without enough terminal proof are preserved. */
43
50
  skippedInvalid?: number;
44
51
  error?: string;
@@ -125,28 +132,52 @@ export function gcTerminalProcesses(options: ProcessGcOptions): ProcessGcResult
125
132
  removedRecords: 0,
126
133
  removedLogs: 0,
127
134
  skippedActive: 0,
135
+ reconciledStaleActive: 0,
128
136
  error: `writer_fenced:${fence.reason ?? 'denied'}`,
129
137
  };
130
138
  }
131
139
 
132
140
  const maxAgeMs = options.maxAgeMs ?? 7 * 24 * 60 * 60_000;
133
141
  const maxTerminal = options.maxTerminalRecords ?? 500;
142
+ const staleActiveMinAgeMs = Math.max(1_000, options.staleActiveMinAgeMs ?? 5 * 60_000);
143
+ const maxStaleReconciliations = Math.max(0, options.maxStaleReconciliations ?? 100);
134
144
  const root = processesDir(options.controllerHome, options.repoId);
135
145
  if (!existsSync(root)) {
136
- return { ok: true, removedRecords: 0, removedLogs: 0, skippedActive: 0 };
146
+ return { ok: true, removedRecords: 0, removedLogs: 0, skippedActive: 0, reconciledStaleActive: 0 };
137
147
  }
138
148
 
139
149
  const active = new Set(listActiveProcessIds(options.controllerHome, options.repoId));
140
150
  const terminal: Array<{ processId: string; finishedAt: number; path: string; reusableCheckEvidence: boolean }> = [];
141
151
  let skippedActive = 0;
142
152
  let skippedInvalid = 0;
153
+ let reconciledStaleActive = 0;
154
+ const nowMs = Date.now();
143
155
 
144
156
  for (const name of readdirSync(root)) {
145
157
  if (!name.endsWith('.json') || name === 'active-index.json') continue;
146
158
  const processId = name.slice(0, -'.json'.length);
147
159
  if (active.has(processId)) {
148
- skippedActive += 1;
149
- continue;
160
+ let record = getProcessRecord(options.controllerHome, options.repoId, processId);
161
+ if (!record) {
162
+ skippedActive += 1;
163
+ continue;
164
+ }
165
+ if (isManagedProcessActive(record) && reconciledStaleActive < maxStaleReconciliations) {
166
+ const reconciled = reconcileStaleManagedProcessForMaintenance(
167
+ options.controllerHome,
168
+ options.repoId,
169
+ processId,
170
+ { nowMs, minAgeMs: staleActiveMinAgeMs },
171
+ );
172
+ if (reconciled) {
173
+ if (isManagedProcessActive(record) && !isManagedProcessActive(reconciled)) reconciledStaleActive += 1;
174
+ record = reconciled;
175
+ }
176
+ }
177
+ if (isManagedProcessActive(record)) {
178
+ skippedActive += 1;
179
+ continue;
180
+ }
150
181
  }
151
182
  const metadata = readProcessGcMetadata(join(root, name));
152
183
  if (!metadata?.status) {
@@ -194,13 +225,14 @@ export function gcTerminalProcesses(options: ProcessGcOptions): ProcessGcResult
194
225
  }
195
226
  }
196
227
 
197
- return { ok: true, removedRecords, removedLogs, skippedActive, skippedInvalid };
228
+ return { ok: true, removedRecords, removedLogs, skippedActive, reconciledStaleActive, skippedInvalid };
198
229
  } catch (error) {
199
230
  return {
200
231
  ok: false,
201
232
  removedRecords: 0,
202
233
  removedLogs: 0,
203
234
  skippedActive: 0,
235
+ reconciledStaleActive: 0,
204
236
  error: error instanceof Error ? error.message : String(error),
205
237
  };
206
238
  }