@moretea-labs/forge 1.5.0-rc.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 (903) hide show
  1. package/AGENTS.md +120 -0
  2. package/CHANGELOG.md +36 -0
  3. package/CLAUDE.md +120 -0
  4. package/CODE_OF_CONDUCT.md +23 -0
  5. package/CONTRIBUTING.md +77 -0
  6. package/LICENSE +22 -0
  7. package/NOTICE +9 -0
  8. package/README.en.md +5 -0
  9. package/README.es.md +12 -0
  10. package/README.fr.md +12 -0
  11. package/README.ja.md +12 -0
  12. package/README.md +132 -0
  13. package/README.zh-CN.md +84 -0
  14. package/SECURITY.md +50 -0
  15. package/SKILL.md +431 -0
  16. package/SUPPORT.md +31 -0
  17. package/THIRD_PARTY_NOTICES.md +18 -0
  18. package/agents/openai.yaml +4 -0
  19. package/assets/AGENTS.md +68 -0
  20. package/assets/CLAUDE.md +68 -0
  21. package/assets/branding/README.md +10 -0
  22. package/assets/branding/forge-controller-flow.mmd +8 -0
  23. package/assets/hooks/AGENTS.md +75 -0
  24. package/assets/hooks/CLAUDE.md +75 -0
  25. package/assets/hooks/anti-simplification.sh +12 -0
  26. package/assets/hooks/changelog-guard.sh +80 -0
  27. package/assets/hooks/codex.hooks.template.json +60 -0
  28. package/assets/hooks/first-principles-guard.sh +72 -0
  29. package/assets/hooks/hook-input.sh +618 -0
  30. package/assets/hooks/lib/session-state.sh +106 -0
  31. package/assets/hooks/lib/workflow-state.sh +1730 -0
  32. package/assets/hooks/post-bash.sh +215 -0
  33. package/assets/hooks/post-edit-guard.sh +256 -0
  34. package/assets/hooks/post-tool-observer.sh +99 -0
  35. package/assets/hooks/pre-edit-guard.sh +240 -0
  36. package/assets/hooks/prompt-guard.sh +1077 -0
  37. package/assets/hooks/run-hook.sh +67 -0
  38. package/assets/hooks/security-sentinel.sh +115 -0
  39. package/assets/hooks/session-start-context.sh +593 -0
  40. package/assets/hooks/settings.template.json +60 -0
  41. package/assets/hooks/stop-orchestrator.sh +171 -0
  42. package/assets/hooks/subagent-return-channel-guard.sh +107 -0
  43. package/assets/hooks/worktree-guard.sh +39 -0
  44. package/assets/initializer-question-pack.v1.json +132 -0
  45. package/assets/initializer-question-pack.v1.schema.json +46 -0
  46. package/assets/initializer-question-pack.v2.json +174 -0
  47. package/assets/initializer-question-pack.v3.json +206 -0
  48. package/assets/initializer-question-pack.v3.schema.json +92 -0
  49. package/assets/initializer-question-pack.v4.json +557 -0
  50. package/assets/initializer-question-pack.v4.schema.json +169 -0
  51. package/assets/partials/01-header.partial.md +10 -0
  52. package/assets/partials/02-iron-rules.partial.md +30 -0
  53. package/assets/partials/03-philosophy.partial.md +32 -0
  54. package/assets/partials/04-project-structure.partial.md +56 -0
  55. package/assets/partials/05-workflow.partial.md +79 -0
  56. package/assets/partials/06-cloudflare.partial.md +20 -0
  57. package/assets/partials/07-footer.partial.md +38 -0
  58. package/assets/partials/08-orchestration.partial.md +49 -0
  59. package/assets/partials/09-compact-instructions.partial.md +11 -0
  60. package/assets/partials/_assembly-order.md +40 -0
  61. package/assets/partials-agents/01-header.partial.md +8 -0
  62. package/assets/partials-agents/02-operating-mode.partial.md +44 -0
  63. package/assets/partials-agents/03-orchestration.partial.md +45 -0
  64. package/assets/partials-agents/04-task-protocol.partial.md +69 -0
  65. package/assets/partials-agents/05-coding-constraints.partial.md +20 -0
  66. package/assets/partials-agents/06-quality-safety.partial.md +23 -0
  67. package/assets/partials-agents/07-cloudflare.partial.md +9 -0
  68. package/assets/partials-agents/08-deep-docs.partial.md +32 -0
  69. package/assets/plan-map.json +301 -0
  70. package/assets/plugin-registry.v1.json +8 -0
  71. package/assets/project-structures/ai-native-collaborative-editor.txt +25 -0
  72. package/assets/project-structures/ai-native-product-copilot.txt +21 -0
  73. package/assets/project-structures/ai-native-runtime-console.txt +25 -0
  74. package/assets/project-structures/ai-native-sidecar-kernel.txt +19 -0
  75. package/assets/project-structures/astro-ssr.txt +25 -0
  76. package/assets/project-structures/expo-nativewind.txt +52 -0
  77. package/assets/project-structures/monorepo.txt +63 -0
  78. package/assets/project-structures/remix.txt +50 -0
  79. package/assets/project-structures/tanstack-start-workers.txt +51 -0
  80. package/assets/project-structures/vite-tanstack.txt +58 -0
  81. package/assets/reference-configs/AGENTS.md +13 -0
  82. package/assets/reference-configs/CLAUDE.md +13 -0
  83. package/assets/reference-configs/agentic-development-flow.md +94 -0
  84. package/assets/reference-configs/ai-workflows.md +14 -0
  85. package/assets/reference-configs/changelog-versioning.md +13 -0
  86. package/assets/reference-configs/coding-standards.md +14 -0
  87. package/assets/reference-configs/development-protocol.md +21 -0
  88. package/assets/reference-configs/document-generation.md +38 -0
  89. package/assets/reference-configs/evaluator-rubric.md +19 -0
  90. package/assets/reference-configs/external-tooling.md +374 -0
  91. package/assets/reference-configs/git-strategy.md +14 -0
  92. package/assets/reference-configs/global-working-rules.md +64 -0
  93. package/assets/reference-configs/handoff-protocol.md +38 -0
  94. package/assets/reference-configs/harness-overview.md +124 -0
  95. package/assets/reference-configs/heartbeat-triage.md +57 -0
  96. package/assets/reference-configs/hook-operations.md +96 -0
  97. package/assets/reference-configs/release-deploy.md +40 -0
  98. package/assets/reference-configs/spa-day-protocol.md +14 -0
  99. package/assets/reference-configs/sprint-contracts.md +89 -0
  100. package/assets/reference-configs/workflow-orchestration.md +14 -0
  101. package/assets/skill-hooks.json +41 -0
  102. package/assets/skill-version.json +189 -0
  103. package/assets/skills/claude-review/SKILL.md +231 -0
  104. package/assets/skills/codex-review/SKILL.md +103 -0
  105. package/assets/templates/AGENTS.md +13 -0
  106. package/assets/templates/CLAUDE.md +13 -0
  107. package/assets/templates/architecture.template.md +80 -0
  108. package/assets/templates/brief.template.md +38 -0
  109. package/assets/templates/contract.template.md +104 -0
  110. package/assets/templates/decisions.template.md +85 -0
  111. package/assets/templates/factor-factory/factor-backtest-report.template.md +36 -0
  112. package/assets/templates/factor-factory/factor-hypothesis.template.md +30 -0
  113. package/assets/templates/factor-factory/factor-registry.template.json +5 -0
  114. package/assets/templates/gitignore.template +53 -0
  115. package/assets/templates/guides/metro-esm-gotchas.template.md +46 -0
  116. package/assets/templates/helpers/architecture-event.ts +822 -0
  117. package/assets/templates/helpers/architecture-queue.sh +647 -0
  118. package/assets/templates/helpers/archive-architecture-request.sh +244 -0
  119. package/assets/templates/helpers/archive-workflow.sh +295 -0
  120. package/assets/templates/helpers/capability-config.ts +394 -0
  121. package/assets/templates/helpers/capability-resolver.ts +505 -0
  122. package/assets/templates/helpers/capture-plan.sh +335 -0
  123. package/assets/templates/helpers/check-agent-tooling.sh +1533 -0
  124. package/assets/templates/helpers/check-architecture-sync.sh +287 -0
  125. package/assets/templates/helpers/check-brain-manifest.sh +255 -0
  126. package/assets/templates/helpers/check-context-files.sh +49 -0
  127. package/assets/templates/helpers/check-deploy-sql-order.sh +117 -0
  128. package/assets/templates/helpers/check-skill-version.ts +193 -0
  129. package/assets/templates/helpers/check-task-sync.sh +65 -0
  130. package/assets/templates/helpers/check-task-workflow.sh +1002 -0
  131. package/assets/templates/helpers/codex-handoff-resume.sh +269 -0
  132. package/assets/templates/helpers/context-contract-sync.sh +516 -0
  133. package/assets/templates/helpers/contract-run.ts +465 -0
  134. package/assets/templates/helpers/contract-worktree.sh +725 -0
  135. package/assets/templates/helpers/ensure-task-workflow.sh +1218 -0
  136. package/assets/templates/helpers/harness-trace-grade.sh +150 -0
  137. package/assets/templates/helpers/heartbeat-triage.sh +361 -0
  138. package/assets/templates/helpers/inspect-project-state.ts +280 -0
  139. package/assets/templates/helpers/maintenance-triage.sh +167 -0
  140. package/assets/templates/helpers/migrate-project-template.sh +54 -0
  141. package/assets/templates/helpers/migrate-workflow-docs.ts +413 -0
  142. package/assets/templates/helpers/new-plan.sh +183 -0
  143. package/assets/templates/helpers/new-spec.sh +50 -0
  144. package/assets/templates/helpers/new-sprint.sh +49 -0
  145. package/assets/templates/helpers/plan-to-todo.sh +799 -0
  146. package/assets/templates/helpers/prepare-codex-handoff.sh +203 -0
  147. package/assets/templates/helpers/prepare-handoff.sh +81 -0
  148. package/assets/templates/helpers/refresh-current-status.sh +469 -0
  149. package/assets/templates/helpers/select-agent-context-blocks.sh +66 -0
  150. package/assets/templates/helpers/ship-worktrees.sh +561 -0
  151. package/assets/templates/helpers/sprint-backlog.sh +837 -0
  152. package/assets/templates/helpers/summarize-failures.sh +125 -0
  153. package/assets/templates/helpers/switch-plan.sh +153 -0
  154. package/assets/templates/helpers/sync-brain-docs.sh +381 -0
  155. package/assets/templates/helpers/verify-contract.sh +705 -0
  156. package/assets/templates/helpers/verify-sprint.sh +680 -0
  157. package/assets/templates/helpers/workflow-contract.ts +202 -0
  158. package/assets/templates/helpers/workstream-sync.sh +263 -0
  159. package/assets/templates/implementation-notes.template.md +37 -0
  160. package/assets/templates/packages.template.md +45 -0
  161. package/assets/templates/plan.template.md +77 -0
  162. package/assets/templates/prd.template.md +165 -0
  163. package/assets/templates/research.template.md +25 -0
  164. package/assets/templates/review.template.md +78 -0
  165. package/assets/templates/spec.template.md +31 -0
  166. package/assets/templates/sprint.template.md +69 -0
  167. package/assets/templates/tech-stack.template.md +57 -0
  168. package/assets/versions.json +77 -0
  169. package/assets/workflow-contract.v1.json +671 -0
  170. package/bin/forge-desktop-helper.mjs +204 -0
  171. package/bin/forge-hook.mjs +32 -0
  172. package/bin/forge-runtime-service.mjs +34 -0
  173. package/bin/forge-runtime.mjs +30 -0
  174. package/bin/forge.mjs +32 -0
  175. package/docs/CHANGELOG.md +978 -0
  176. package/docs/README.md +72 -0
  177. package/docs/README.zh-CN.md +58 -0
  178. package/docs/architecture/chatgpt-handoff-facade.md +393 -0
  179. package/docs/architecture/current/README.md +158 -0
  180. package/docs/architecture/current/approved-target-architecture.zh-CN.md +1501 -0
  181. package/docs/architecture/current/architecture-invariants.md +331 -0
  182. package/docs/architecture/current/automation-and-schedule-engine.md +453 -0
  183. package/docs/architecture/current/codegraph-context-provider.md +226 -0
  184. package/docs/architecture/current/control-plane-authority-inventory.md +82 -0
  185. package/docs/architecture/current/controller-repository-decoupling.md +148 -0
  186. package/docs/architecture/current/dispatch-and-agent-strategy.md +354 -0
  187. package/docs/architecture/current/entity-model.md +619 -0
  188. package/docs/architecture/current/external-plugin-governance.md +396 -0
  189. package/docs/architecture/current/failure-recovery.md +471 -0
  190. package/docs/architecture/current/governance.md +213 -0
  191. package/docs/architecture/current/human-interaction-plane.md +87 -0
  192. package/docs/architecture/current/implementation-status.md +128 -0
  193. package/docs/architecture/current/job-and-run-lifecycle.md +413 -0
  194. package/docs/architecture/current/local-workspace-target-grants.md +70 -0
  195. package/docs/architecture/current/migration-roadmap.md +105 -0
  196. package/docs/architecture/current/multi-repository-execution.md +371 -0
  197. package/docs/architecture/current/operations-runbook.md +45 -0
  198. package/docs/architecture/current/peer-supercontroller-runtime.md +32 -0
  199. package/docs/architecture/current/personal-assistant-plugin-baseline.md +207 -0
  200. package/docs/architecture/current/repository-access-modes.md +102 -0
  201. package/docs/architecture/current/route-policy.md +15 -0
  202. package/docs/architecture/current/route-session-concurrency.md +85 -0
  203. package/docs/architecture/current/runtime-architecture-simplification.md +214 -0
  204. package/docs/architecture/current/runtime-directory-map.md +49 -0
  205. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +85 -0
  206. package/docs/architecture/current/scheduler-and-resource-claims.md +432 -0
  207. package/docs/architecture/current/session-aware-execution-and-authorization.md +77 -0
  208. package/docs/architecture/current/system-overview.md +144 -0
  209. package/docs/architecture/current/target-requirements-traceability.md +202 -0
  210. package/docs/architecture/current/thin-harness-governance-policy.md +32 -0
  211. package/docs/architecture/current/thin-harness-v1.md +313 -0
  212. package/docs/architecture/current/verification-and-release-gates.md +391 -0
  213. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +100 -0
  214. package/docs/architecture/decisions/20260803-canonical-skill-surface.md +40 -0
  215. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +53 -0
  216. package/docs/architecture/diagrams/.gitkeep +1 -0
  217. package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +186 -0
  218. package/docs/architecture/domains/.gitkeep +0 -0
  219. package/docs/architecture/domains/public-surface.md +30 -0
  220. package/docs/architecture/domains/runtime-harness.md +33 -0
  221. package/docs/architecture/domains/verification.md +33 -0
  222. package/docs/architecture/domains/workflow-engine.md +30 -0
  223. package/docs/architecture/global-hook-runtime.md +162 -0
  224. package/docs/architecture/history.md +38 -0
  225. package/docs/architecture/index.md +114 -0
  226. package/docs/architecture/ios-semantic-automation-provider-v2.md +166 -0
  227. package/docs/architecture/modules/.gitkeep +0 -0
  228. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +24 -0
  229. package/docs/architecture/requests/.gitkeep +1 -0
  230. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +59 -0
  231. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +59 -0
  232. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +59 -0
  233. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +59 -0
  234. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +59 -0
  235. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +59 -0
  236. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +59 -0
  237. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +59 -0
  238. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +59 -0
  239. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +59 -0
  240. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +59 -0
  241. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +59 -0
  242. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +59 -0
  243. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +59 -0
  244. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +59 -0
  245. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +59 -0
  246. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +59 -0
  247. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +59 -0
  248. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +59 -0
  249. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +59 -0
  250. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +59 -0
  251. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +63 -0
  252. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +60 -0
  253. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +60 -0
  254. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +60 -0
  255. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +60 -0
  256. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +60 -0
  257. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +60 -0
  258. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +68 -0
  259. package/docs/architecture/requests/archive/2026/root.md +68 -0
  260. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +76 -0
  261. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +68 -0
  262. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +69 -0
  263. package/docs/architecture/reviews/2026-07-11-access-surface-review.md +99 -0
  264. package/docs/architecture/snapshots/.gitkeep +1 -0
  265. package/docs/forge-autonomous-goal-loop.md +7 -0
  266. package/docs/forge-chatgpt-browser-engine.md +11 -0
  267. package/docs/forge-chatgpt-mcp-setup.md +239 -0
  268. package/docs/forge-chatgpt-supervised-automation.md +11 -0
  269. package/docs/forge-github-issue-launcher.md +7 -0
  270. package/docs/forge-plugin-management.md +95 -0
  271. package/docs/forge-provider-configuration.md +7 -0
  272. package/docs/forge-runtime-self-healing-loop.md +7 -0
  273. package/docs/images/forge-banner-cn.svg +1 -0
  274. package/docs/images/forge-banner.svg +1 -0
  275. package/docs/images/forge-controller-flow.svg +1 -0
  276. package/docs/images/image.png +0 -0
  277. package/docs/images/repo-harness-gptpro-cn.png +0 -0
  278. package/docs/images/repo-harness-gptpro.png +0 -0
  279. package/docs/images/repo-harness-hook-carrot.png +0 -0
  280. package/docs/operations/chatgpt-personal-assistant-runtime.md +83 -0
  281. package/docs/operations/checkout-route-integrity.md +22 -0
  282. package/docs/operations/controller-browser-plugin.md +299 -0
  283. package/docs/operations/controller-desktop-plugin.md +112 -0
  284. package/docs/operations/controller-performance-and-502.md +126 -0
  285. package/docs/operations/controller-reliability-runbook.md +134 -0
  286. package/docs/operations/environment-hardening.md +50 -0
  287. package/docs/operations/features.md +45 -0
  288. package/docs/operations/features.zh-CN.md +45 -0
  289. package/docs/operations/github-repository.md +60 -0
  290. package/docs/operations/gmail-assistant-routines.md +186 -0
  291. package/docs/operations/homebrew.md +26 -0
  292. package/docs/operations/local-system-assistant.md +46 -0
  293. package/docs/operations/local-system-assistant.zh-CN.md +46 -0
  294. package/docs/operations/mcp-tool-exposure.md +58 -0
  295. package/docs/operations/mobile-intents-shortcuts.md +117 -0
  296. package/docs/operations/open-source-release-hygiene.md +31 -0
  297. package/docs/operations/personal-assistant-operator-guide.md +61 -0
  298. package/docs/operations/platform-support.md +62 -0
  299. package/docs/operations/platform-support.zh-CN.md +61 -0
  300. package/docs/operations/provider-configuration.md +31 -0
  301. package/docs/operations/releasing.md +86 -0
  302. package/docs/operations/releasing.zh-CN.md +86 -0
  303. package/docs/operations/route-session-concurrency-baseline.json +893 -0
  304. package/docs/operations/route-session-concurrency-baseline.md +94 -0
  305. package/docs/operations/runtime-performance-diagnostics.md +14 -0
  306. package/docs/operations/sqlite-control-plane-recovery.md +65 -0
  307. package/docs/operations/standalone-disaster-recovery.md +136 -0
  308. package/docs/operations/troubleshooting.md +72 -0
  309. package/docs/operations/troubleshooting.zh-CN.md +70 -0
  310. package/docs/public-usage-guide.md +33 -0
  311. package/docs/public-usage-guide.zh-CN.md +33 -0
  312. package/docs/tutorials/01-install-and-start.md +93 -0
  313. package/docs/tutorials/01-install-and-start.zh-CN.md +93 -0
  314. package/docs/tutorials/02-connect-chatgpt.md +82 -0
  315. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +80 -0
  316. package/docs/tutorials/03-first-repository-task.md +57 -0
  317. package/docs/tutorials/03-first-repository-task.zh-CN.md +57 -0
  318. package/docs/tutorials/README.md +13 -0
  319. package/docs/tutorials/README.zh-CN.md +13 -0
  320. package/docs/wiki/Architecture.md +85 -0
  321. package/docs/wiki/Core-Concepts.md +27 -0
  322. package/docs/wiki/Home.md +36 -0
  323. package/docs/wiki/Implementation.md +172 -0
  324. package/docs/wiki/Installation.md +50 -0
  325. package/docs/wiki/Integrations.md +29 -0
  326. package/docs/wiki/Operations.md +26 -0
  327. package/docs/wiki/Quick-Start.md +46 -0
  328. package/docs/wiki/Releases-and-Upgrades.md +34 -0
  329. package/docs/wiki/Runtime-Architecture.md +50 -0
  330. package/docs/wiki/Security-Model.md +31 -0
  331. package/docs/wiki/Troubleshooting.md +38 -0
  332. package/docs/wiki/Work-Lifecycle.md +31 -0
  333. package/docs/wiki/_Sidebar.md +21 -0
  334. package/install.ps1 +48 -0
  335. package/install.sh +66 -0
  336. package/package.json +156 -0
  337. package/scripts/activate-source-baseline.ts +335 -0
  338. package/scripts/architecture-event.ts +822 -0
  339. package/scripts/architecture-queue.sh +647 -0
  340. package/scripts/archive-architecture-request.sh +244 -0
  341. package/scripts/archive-workflow.sh +295 -0
  342. package/scripts/assemble-template.ts +1172 -0
  343. package/scripts/benchmark-controller-hotpath.ts +212 -0
  344. package/scripts/benchmark-controller-round2.ts +139 -0
  345. package/scripts/benchmark-ios-agent-device.ts +514 -0
  346. package/scripts/benchmark-route-session-concurrency.ts +714 -0
  347. package/scripts/benchmark-thin-harness-gateway-ab.ts +632 -0
  348. package/scripts/benchmark-thin-harness-mcp-e2e.ts +479 -0
  349. package/scripts/benchmark-thin-harness.ts +251 -0
  350. package/scripts/bootstrap-runtime-maintenance-recovery.sh +183 -0
  351. package/scripts/canary-global-hook.sh +263 -0
  352. package/scripts/capability-config.ts +394 -0
  353. package/scripts/capability-resolver.ts +505 -0
  354. package/scripts/capture-plan.sh +335 -0
  355. package/scripts/check-agent-tooling.sh +1549 -0
  356. package/scripts/check-architecture-sync.sh +373 -0
  357. package/scripts/check-brain-manifest.sh +255 -0
  358. package/scripts/check-ci.sh +16 -0
  359. package/scripts/check-context-files.sh +49 -0
  360. package/scripts/check-deploy-sql-order.sh +117 -0
  361. package/scripts/check-mcp-compatibility.ts +93 -0
  362. package/scripts/check-npm-release.sh +32 -0
  363. package/scripts/check-open-source-tracked-surface.sh +146 -0
  364. package/scripts/check-package-identity.mjs +117 -0
  365. package/scripts/check-platform-support.mjs +79 -0
  366. package/scripts/check-public-docs.sh +105 -0
  367. package/scripts/check-public-release-surface.sh +19 -0
  368. package/scripts/check-release-published.sh +57 -0
  369. package/scripts/check-release-readiness.sh +43 -0
  370. package/scripts/check-release-version.mjs +74 -0
  371. package/scripts/check-runtime-architecture.mjs +593 -0
  372. package/scripts/check-runtime-env.mjs +89 -0
  373. package/scripts/check-skill-version.ts +193 -0
  374. package/scripts/check-tarball-install-smoke.sh +66 -0
  375. package/scripts/check-task-sync.sh +65 -0
  376. package/scripts/check-task-workflow.sh +1002 -0
  377. package/scripts/check-third-party-notices.mjs +36 -0
  378. package/scripts/codex-handoff-resume.sh +269 -0
  379. package/scripts/complete-control-plane-cutover.ts +126 -0
  380. package/scripts/context-contract-sync.sh +516 -0
  381. package/scripts/contract-run.ts +465 -0
  382. package/scripts/contract-worktree.sh +725 -0
  383. package/scripts/controller-ngrok-rotation.sh +678 -0
  384. package/scripts/create-project-dirs.sh +282 -0
  385. package/scripts/ensure-codegraph.sh +16 -0
  386. package/scripts/ensure-task-workflow.sh +1218 -0
  387. package/scripts/export-requirement-portfolio.ts +28 -0
  388. package/scripts/factor-lab-check.sh +73 -0
  389. package/scripts/factor-lab-new.sh +96 -0
  390. package/scripts/factor-lab-promote.sh +86 -0
  391. package/scripts/factor-lab-reject.sh +68 -0
  392. package/scripts/harness-trace-grade.sh +150 -0
  393. package/scripts/heartbeat-triage.sh +361 -0
  394. package/scripts/hook-dispatch-diet-report.ts +234 -0
  395. package/scripts/hook-shim.sh +93 -0
  396. package/scripts/init-project.sh +505 -0
  397. package/scripts/initializer-question-pack.ts +200 -0
  398. package/scripts/inspect-project-state.ts +280 -0
  399. package/scripts/install-desktop-operator-registration.ts +45 -0
  400. package/scripts/install-standalone-recovery.ts +74 -0
  401. package/scripts/lib/AGENTS.md +13 -0
  402. package/scripts/lib/CLAUDE.md +13 -0
  403. package/scripts/lib/controller-home-env.sh +69 -0
  404. package/scripts/lib/project-init-lib.sh +2759 -0
  405. package/scripts/load-standalone-recovery.sh +17 -0
  406. package/scripts/loop-engine-cutover-gate.ts +324 -0
  407. package/scripts/maintenance-triage.sh +167 -0
  408. package/scripts/migrate-project-template.sh +1189 -0
  409. package/scripts/migrate-requirement-portfolio.ts +86 -0
  410. package/scripts/migrate-workflow-docs.ts +413 -0
  411. package/scripts/new-plan.sh +183 -0
  412. package/scripts/new-spec.sh +50 -0
  413. package/scripts/new-sprint.sh +49 -0
  414. package/scripts/open-source-audit-allowlist.txt +46 -0
  415. package/scripts/package-source-archive.sh +147 -0
  416. package/scripts/plan-to-todo.sh +799 -0
  417. package/scripts/prepare-codex-handoff.sh +203 -0
  418. package/scripts/prepare-handoff.sh +81 -0
  419. package/scripts/public-release-files.txt +39 -0
  420. package/scripts/public-release.ts +174 -0
  421. package/scripts/publish-release-tarball.sh +27 -0
  422. package/scripts/refresh-current-status.sh +469 -0
  423. package/scripts/refresh-ios-physical-runtime.ts +196 -0
  424. package/scripts/repair-executable-modes.ts +8 -0
  425. package/scripts/rollout-all-registered-repos.sh +7 -0
  426. package/scripts/route-nl-vs-ts-eval.ts +679 -0
  427. package/scripts/run-bun-test-file.ts +182 -0
  428. package/scripts/run-governed-gate.ts +140 -0
  429. package/scripts/run-mobile-monitor-bridge.ts +71 -0
  430. package/scripts/run-skill-evals.ts +1022 -0
  431. package/scripts/run-skill-hook.ts +244 -0
  432. package/scripts/run-supervised-command.ts +24 -0
  433. package/scripts/run-tests-portable.sh +20 -0
  434. package/scripts/select-agent-context-blocks.sh +66 -0
  435. package/scripts/setup-plugins.sh +43 -0
  436. package/scripts/ship-worktrees.sh +561 -0
  437. package/scripts/smoke-browser-cdp-bridge.ts +93 -0
  438. package/scripts/smoke-chatgpt-supervised-automation.ts +208 -0
  439. package/scripts/smoke-ios-physical-device.ts +114 -0
  440. package/scripts/smoke-mcp-http-runtime.ts +216 -0
  441. package/scripts/smoke-mcp-tool-surface.ts +117 -0
  442. package/scripts/smoke-runtime-recovery.ts +220 -0
  443. package/scripts/smoke-schedule-engine.ts +283 -0
  444. package/scripts/sprint-backlog.sh +837 -0
  445. package/scripts/stage-runtime-release.ts +38 -0
  446. package/scripts/summarize-failures.sh +125 -0
  447. package/scripts/switch-plan.sh +153 -0
  448. package/scripts/sync-brain-docs.sh +381 -0
  449. package/scripts/sync-codex-installed-copies.sh +183 -0
  450. package/scripts/test-governance.ts +75 -0
  451. package/scripts/verify-contract.sh +705 -0
  452. package/scripts/verify-forge-runtime.sh +31 -0
  453. package/scripts/verify-sprint.sh +680 -0
  454. package/scripts/windows-paths.d.mts +7 -0
  455. package/scripts/windows-paths.mjs +28 -0
  456. package/scripts/workflow-contract.ts +206 -0
  457. package/scripts/workstream-sync.sh +263 -0
  458. package/src/cli/agent-jobs/executable-resolver.ts +289 -0
  459. package/src/cli/agent-jobs/executor-health.ts +252 -0
  460. package/src/cli/agent-jobs/integration.ts +963 -0
  461. package/src/cli/agent-jobs/job-manager.ts +2030 -0
  462. package/src/cli/agent-jobs/types.ts +219 -0
  463. package/src/cli/agent-jobs/worker-lifecycle.ts +288 -0
  464. package/src/cli/chatgpt-browser/bind-server.ts +504 -0
  465. package/src/cli/chatgpt-browser/binding.ts +140 -0
  466. package/src/cli/chatgpt-browser/bridge-extension.ts +221 -0
  467. package/src/cli/chatgpt-browser/bridge-provider.ts +268 -0
  468. package/src/cli/chatgpt-browser/engine.ts +569 -0
  469. package/src/cli/chatgpt-browser/file-policy.ts +158 -0
  470. package/src/cli/chatgpt-browser/native-provider.ts +553 -0
  471. package/src/cli/chatgpt-browser/oracle-provider.ts +437 -0
  472. package/src/cli/chatgpt-browser/prompt-assembler.ts +29 -0
  473. package/src/cli/chatgpt-browser/session-store.ts +301 -0
  474. package/src/cli/chatgpt-browser/types.ts +172 -0
  475. package/src/cli/commands/adopt-plan.ts +186 -0
  476. package/src/cli/commands/brain-root.ts +132 -0
  477. package/src/cli/commands/brain.ts +609 -0
  478. package/src/cli/commands/capability-context.ts +613 -0
  479. package/src/cli/commands/chatgpt.ts +424 -0
  480. package/src/cli/commands/controller.ts +680 -0
  481. package/src/cli/commands/docs.ts +103 -0
  482. package/src/cli/commands/doctor.ts +476 -0
  483. package/src/cli/commands/global-runtime.ts +323 -0
  484. package/src/cli/commands/hook.ts +34 -0
  485. package/src/cli/commands/init-hook.ts +870 -0
  486. package/src/cli/commands/init.ts +829 -0
  487. package/src/cli/commands/install.ts +86 -0
  488. package/src/cli/commands/mcp.ts +315 -0
  489. package/src/cli/commands/migrate.ts +154 -0
  490. package/src/cli/commands/plugin.ts +74 -0
  491. package/src/cli/commands/prompt-guard-decision.ts +238 -0
  492. package/src/cli/commands/recovery.ts +667 -0
  493. package/src/cli/commands/repository.ts +224 -0
  494. package/src/cli/commands/run.ts +23 -0
  495. package/src/cli/commands/runtime.ts +191 -0
  496. package/src/cli/commands/security.ts +440 -0
  497. package/src/cli/commands/status.ts +137 -0
  498. package/src/cli/commands/tools.ts +167 -0
  499. package/src/cli/controller/check-runner.ts +1022 -0
  500. package/src/cli/controller/codex-continuation.ts +153 -0
  501. package/src/cli/controller/completion-backlog.ts +380 -0
  502. package/src/cli/controller/completion-orchestrator.ts +1807 -0
  503. package/src/cli/controller/completion-target.ts +96 -0
  504. package/src/cli/controller/composite-operations.ts +196 -0
  505. package/src/cli/controller/composite-result.ts +130 -0
  506. package/src/cli/controller/context-pack.ts +652 -0
  507. package/src/cli/controller/execution-completion.ts +175 -0
  508. package/src/cli/controller/execution-policy.ts +392 -0
  509. package/src/cli/controller/governance.ts +594 -0
  510. package/src/cli/controller/issue-store.ts +1421 -0
  511. package/src/cli/controller/launch-agents.ts +410 -0
  512. package/src/cli/controller/legacy-issue-cutover.ts +415 -0
  513. package/src/cli/controller/lifecycle-authority.ts +16 -0
  514. package/src/cli/controller/migrated-issue-decision.ts +237 -0
  515. package/src/cli/controller/operational-plan.ts +226 -0
  516. package/src/cli/controller/postcondition.ts +134 -0
  517. package/src/cli/controller/progress.ts +656 -0
  518. package/src/cli/controller/project-state.ts +147 -0
  519. package/src/cli/controller/requirement-portfolio-export.ts +146 -0
  520. package/src/cli/controller/requirement-portfolio-migration.ts +613 -0
  521. package/src/cli/controller/run-evidence.ts +43 -0
  522. package/src/cli/controller/runtime-config.ts +92 -0
  523. package/src/cli/controller/stuck-state-migration.ts +396 -0
  524. package/src/cli/controller/task-ledger.ts +632 -0
  525. package/src/cli/controller/task-status-resolver.ts +403 -0
  526. package/src/cli/controller/types.ts +273 -0
  527. package/src/cli/controller/work-mode.ts +186 -0
  528. package/src/cli/controller/worklog.ts +201 -0
  529. package/src/cli/diagnostic-entry.ts +39 -0
  530. package/src/cli/editing/edit-session.ts +1606 -0
  531. package/src/cli/editing/executable-modes.ts +70 -0
  532. package/src/cli/github/github.ts +274 -0
  533. package/src/cli/github/plugin.ts +345 -0
  534. package/src/cli/github/session.ts +104 -0
  535. package/src/cli/hook/prompt-guard-decision.ts +238 -0
  536. package/src/cli/hook/prompt-intents.ts +534 -0
  537. package/src/cli/hook/route-registry.ts +107 -0
  538. package/src/cli/hook/runtime.ts +303 -0
  539. package/src/cli/hook/state-snapshot.ts +388 -0
  540. package/src/cli/hook-entry.ts +51 -0
  541. package/src/cli/index.ts +574 -0
  542. package/src/cli/installer/managed-entries.ts +78 -0
  543. package/src/cli/installer/shared.ts +65 -0
  544. package/src/cli/installer/targets/claude.ts +136 -0
  545. package/src/cli/installer/targets/codex.ts +174 -0
  546. package/src/cli/installer/targets/registry.ts +28 -0
  547. package/src/cli/installer/types.ts +72 -0
  548. package/src/cli/local-bridge/connector-freshness.ts +529 -0
  549. package/src/cli/local-bridge/console-view-models.ts +324 -0
  550. package/src/cli/local-bridge/dashboard.ts +1312 -0
  551. package/src/cli/local-bridge/facade-api.ts +1877 -0
  552. package/src/cli/local-bridge/job-store.ts +1451 -0
  553. package/src/cli/local-bridge/mobile-intents.ts +344 -0
  554. package/src/cli/local-bridge/mobile-monitor.ts +209 -0
  555. package/src/cli/local-bridge/server.ts +3183 -0
  556. package/src/cli/local-bridge/types.ts +176 -0
  557. package/src/cli/mcp/access-mode.ts +132 -0
  558. package/src/cli/mcp/access-tools.ts +387 -0
  559. package/src/cli/mcp/audit.ts +32 -0
  560. package/src/cli/mcp/auth.ts +402 -0
  561. package/src/cli/mcp/instructions.ts +33 -0
  562. package/src/cli/mcp/legacy-context.ts +16 -0
  563. package/src/cli/mcp/legacy-tool-service.ts +5516 -0
  564. package/src/cli/mcp/multi-repository.ts +423 -0
  565. package/src/cli/mcp/oauth.ts +226 -0
  566. package/src/cli/mcp/paths.ts +134 -0
  567. package/src/cli/mcp/policy.ts +230 -0
  568. package/src/cli/mcp/proxy-env.ts +62 -0
  569. package/src/cli/mcp/readiness-projection.ts +24 -0
  570. package/src/cli/mcp/redaction.ts +21 -0
  571. package/src/cli/mcp/repo.ts +24 -0
  572. package/src/cli/mcp/repository-tools.ts +1295 -0
  573. package/src/cli/mcp/server.ts +341 -0
  574. package/src/cli/mcp/setup.ts +793 -0
  575. package/src/cli/mcp/tool-capability-cache.ts +87 -0
  576. package/src/cli/mcp/tools.ts +9 -0
  577. package/src/cli/mcp/toolset-names.ts +70 -0
  578. package/src/cli/mcp/toolset-selection.ts +41 -0
  579. package/src/cli/mcp/toolset.ts +300 -0
  580. package/src/cli/mcp/transports/AGENTS.md +39 -0
  581. package/src/cli/mcp/transports/CLAUDE.md +39 -0
  582. package/src/cli/mcp/transports/http.ts +1234 -0
  583. package/src/cli/mcp/transports/session-registry.ts +428 -0
  584. package/src/cli/mcp/transports/stdio.ts +8 -0
  585. package/src/cli/mcp/types.ts +41 -0
  586. package/src/cli/mcp/umbrella-tools.ts +117 -0
  587. package/src/cli/repo-adoption/reclaim-runtime.ts +681 -0
  588. package/src/cli/repositories/branch-name-policy.ts +57 -0
  589. package/src/cli/repositories/command-classifier.ts +600 -0
  590. package/src/cli/repositories/command-executor.ts +1066 -0
  591. package/src/cli/repositories/command-normalization.ts +53 -0
  592. package/src/cli/repositories/command-scope.ts +264 -0
  593. package/src/cli/repositories/controller-home.ts +88 -0
  594. package/src/cli/repositories/entity-migration.ts +199 -0
  595. package/src/cli/repositories/ephemeral-workspace.ts +62 -0
  596. package/src/cli/repositories/git-command-executor.ts +301 -0
  597. package/src/cli/repositories/goal-registry.ts +239 -0
  598. package/src/cli/repositories/identity.ts +64 -0
  599. package/src/cli/repositories/local-project-onboarding.ts +539 -0
  600. package/src/cli/repositories/locks.ts +262 -0
  601. package/src/cli/repositories/registry.ts +1057 -0
  602. package/src/cli/repositories/runtime-storage.ts +411 -0
  603. package/src/cli/repositories/safe-patch.ts +421 -0
  604. package/src/cli/repositories/selected-path-actions.ts +341 -0
  605. package/src/cli/repositories/structured-git.ts +257 -0
  606. package/src/cli/repositories/types.ts +155 -0
  607. package/src/cli/repositories/umbrella.ts +134 -0
  608. package/src/cli/repositories/workbench.ts +140 -0
  609. package/src/cli/repositories/worktree-storage.ts +137 -0
  610. package/src/cli/repository/inspector.ts +637 -0
  611. package/src/cli/repository/session-cache.ts +453 -0
  612. package/src/cli/runtime/helper-runner.ts +172 -0
  613. package/src/cli/runtime-invocation.ts +318 -0
  614. package/src/cli/tools/codegraph.ts +676 -0
  615. package/src/cli/v81-entry.ts +29 -0
  616. package/src/core/adoption/gitignore-plan.ts +127 -0
  617. package/src/core/adoption/helper-wrapper-plan.ts +159 -0
  618. package/src/core/adoption/manifest-templates.ts +60 -0
  619. package/src/core/adoption/modes.ts +7 -0
  620. package/src/core/adoption/operations.ts +126 -0
  621. package/src/core/adoption/plan.ts +149 -0
  622. package/src/core/adoption/render.ts +70 -0
  623. package/src/core/adoption/rollback.ts +62 -0
  624. package/src/core/adoption/summary.ts +43 -0
  625. package/src/core/adoption/workflow-contract-asset.ts +14 -0
  626. package/src/core/adoption/workflow-contract-plan.ts +31 -0
  627. package/src/effects/fs-transaction.ts +540 -0
  628. package/src/effects/git-executable.ts +59 -0
  629. package/src/effects/managed-block.ts +86 -0
  630. package/src/effects/path-safety.ts +47 -0
  631. package/src/effects/process-runner.ts +192 -0
  632. package/src/runtime/assistant/action-proposals.ts +279 -0
  633. package/src/runtime/assistant/intent.ts +395 -0
  634. package/src/runtime/assistant/model-provider.ts +460 -0
  635. package/src/runtime/assistant/openapi.ts +223 -0
  636. package/src/runtime/assistant/policy.ts +89 -0
  637. package/src/runtime/assistant/readiness.ts +258 -0
  638. package/src/runtime/assistant/routine-runtime.ts +477 -0
  639. package/src/runtime/assistant/schedule-binding.ts +171 -0
  640. package/src/runtime/assistant/standing-grants.ts +375 -0
  641. package/src/runtime/assistant/store.ts +243 -0
  642. package/src/runtime/assistant/types.ts +135 -0
  643. package/src/runtime/context/codegraph-read-provider.ts +347 -0
  644. package/src/runtime/context/codegraph-sidecar.cjs +207 -0
  645. package/src/runtime/control-plane/dispatch-priority.ts +67 -0
  646. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +151 -0
  647. package/src/runtime/control-plane/execution/execution-identity.ts +456 -0
  648. package/src/runtime/control-plane/execution/session-store.ts +175 -0
  649. package/src/runtime/control-plane/execution/validation.ts +117 -0
  650. package/src/runtime/control-plane/execution/verification-evidence.ts +191 -0
  651. package/src/runtime/control-plane/execution/work-handle-store.ts +225 -0
  652. package/src/runtime/control-plane/execution/work-prepare-request-store.ts +159 -0
  653. package/src/runtime/control-plane/execution/work-task-receipt.ts +445 -0
  654. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +821 -0
  655. package/src/runtime/control-plane/facade/capability-registry.ts +279 -0
  656. package/src/runtime/control-plane/facade/check-normalization.ts +233 -0
  657. package/src/runtime/control-plane/facade/codex-delegation.ts +196 -0
  658. package/src/runtime/control-plane/facade/controller-session-store.ts +199 -0
  659. package/src/runtime/control-plane/facade/facade-result.ts +41 -0
  660. package/src/runtime/control-plane/facade/goal-workloop-access.ts +177 -0
  661. package/src/runtime/control-plane/facade/goal-workloop.ts +1216 -0
  662. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +221 -0
  663. package/src/runtime/control-plane/facade/index.ts +16 -0
  664. package/src/runtime/control-plane/facade/operation-digest.ts +528 -0
  665. package/src/runtime/control-plane/facade/plan-contract-store.ts +403 -0
  666. package/src/runtime/control-plane/facade/policy-gate.ts +149 -0
  667. package/src/runtime/control-plane/facade/requirement-board.ts +280 -0
  668. package/src/runtime/control-plane/facade/self-healing-loop.ts +555 -0
  669. package/src/runtime/control-plane/facade/suggested-actions.ts +108 -0
  670. package/src/runtime/control-plane/facade/types.ts +729 -0
  671. package/src/runtime/control-plane/facade/work-admission-policy.ts +93 -0
  672. package/src/runtime/control-plane/facade/work-continuation.ts +104 -0
  673. package/src/runtime/control-plane/facade/work-contract-store.ts +1118 -0
  674. package/src/runtime/control-plane/facade/work-task-projection.ts +66 -0
  675. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +365 -0
  676. package/src/runtime/control-plane/global-scheduler/scheduler.ts +920 -0
  677. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +89 -0
  678. package/src/runtime/control-plane/goal-loop/config-facade.ts +700 -0
  679. package/src/runtime/control-plane/goal-loop/config-store.ts +570 -0
  680. package/src/runtime/control-plane/goal-loop/config-types.ts +213 -0
  681. package/src/runtime/control-plane/goal-loop/executor-router.ts +109 -0
  682. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +149 -0
  683. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +304 -0
  684. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +1001 -0
  685. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +160 -0
  686. package/src/runtime/control-plane/goal-loop/index.ts +13 -0
  687. package/src/runtime/control-plane/goal-loop/local-tools.ts +290 -0
  688. package/src/runtime/control-plane/goal-loop/policy-gates.ts +203 -0
  689. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +93 -0
  690. package/src/runtime/control-plane/goal-loop/provider-registry.ts +545 -0
  691. package/src/runtime/control-plane/goal-loop/types.ts +378 -0
  692. package/src/runtime/control-plane/governance/access-policy.ts +147 -0
  693. package/src/runtime/control-plane/governance/authorization.ts +195 -0
  694. package/src/runtime/control-plane/governance/external-effects.ts +43 -0
  695. package/src/runtime/control-plane/launcher/thin-launcher.ts +97 -0
  696. package/src/runtime/control-plane/persistence/metadata-payload-policy.ts +53 -0
  697. package/src/runtime/control-plane/persistence/requirement-store.ts +241 -0
  698. package/src/runtime/control-plane/persistence/sqlite-store.ts +539 -0
  699. package/src/runtime/control-plane/release-retention.ts +447 -0
  700. package/src/runtime/control-plane/repo-actor/actor.ts +181 -0
  701. package/src/runtime/control-plane/repo-actor/registry.ts +20 -0
  702. package/src/runtime/control-plane/routing/index.ts +1 -0
  703. package/src/runtime/control-plane/routing/route-policy.ts +379 -0
  704. package/src/runtime/control-plane/runtime-cleanup.ts +727 -0
  705. package/src/runtime/control-plane/runtime-generation.ts +509 -0
  706. package/src/runtime/control-plane/runtime-pid-ownership.ts +11 -0
  707. package/src/runtime/control-plane/runtime-status-client.ts +90 -0
  708. package/src/runtime/control-plane/startup-recovery.ts +198 -0
  709. package/src/runtime/diagnostics/connectivity-observability.ts +475 -0
  710. package/src/runtime/diagnostics/mcp-timing.ts +64 -0
  711. package/src/runtime/diagnostics/performance.ts +379 -0
  712. package/src/runtime/diagnostics/process-facade.ts +350 -0
  713. package/src/runtime/diagnostics/read-only-tool.ts +226 -0
  714. package/src/runtime/evidence/artifact-store.ts +154 -0
  715. package/src/runtime/evidence/event-ledger.ts +80 -0
  716. package/src/runtime/evidence/evidence-store.ts +68 -0
  717. package/src/runtime/evidence/process-check-receipt.ts +43 -0
  718. package/src/runtime/evidence/result-store.ts +209 -0
  719. package/src/runtime/evidence/sensitive-output.ts +353 -0
  720. package/src/runtime/execution/jobs/child-reference.ts +124 -0
  721. package/src/runtime/execution/jobs/legacy-adapter.ts +22 -0
  722. package/src/runtime/execution/jobs/receipt-recovery.ts +119 -0
  723. package/src/runtime/execution/jobs/receipt-store.ts +243 -0
  724. package/src/runtime/execution/jobs/store.ts +737 -0
  725. package/src/runtime/execution/jobs/timeouts.ts +109 -0
  726. package/src/runtime/execution/jobs/types.ts +230 -0
  727. package/src/runtime/execution/jobs/wait.ts +45 -0
  728. package/src/runtime/execution/process-runtime/canary.ts +26 -0
  729. package/src/runtime/execution/process-runtime/check-facade.ts +272 -0
  730. package/src/runtime/execution/process-runtime/check-receipt.ts +185 -0
  731. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +60 -0
  732. package/src/runtime/execution/process-runtime/command-facade.ts +321 -0
  733. package/src/runtime/execution/process-runtime/gc.ts +171 -0
  734. package/src/runtime/execution/process-runtime/index.ts +9 -0
  735. package/src/runtime/execution/process-runtime/interactive-admission.ts +23 -0
  736. package/src/runtime/execution/process-runtime/process-runner-entry.ts +466 -0
  737. package/src/runtime/execution/process-runtime/resource-claims.ts +440 -0
  738. package/src/runtime/execution/process-runtime/runtime.ts +1816 -0
  739. package/src/runtime/execution/process-runtime/store.ts +687 -0
  740. package/src/runtime/execution/process-runtime/types.ts +352 -0
  741. package/src/runtime/execution/thin-harness/async-process.ts +181 -0
  742. package/src/runtime/execution/thin-harness/batch-executor.ts +671 -0
  743. package/src/runtime/execution/thin-harness/execution-router.ts +700 -0
  744. package/src/runtime/execution/thin-harness/fast-executor.ts +1263 -0
  745. package/src/runtime/execution/thin-harness/fast-receipt.ts +235 -0
  746. package/src/runtime/execution/thin-harness/fingerprint-worker.ts +191 -0
  747. package/src/runtime/execution/thin-harness/index.ts +13 -0
  748. package/src/runtime/execution/thin-harness/latency-trace.ts +107 -0
  749. package/src/runtime/execution/thin-harness/lightweight-lanes.ts +745 -0
  750. package/src/runtime/execution/thin-harness/mutation-gate.ts +578 -0
  751. package/src/runtime/execution/thin-harness/proposal-store.ts +409 -0
  752. package/src/runtime/execution/thin-harness/request-ledger.ts +595 -0
  753. package/src/runtime/execution/thin-harness/search-worker.ts +114 -0
  754. package/src/runtime/execution/thin-harness/types.ts +313 -0
  755. package/src/runtime/execution/thin-harness/workspace-savepoint.ts +333 -0
  756. package/src/runtime/execution/workers/executor.ts +687 -0
  757. package/src/runtime/execution/workers/ownership.ts +94 -0
  758. package/src/runtime/execution/workers/worker-entry.ts +277 -0
  759. package/src/runtime/gateway/mcp/execution-tools.ts +2009 -0
  760. package/src/runtime/gateway/mcp/persisted-check-process.ts +255 -0
  761. package/src/runtime/gateway/mcp/process-tools.ts +230 -0
  762. package/src/runtime/gateway/mcp/resource-policy.ts +195 -0
  763. package/src/runtime/gateway/mcp/router.ts +1088 -0
  764. package/src/runtime/gateway/mcp/runtime-tools.ts +5994 -0
  765. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +219 -0
  766. package/src/runtime/health/evaluator.ts +474 -0
  767. package/src/runtime/health/index.ts +2 -0
  768. package/src/runtime/health/operational-view.ts +198 -0
  769. package/src/runtime/maintenance/cleanup.ts +419 -0
  770. package/src/runtime/model-clients/deepseek-adapter.ts +312 -0
  771. package/src/runtime/model-clients/index.ts +2 -0
  772. package/src/runtime/model-clients/types.ts +101 -0
  773. package/src/runtime/observability/gateway-contention-metrics.ts +129 -0
  774. package/src/runtime/observability/route-session-benchmark.ts +110 -0
  775. package/src/runtime/personal-assistant/gmail-triage-manager.ts +231 -0
  776. package/src/runtime/personal-assistant/reporting-runtime.ts +344 -0
  777. package/src/runtime/personal-assistant/triage-runtime.ts +748 -0
  778. package/src/runtime/plugins/app-store-connect-adapter.ts +868 -0
  779. package/src/runtime/plugins/browser-adapter.ts +2812 -0
  780. package/src/runtime/plugins/browser-automation-service.ts +46 -0
  781. package/src/runtime/plugins/browser-handoff-host.ts +304 -0
  782. package/src/runtime/plugins/browser-handoff.ts +371 -0
  783. package/src/runtime/plugins/browser-macos-bridge.ts +865 -0
  784. package/src/runtime/plugins/browser-node-bridge-host.ts +64 -0
  785. package/src/runtime/plugins/browser-node-bridge.ts +249 -0
  786. package/src/runtime/plugins/desktop-adapter.ts +374 -0
  787. package/src/runtime/plugins/desktop-operator-registration.ts +418 -0
  788. package/src/runtime/plugins/errors.ts +39 -0
  789. package/src/runtime/plugins/execution-origin.ts +26 -0
  790. package/src/runtime/plugins/external-adapter.ts +322 -0
  791. package/src/runtime/plugins/external-registration.ts +308 -0
  792. package/src/runtime/plugins/external-unix-socket-probe.cjs +79 -0
  793. package/src/runtime/plugins/external-unix-socket.ts +238 -0
  794. package/src/runtime/plugins/first-party-registry.ts +37 -0
  795. package/src/runtime/plugins/github-adapter.ts +266 -0
  796. package/src/runtime/plugins/gmail-adapter.ts +729 -0
  797. package/src/runtime/plugins/google-calendar-adapter.ts +468 -0
  798. package/src/runtime/plugins/google-shared.ts +578 -0
  799. package/src/runtime/plugins/google-tasks-adapter.ts +535 -0
  800. package/src/runtime/plugins/interaction-session.ts +252 -0
  801. package/src/runtime/plugins/ios/agent-device-capabilities.ts +258 -0
  802. package/src/runtime/plugins/ios/agent-device-failures.ts +95 -0
  803. package/src/runtime/plugins/ios/agent-device-provider.ts +63 -0
  804. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +268 -0
  805. package/src/runtime/plugins/ios/app-adapters.ts +122 -0
  806. package/src/runtime/plugins/ios-adapter.ts +509 -0
  807. package/src/runtime/plugins/ios-agent-device.ts +2311 -0
  808. package/src/runtime/plugins/ios-physical-device.ts +955 -0
  809. package/src/runtime/plugins/local-system-adapter.ts +1016 -0
  810. package/src/runtime/plugins/macos-capability-broker.ts +73 -0
  811. package/src/runtime/plugins/managed-process-adapter.ts +366 -0
  812. package/src/runtime/plugins/resend-adapter.ts +506 -0
  813. package/src/runtime/plugins/store.ts +1025 -0
  814. package/src/runtime/plugins/types.ts +138 -0
  815. package/src/runtime/projections/controller-context.ts +596 -0
  816. package/src/runtime/projections/git-status-sampler.ts +84 -0
  817. package/src/runtime/projections/invalidation.ts +250 -0
  818. package/src/runtime/projections/materialized-view.ts +694 -0
  819. package/src/runtime/recovery/actions.ts +158 -0
  820. package/src/runtime/recovery/audit.ts +38 -0
  821. package/src/runtime/recovery/classifier.ts +163 -0
  822. package/src/runtime/recovery/index.ts +9 -0
  823. package/src/runtime/recovery/local-jobs-repair.ts +393 -0
  824. package/src/runtime/recovery/maintenance-executor.ts +822 -0
  825. package/src/runtime/recovery/patch-handoff.ts +50 -0
  826. package/src/runtime/recovery/probe.ts +308 -0
  827. package/src/runtime/recovery/store.ts +35 -0
  828. package/src/runtime/recovery/types.ts +175 -0
  829. package/src/runtime/release/identity.ts +101 -0
  830. package/src/runtime/release/release-gate.ts +137 -0
  831. package/src/runtime/resources/claims/conflicts.ts +205 -0
  832. package/src/runtime/resources/index.ts +1 -0
  833. package/src/runtime/resources/leases/store.ts +539 -0
  834. package/src/runtime/resources/leases/types.ts +50 -0
  835. package/src/runtime/resources/ownership.ts +72 -0
  836. package/src/runtime/root/controller-services.ts +42 -0
  837. package/src/runtime/root/entry.ts +70 -0
  838. package/src/runtime/root/gateway-adapter.ts +46 -0
  839. package/src/runtime/root/local-bridge.ts +72 -0
  840. package/src/runtime/root/mcp-transport.ts +198 -0
  841. package/src/runtime/root/ownership.ts +94 -0
  842. package/src/runtime/root/readiness.ts +69 -0
  843. package/src/runtime/root/release-manifest.ts +147 -0
  844. package/src/runtime/root/release-materialize.ts +509 -0
  845. package/src/runtime/root/release-store.ts +236 -0
  846. package/src/runtime/root/runtime.ts +318 -0
  847. package/src/runtime/root/scheduler.ts +58 -0
  848. package/src/runtime/root/service-runner-entry.ts +20 -0
  849. package/src/runtime/root/service-runner.ts +69 -0
  850. package/src/runtime/root/service.ts +291 -0
  851. package/src/runtime/root/status.ts +127 -0
  852. package/src/runtime/root/types.ts +112 -0
  853. package/src/runtime/root/write-fence.ts +309 -0
  854. package/src/runtime/safe-tooling/browser-safe.ts +133 -0
  855. package/src/runtime/safe-tooling/external-filesystem.ts +304 -0
  856. package/src/runtime/safe-tooling/google-credential-store.ts +95 -0
  857. package/src/runtime/safe-tooling/google-oauth-broker.ts +286 -0
  858. package/src/runtime/safe-tooling/index.ts +11 -0
  859. package/src/runtime/safe-tooling/ios-development.ts +1039 -0
  860. package/src/runtime/safe-tooling/job-summary.ts +361 -0
  861. package/src/runtime/safe-tooling/plugin-summary.ts +53 -0
  862. package/src/runtime/safe-tooling/resend-oauth.ts +387 -0
  863. package/src/runtime/safe-tooling/review-artifacts.ts +133 -0
  864. package/src/runtime/safe-tooling/types.ts +103 -0
  865. package/src/runtime/safe-tooling/workspace-auth.ts +116 -0
  866. package/src/runtime/shared/bounded-child-supervisor.ts +235 -0
  867. package/src/runtime/shared/idempotent-retry.ts +128 -0
  868. package/src/runtime/shared/json-files.ts +43 -0
  869. package/src/runtime/shared/local-bridge-surface.ts +231 -0
  870. package/src/runtime/shared/managed-env.ts +154 -0
  871. package/src/runtime/shared/node-ts-loader.mjs +50 -0
  872. package/src/runtime/shared/process-environment.ts +97 -0
  873. package/src/runtime/shared/process-identity.ts +72 -0
  874. package/src/runtime/shared/process-tree.ts +227 -0
  875. package/src/runtime/shared/response-budget.ts +246 -0
  876. package/src/runtime/standalone-recovery/core.ts +2275 -0
  877. package/src/runtime/standalone-recovery/entry.ts +686 -0
  878. package/src/runtime/standalone-recovery/http-transport.ts +317 -0
  879. package/src/runtime/standalone-recovery/installer.ts +747 -0
  880. package/src/runtime/standalone-recovery/release.ts +241 -0
  881. package/src/runtime/watchdog/workflow-watchdog.ts +118 -0
  882. package/src/runtime/workflow/campaigns/cleanup.ts +263 -0
  883. package/src/runtime/workflow/campaigns/engine.ts +112 -0
  884. package/src/runtime/workflow/campaigns/normalize.ts +100 -0
  885. package/src/runtime/workflow/campaigns/review.ts +315 -0
  886. package/src/runtime/workflow/campaigns/store.ts +526 -0
  887. package/src/runtime/workflow/campaigns/supervisor.ts +112 -0
  888. package/src/runtime/workflow/campaigns/types.ts +290 -0
  889. package/src/runtime/workflow/campaigns/workspace-agent.ts +155 -0
  890. package/src/runtime/workflow/campaigns/workspace.ts +200 -0
  891. package/src/runtime/workflow/findings/store.ts +165 -0
  892. package/src/runtime/workflow/findings/types.ts +36 -0
  893. package/src/runtime/workflow/portfolio/engine.ts +20 -0
  894. package/src/runtime/workflow/portfolio/store.ts +185 -0
  895. package/src/runtime/workflow/portfolio/types.ts +29 -0
  896. package/src/runtime/workflow/schedules/engine.ts +620 -0
  897. package/src/runtime/workflow/schedules/settlement.ts +144 -0
  898. package/src/runtime/workflow/schedules/store.ts +417 -0
  899. package/src/runtime/workflow/schedules/types.ts +124 -0
  900. package/src/runtime/workspace-targets/index.ts +24 -0
  901. package/src/runtime/workspace-targets/target-grant.ts +721 -0
  902. package/src/testing/test-governance.ts +626 -0
  903. package/src/version.ts +21 -0
@@ -0,0 +1,2030 @@
1
+ import { createHash, randomBytes } from "crypto";
2
+ import {
3
+ closeSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ openSync,
7
+ readFileSync,
8
+ readdirSync,
9
+ renameSync,
10
+ rmSync,
11
+ writeFileSync,
12
+ appendFileSync,
13
+ } from "fs";
14
+ import { dirname, join, relative } from "path";
15
+ import { spawn, spawnSync } from "child_process";
16
+ import { fileURLToPath } from "url";
17
+ import { runProcess } from "../../effects/process-runner";
18
+ import {
19
+ getGitHubAgentSession,
20
+ getGitHubAgentSessionLog,
21
+ startGitHubAgentSession,
22
+ } from "../github/session";
23
+ import { getGitHubStatus } from "../github/github";
24
+ import { getIssue, inspectTaskReadiness, removeEphemeralIssue, updateTask } from "../controller/issue-store";
25
+ import { readTaskRunEvidence } from "../controller/run-evidence";
26
+ import { resolveEffectiveTaskState } from "../controller/task-status-resolver";
27
+ import type { ControllerAgent, ControllerTask } from "../controller/types";
28
+ import { tryAppendControllerWorklogEvent } from "../controller/worklog";
29
+ import { continueTaskAfterSuccessfulRun } from "../controller/execution-completion";
30
+ import { executionScopesConflict, taskExecutionPolicy } from "../controller/execution-policy";
31
+ import {
32
+ DEFAULT_AGENT_TIMEOUT_MS,
33
+ MAX_AGENT_TIMEOUT_MS,
34
+ normalizeAgentTimeoutMs,
35
+ repositoryIdentity,
36
+ } from "../controller/runtime-config";
37
+ import { normalizeRemoteUrl, stableCheckoutId, stableRemoteRepoId } from "../repositories/identity";
38
+ import { resolveRepoPreferredControllerHome } from "../repositories/controller-home";
39
+ import { listRepositories } from "../repositories/registry";
40
+ import { managedWorktreePath } from "../repositories/worktree-storage";
41
+ import {
42
+ ensureControllerEpoch,
43
+ hasControllerOwnershipMetadata,
44
+ invalidateAgentWorker,
45
+ isPidAlive,
46
+ matchesAgentWorkerCommand,
47
+ shouldTolerateOwnedFinalizationInvalidation,
48
+ } from "./worker-lifecycle";
49
+ import { terminateProcessTreeSync } from "../../runtime/shared/process-tree";
50
+ import { managedResource } from "../../runtime/resources";
51
+ import type {
52
+ AgentExecutionMode,
53
+ AgentJobEvent,
54
+ AgentJobClosureState,
55
+ AgentJobMeta,
56
+ AgentJobPreservationReason,
57
+ AgentJobStatus,
58
+ AgentJobWorkerConfig,
59
+ } from "./types";
60
+ import {
61
+ classifyExecutorFailure,
62
+ classifyGitHubCopilotPreflight,
63
+ classifyLocalExecutorHealth,
64
+ ExecutorHealthError,
65
+ isExecutorHealthError,
66
+ type LocalExecutorPolicy,
67
+ } from "./executor-health";
68
+ import {
69
+ AgentExecutableError,
70
+ agentProcessEnv,
71
+ assertAgentExecutableReady,
72
+ resolveAgentExecutable,
73
+ } from "./executable-resolver";
74
+
75
+ const JOB_ROOT = ".ai/harness/jobs";
76
+ const RUN_LAUNCH_LOCK = ".ai/harness/controller/run-launch.lock";
77
+ const RUN_REQUEST_INDEX_ROOT = ".ai/harness/controller/run-requests";
78
+ const RUN_INDEX_ROOT = ".ai/harness/controller/run-indexes";
79
+ const RUN_INDEX_LOCK = ".ai/harness/controller/run-index.lock";
80
+ const RUN_LAUNCH_LOCK_STALE_MS = 30_000;
81
+ const RUN_LAUNCH_LOCK_WAIT_MS = 500;
82
+ const RUN_LAUNCH_LOCK_POLL_MS = 10;
83
+ const DEFAULT_AGENT_STARTUP_TIMEOUT_MS = 90_000;
84
+ const MIN_AGENT_STARTUP_TIMEOUT_MS = 30_000;
85
+ const MAX_AGENT_STARTUP_TIMEOUT_MS = 300_000;
86
+
87
+ export function agentStartupTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {
88
+ const configured = Number(env.FORGE_AGENT_STARTUP_TIMEOUT_MS);
89
+ if (!Number.isFinite(configured)) return DEFAULT_AGENT_STARTUP_TIMEOUT_MS;
90
+ return Math.max(MIN_AGENT_STARTUP_TIMEOUT_MS, Math.min(Math.trunc(configured), MAX_AGENT_STARTUP_TIMEOUT_MS));
91
+ }
92
+
93
+ function shortId(): string {
94
+ return randomBytes(4).toString("hex");
95
+ }
96
+
97
+ function sanitize(value: string): string {
98
+ return (
99
+ value
100
+ .toLowerCase()
101
+ .replace(/[^a-z0-9-]+/g, "-")
102
+ .replace(/^-+|-+$/g, "")
103
+ .slice(0, 50) || "task"
104
+ );
105
+ }
106
+
107
+ function readJson<T>(path: string): T {
108
+ return JSON.parse(readFileSync(path, "utf-8")) as T;
109
+ }
110
+
111
+ function normalizeAgentMeta(
112
+ repoRoot: string,
113
+ runId: string,
114
+ meta: AgentJobMeta,
115
+ ): AgentJobMeta {
116
+ meta.schemaVersion = meta.schemaVersion ?? 1;
117
+ meta.provider = meta.provider ?? "local";
118
+ meta.executionMode =
119
+ meta.executionMode ??
120
+ (meta.provider === "github"
121
+ ? "github"
122
+ : meta.worktree === repoRoot
123
+ ? "workspace"
124
+ : "worktree");
125
+ meta.autoIntegrate = meta.autoIntegrate ?? meta.executionMode === "worktree";
126
+ meta.closureState = meta.closureState ?? (
127
+ meta.provider === "local" && meta.executionMode === "worktree"
128
+ ? meta.worktreeCleanedAt
129
+ ? "completed"
130
+ : meta.status === "waiting_for_user"
131
+ ? "preserved"
132
+ : meta.integratedSessionId
133
+ ? "cleanup_pending"
134
+ : ["queued", "starting", "running", "succeeded"].includes(meta.status)
135
+ ? "integration_pending"
136
+ : "none"
137
+ : meta.status === "succeeded"
138
+ ? "completed"
139
+ : "none"
140
+ );
141
+ meta.eventsPath =
142
+ meta.eventsPath ??
143
+ relative(repoRoot, eventPath(repoRoot, runId)).replace(/\\/g, "/");
144
+ return meta;
145
+ }
146
+
147
+ function writeJson(path: string, value: unknown): void {
148
+ mkdirSync(dirname(path), { recursive: true });
149
+ const temporaryPath = `${path}.${process.pid}.tmp`;
150
+ writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
151
+ renameSync(temporaryPath, path);
152
+ }
153
+
154
+ function isAlive(pid: number | undefined): boolean {
155
+ return isPidAlive(pid);
156
+ }
157
+
158
+ function atomicCreateJson(path: string, value: unknown): boolean {
159
+ mkdirSync(dirname(path), { recursive: true });
160
+ try {
161
+ const fd = openSync(path, "wx", 0o600);
162
+ try {
163
+ writeFileSync(fd, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
164
+ } finally {
165
+ closeSync(fd);
166
+ }
167
+ return true;
168
+ } catch (error) {
169
+ if ((error as NodeJS.ErrnoException).code === "EEXIST") return false;
170
+ throw error;
171
+ }
172
+ }
173
+
174
+ function tryReadRepositoryIdentity(repoRoot: string): { repoId: string; checkoutId: string } {
175
+ const path = join(repoRoot, ".ai/harness/repository.json");
176
+ let repoId = "";
177
+ try {
178
+ if (existsSync(path)) {
179
+ const parsed = readJson<{ repoId?: string }>(path);
180
+ repoId = typeof parsed.repoId === "string" ? parsed.repoId.trim() : "";
181
+ }
182
+ } catch (_error) {
183
+ repoId = "";
184
+ }
185
+ if (!repoId) {
186
+ const remote = normalizeRemoteUrl(
187
+ runProcess("git", ["config", "--get", "remote.origin.url"], {
188
+ cwd: repoRoot,
189
+ timeoutMs: 10_000,
190
+ maxOutputBytes: 8 * 1024,
191
+ }).stdout.trim(),
192
+ );
193
+ repoId = remote ? stableRemoteRepoId(remote) : repositoryIdentity(repoRoot);
194
+ }
195
+ return {
196
+ repoId,
197
+ checkoutId: stableCheckoutId(repoId, repoRoot),
198
+ };
199
+ }
200
+
201
+ function withRunLaunchLock<T>(repoRoot: string, action: () => T): T {
202
+ const path = join(repoRoot, RUN_LAUNCH_LOCK);
203
+ mkdirSync(dirname(path), { recursive: true });
204
+ const acquire = (): number => {
205
+ try {
206
+ const fd = openSync(path, "wx", 0o600);
207
+ writeFileSync(fd, `${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() })}\n`, "utf-8");
208
+ return fd;
209
+ } catch (error) {
210
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
211
+ let stale = true;
212
+ try {
213
+ const lock = readJson<{ pid?: number; createdAt?: string }>(path);
214
+ const createdAt = Date.parse(lock.createdAt ?? "");
215
+ stale =
216
+ !Number.isFinite(createdAt) ||
217
+ Date.now() - createdAt > RUN_LAUNCH_LOCK_STALE_MS ||
218
+ !isAlive(lock.pid);
219
+ } catch (_readError) {
220
+ stale = true;
221
+ }
222
+ if (stale) {
223
+ rmSync(path, { force: true });
224
+ return acquire();
225
+ }
226
+ return -1;
227
+ }
228
+ };
229
+
230
+ const deadline = Date.now() + RUN_LAUNCH_LOCK_WAIT_MS;
231
+ let fd = acquire();
232
+ while (fd < 0 && Date.now() < deadline) {
233
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, RUN_LAUNCH_LOCK_POLL_MS);
234
+ fd = acquire();
235
+ }
236
+ if (fd < 0)
237
+ throw new Error("another Controller is preparing a local Task Run; retry after its workspace reservation is visible");
238
+ try {
239
+ return action();
240
+ } finally {
241
+ closeSync(fd);
242
+ rmSync(path, { force: true });
243
+ }
244
+ }
245
+
246
+ function jobDir(repoRoot: string, runId: string): string {
247
+ return join(repoRoot, JOB_ROOT, runId);
248
+ }
249
+
250
+ function metaPath(repoRoot: string, runId: string): string {
251
+ return join(jobDir(repoRoot, runId), "meta.json");
252
+ }
253
+
254
+ function eventPath(repoRoot: string, runId: string): string {
255
+ return join(jobDir(repoRoot, runId), "events.jsonl");
256
+ }
257
+
258
+ function eventReceiptPath(repoRoot: string, runId: string, eventId: string): string {
259
+ return join(jobDir(repoRoot, runId), "event-receipts", `${createHash("sha256").update(eventId).digest("hex")}.json`);
260
+ }
261
+
262
+ function rawJobMetaPath(repoRoot: string, runId: string): string {
263
+ return join(jobDir(repoRoot, runId), "meta.json");
264
+ }
265
+
266
+ function readRawAgentMeta(
267
+ repoRoot: string,
268
+ runId: string,
269
+ ): AgentJobMeta | undefined {
270
+ const path = rawJobMetaPath(repoRoot, runId);
271
+ if (!existsSync(path)) return undefined;
272
+ try {
273
+ return normalizeAgentMeta(repoRoot, runId, readJson<AgentJobMeta>(path));
274
+ } catch (_error) {
275
+ return undefined;
276
+ }
277
+ }
278
+
279
+ function requestIndexDir(repoRoot: string): string {
280
+ return join(repoRoot, RUN_REQUEST_INDEX_ROOT);
281
+ }
282
+
283
+ function requestIndexPath(repoRoot: string, requestId: string): string {
284
+ const digest = createHash("sha256").update(requestId).digest("hex");
285
+ return join(requestIndexDir(repoRoot), `${digest}.json`);
286
+ }
287
+
288
+ interface AgentRunIndexEntry {
289
+ runId: string;
290
+ issueId: string;
291
+ taskId: string;
292
+ status: AgentJobStatus;
293
+ createdAt: string;
294
+ updatedAt: string;
295
+ executionMode: AgentExecutionMode;
296
+ integratedSessionId?: string;
297
+ }
298
+
299
+ interface AgentRunIndex {
300
+ schemaVersion: 1;
301
+ updatedAt: string;
302
+ runs: AgentRunIndexEntry[];
303
+ }
304
+
305
+ function runIndexPath(repoRoot: string, name: 'active' | 'recent' | 'pending-integration'): string {
306
+ return join(repoRoot, RUN_INDEX_ROOT, `${name}.json`);
307
+ }
308
+
309
+ function taskRunIndexPath(repoRoot: string, issueId: string, taskId: string): string {
310
+ const digest = createHash('sha256').update(`${issueId}\0${taskId}`).digest('hex');
311
+ return join(repoRoot, RUN_INDEX_ROOT, 'tasks', `${digest}.json`);
312
+ }
313
+
314
+ function withRunIndexLock<T>(repoRoot: string, action: () => T): T {
315
+ const path = join(repoRoot, RUN_INDEX_LOCK);
316
+ mkdirSync(dirname(path), { recursive: true });
317
+ const deadline = Date.now() + 2_000;
318
+ let descriptor = -1;
319
+ while (descriptor < 0) {
320
+ try {
321
+ descriptor = openSync(path, 'wx', 0o600);
322
+ writeFileSync(descriptor, `${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() })}\n`, 'utf8');
323
+ } catch (error) {
324
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
325
+ let stale = false;
326
+ try {
327
+ const lock = readJson<{ pid?: number; createdAt?: string }>(path);
328
+ stale = !lock.pid || !isAlive(lock.pid) || Date.now() - Date.parse(lock.createdAt ?? '') > 30_000;
329
+ } catch { stale = true; }
330
+ if (stale) rmSync(path, { force: true });
331
+ else if (Date.now() >= deadline) throw new Error('agent run index is busy; retry');
332
+ else Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
333
+ }
334
+ }
335
+ try { return action(); }
336
+ finally { closeSync(descriptor); rmSync(path, { force: true }); }
337
+ }
338
+
339
+ function readRunIndex(repoRoot: string, name: 'active' | 'recent' | 'pending-integration'): AgentRunIndex {
340
+ const path = runIndexPath(repoRoot, name);
341
+ if (!existsSync(path)) return { schemaVersion: 1, updatedAt: new Date().toISOString(), runs: [] };
342
+ try { return readJson<AgentRunIndex>(path); }
343
+ catch { return { schemaVersion: 1, updatedAt: new Date().toISOString(), runs: [] }; }
344
+ }
345
+
346
+ function updateRunIndexes(repoRoot: string, meta: AgentJobMeta): void {
347
+ withRunIndexLock(repoRoot, () => {
348
+ const entry: AgentRunIndexEntry = {
349
+ runId: meta.runId,
350
+ issueId: meta.issueId,
351
+ taskId: meta.taskId,
352
+ status: meta.status,
353
+ createdAt: meta.createdAt,
354
+ updatedAt: meta.lastHeartbeatAt ?? meta.finishedAt ?? meta.startedAt ?? meta.createdAt,
355
+ executionMode: meta.executionMode,
356
+ integratedSessionId: meta.integratedSessionId,
357
+ };
358
+ const active = readRunIndex(repoRoot, 'active');
359
+ active.runs = active.runs.filter((candidate) => candidate.runId !== meta.runId);
360
+ if (['queued', 'starting', 'running'].includes(meta.status)) active.runs.push(entry);
361
+ active.updatedAt = new Date().toISOString();
362
+ active.runs = active.runs.sort((a, b) => a.createdAt.localeCompare(b.createdAt)).slice(-5000);
363
+ writeJson(runIndexPath(repoRoot, 'active'), active);
364
+
365
+ const recent = readRunIndex(repoRoot, 'recent');
366
+ recent.runs = recent.runs.filter((candidate) => candidate.runId !== meta.runId);
367
+ recent.runs.unshift(entry);
368
+ recent.updatedAt = new Date().toISOString();
369
+ recent.runs = recent.runs.sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, 5000);
370
+ writeJson(runIndexPath(repoRoot, 'recent'), recent);
371
+
372
+ const pendingIntegration = readRunIndex(repoRoot, 'pending-integration');
373
+ pendingIntegration.runs = pendingIntegration.runs.filter((candidate) => candidate.runId !== meta.runId);
374
+ if (meta.closureState && [
375
+ 'ready_to_integrate', 'integration_pending', 'integrating', 'integration_blocked',
376
+ 'integrated', 'cleanup_pending', 'cleaning', 'cleanup_blocked', 'preserved',
377
+ ].includes(meta.closureState)) pendingIntegration.runs.push(entry);
378
+ pendingIntegration.updatedAt = new Date().toISOString();
379
+ pendingIntegration.runs = pendingIntegration.runs.sort((a, b) => a.createdAt.localeCompare(b.createdAt)).slice(-5000);
380
+ writeJson(runIndexPath(repoRoot, 'pending-integration'), pendingIntegration);
381
+
382
+ const taskPath = taskRunIndexPath(repoRoot, meta.issueId, meta.taskId);
383
+ const taskIndex = existsSync(taskPath)
384
+ ? readJson<{ schemaVersion: 1; issueId: string; taskId: string; runIds: string[]; activeRunId?: string; updatedAt: string }>(taskPath)
385
+ : { schemaVersion: 1 as const, issueId: meta.issueId, taskId: meta.taskId, runIds: [], updatedAt: new Date().toISOString() };
386
+ taskIndex.runIds = [meta.runId, ...taskIndex.runIds.filter((runId) => runId !== meta.runId)].slice(0, 500);
387
+ taskIndex.activeRunId = ['queued', 'starting', 'running'].includes(meta.status) ? meta.runId : (taskIndex.activeRunId === meta.runId ? undefined : taskIndex.activeRunId);
388
+ taskIndex.updatedAt = new Date().toISOString();
389
+ writeJson(taskPath, taskIndex);
390
+ });
391
+ }
392
+
393
+ function writeAgentMeta(repoRoot: string, path: string, meta: AgentJobMeta): void {
394
+ if (meta.status === "succeeded") normalizeSuccessfulAgentRunMeta(meta);
395
+ writeJson(path, meta);
396
+ updateRunIndexes(repoRoot, meta);
397
+ }
398
+
399
+ function terminateRunProcess(pid: number | undefined): void {
400
+ if (!pid || pid === process.pid || !isAlive(pid)) return;
401
+ terminateProcessTreeSync(pid, {
402
+ gracePeriodMs: 100,
403
+ killAfterMs: 2_000,
404
+ pollIntervalMs: 25,
405
+ });
406
+ }
407
+
408
+ function terminateRunProcesses(meta: Pick<AgentJobMeta, "agentPid" | "workerPid" | "launchPid">): void {
409
+ for (const pid of [meta.agentPid, meta.workerPid, meta.launchPid]) {
410
+ terminateRunProcess(pid);
411
+ }
412
+ }
413
+
414
+ function cancellationCleanupClaimPath(repoRoot: string, runId: string): string {
415
+ return join(jobDir(repoRoot, runId), "cancel-cleanup.lock.json");
416
+ }
417
+
418
+ function cancellationProcessIds(meta: Pick<AgentJobMeta, "agentPid" | "workerPid" | "launchPid" | "cancellationPids">): number[] {
419
+ return [...new Set([
420
+ ...(meta.cancellationPids ?? []),
421
+ meta.agentPid,
422
+ meta.workerPid,
423
+ meta.launchPid,
424
+ ].filter((pid): pid is number => Boolean(pid && pid > 0 && pid !== process.pid)))];
425
+ }
426
+
427
+ function claimCancellationCleanup(repoRoot: string, runId: string): boolean {
428
+ const path = cancellationCleanupClaimPath(repoRoot, runId);
429
+ const claim = () => atomicCreateJson(path, {
430
+ pid: process.pid,
431
+ runId,
432
+ claimedAt: new Date().toISOString(),
433
+ });
434
+ if (claim()) return true;
435
+ try {
436
+ const existing = readJson<{ pid?: number }>(path);
437
+ if (isAlive(existing.pid)) return false;
438
+ } catch (_error) {
439
+ /* malformed or partially written claims are stale */
440
+ }
441
+ rmSync(path, { force: true });
442
+ return claim();
443
+ }
444
+
445
+ function requestCancellationCleanup(repoRoot: string, runId: string): void {
446
+ try {
447
+ const existingPath = cancellationCleanupClaimPath(repoRoot, runId);
448
+ if (existsSync(existingPath)) {
449
+ try {
450
+ const existing = readJson<{ pid?: number }>(existingPath);
451
+ if (isAlive(existing.pid)) return;
452
+ } catch (_error) {
453
+ /* the detached cleaner will repair a stale claim */
454
+ }
455
+ }
456
+ const cleaner = spawn(
457
+ process.execPath,
458
+ [fileURLToPath(new URL("./job-manager.ts", import.meta.url)), "cancel-cleanup", repoRoot, runId],
459
+ { cwd: repoRoot, detached: true, stdio: "ignore" },
460
+ );
461
+ cleaner.unref();
462
+ cleaner.once("error", (error) => {
463
+ try {
464
+ const current = getAgentJob(repoRoot, runId);
465
+ if (current.status !== "cancelled" || !current.cleanupPending) return;
466
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = current;
467
+ meta.cleanupError = error.message;
468
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), meta);
469
+ } catch (_nested) {
470
+ /* cancellation intent is already durable; a later retry can restart cleanup */
471
+ }
472
+ });
473
+ } catch (error) {
474
+ const current = getAgentJob(repoRoot, runId);
475
+ if (current.status !== "cancelled" || !current.cleanupPending) return;
476
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = current;
477
+ meta.cleanupError = error instanceof Error ? error.message : String(error);
478
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), meta);
479
+ }
480
+ }
481
+
482
+ export function completeAgentJobCancellation(repoRoot: string, runId: string): AgentJobMeta {
483
+ const claimPath = cancellationCleanupClaimPath(repoRoot, runId);
484
+ if (!claimCancellationCleanup(repoRoot, runId)) return getAgentJob(repoRoot, runId);
485
+ try {
486
+ const current = getAgentJob(repoRoot, runId);
487
+ if (current.status !== "cancelled" || !current.cleanupPending) return current;
488
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = current;
489
+ meta.cleanupStartedAt = meta.cleanupStartedAt ?? new Date().toISOString();
490
+ meta.cleanupError = undefined;
491
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), meta);
492
+
493
+ for (const pid of cancellationProcessIds(meta)) terminateRunProcess(pid);
494
+
495
+ const latest = getAgentJob(repoRoot, runId);
496
+ if (latest.status !== "cancelled") return latest;
497
+ const { stdoutTail: _latestStdout, stderrTail: _latestStderr, ...cleaned } = latest;
498
+ cleaned.agentPid = undefined;
499
+ cleaned.workerPid = undefined;
500
+ cleaned.launchPid = undefined;
501
+ cleaned.cancellationPids = [];
502
+ cleaned.cleanupPending = false;
503
+ cleaned.cleanupFinishedAt = new Date().toISOString();
504
+ cleaned.cleanupError = undefined;
505
+ // Persist the completion event before publishing cleanupPending=false. Readers
506
+ // that observe terminal cleanup are therefore guaranteed to observe its event.
507
+ // A crash between these writes is safely replayable without duplicating the event.
508
+ if (!hasAgentJobEvent(repoRoot, runId, "run_cleanup_completed")) {
509
+ appendAgentJobEvent(repoRoot, runId, {
510
+ type: "run_cleanup_completed",
511
+ message: "Cancelled Run process cleanup completed.",
512
+ });
513
+ }
514
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), cleaned);
515
+ try {
516
+ const issue = getIssue(repoRoot, cleaned.issueId);
517
+ const task = issue.tasks.find((entry) => entry.id === cleaned.taskId);
518
+ if (task) {
519
+ const state = resolveEffectiveTaskState({ issue, task, runs: readTaskRunEvidence(repoRoot, task) });
520
+ if (!state.terminal && !state.inactive && (task.supersededBy?.length ?? 0) === 0) {
521
+ updateTask(repoRoot, cleaned.issueId, cleaned.taskId, {
522
+ status: "blocked",
523
+ transition: "run_sync",
524
+ note: `${runId} cancelled; explicit retry is required before another Run can be created.`,
525
+ });
526
+ }
527
+ }
528
+ } catch (_error) {
529
+ /* Run cancellation remains authoritative if Task state has moved or disappeared. */
530
+ }
531
+ return cleaned;
532
+ } catch (error) {
533
+ try {
534
+ const current = getAgentJob(repoRoot, runId);
535
+ if (current.status === "cancelled") {
536
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = current;
537
+ meta.cleanupPending = true;
538
+ meta.cleanupError = error instanceof Error ? error.message : String(error);
539
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), meta);
540
+ appendAgentJobEvent(repoRoot, runId, {
541
+ type: "run_cleanup_failed",
542
+ message: meta.cleanupError,
543
+ });
544
+ return meta;
545
+ }
546
+ return current;
547
+ } catch (_nested) {
548
+ throw error;
549
+ }
550
+ } finally {
551
+ rmSync(claimPath, { force: true });
552
+ }
553
+ }
554
+
555
+ function markRunUnknown(
556
+ repoRoot: string,
557
+ path: string,
558
+ meta: AgentJobMeta,
559
+ reason: string,
560
+ ): void {
561
+ if (["succeeded", "failed", "cancelled", "unknown"].includes(meta.status)) return;
562
+ meta.status = "unknown";
563
+ meta.error = meta.error ?? reason;
564
+ meta.finishedAt = meta.finishedAt ?? new Date().toISOString();
565
+ meta.lastHeartbeatAt = meta.finishedAt;
566
+ writeAgentMeta(repoRoot, path, meta);
567
+ appendAgentJobEvent(repoRoot, meta.runId, {
568
+ type: "run_failed",
569
+ message: meta.error,
570
+ });
571
+ reconcileLatestTerminalRun(repoRoot, meta);
572
+ }
573
+
574
+ function reconcileLocalRunOwnership(
575
+ repoRoot: string,
576
+ path: string,
577
+ meta: AgentJobMeta,
578
+ ): void {
579
+ if (!["queued", "starting", "running"].includes(meta.status)) return;
580
+ const hasOwnershipMetadata = hasControllerOwnershipMetadata({
581
+ controllerPid: meta.controllerPid,
582
+ controllerEpoch: meta.controllerEpoch,
583
+ controllerEpochPath: meta.controllerEpochPath,
584
+ });
585
+ if (!hasOwnershipMetadata && meta.status !== "running") return;
586
+ if (!hasOwnershipMetadata) {
587
+ terminateRunProcesses(meta);
588
+ meta.agentPid = undefined;
589
+ meta.workerPid = undefined;
590
+ meta.launchPid = undefined;
591
+ markRunUnknown(repoRoot, path, meta, "Run ownership metadata is missing");
592
+ return;
593
+ }
594
+ const workerAlive = isAlive(meta.workerPid);
595
+ if (workerAlive && meta.workerPid) {
596
+ const identity = runProcess("ps", ["-o", "command=", "-p", String(meta.workerPid)], {
597
+ timeoutMs: 1_000,
598
+ maxOutputBytes: 32 * 1024,
599
+ });
600
+ if (identity.ok && identity.stdout.trim()) {
601
+ const command = identity.stdout.trim();
602
+ const expectedConfig = join(repoRoot, JOB_ROOT, meta.runId, "worker-config.json");
603
+ if (!matchesAgentWorkerCommand(command, expectedConfig)) {
604
+ meta.agentPid = undefined;
605
+ meta.workerPid = undefined;
606
+ meta.launchPid = undefined;
607
+ markRunUnknown(repoRoot, path, meta, `Worker PID was reused by an unrelated process: ${command.slice(0, 200)}`);
608
+ return;
609
+ }
610
+ }
611
+ }
612
+ const invalidation = invalidateAgentWorker(meta, {
613
+ controllerPid: meta.controllerPid,
614
+ controllerEpoch: meta.controllerEpoch,
615
+ controllerEpochPath: meta.controllerEpochPath
616
+ ? join(repoRoot, meta.controllerEpochPath)
617
+ : undefined,
618
+ }, {
619
+ workerPid: meta.workerPid,
620
+ });
621
+ if (!invalidation) return;
622
+ const ownedCompletionInFlight = workerAlive
623
+ && shouldTolerateOwnedFinalizationInvalidation(meta, invalidation, {
624
+ workerPid: meta.workerPid!,
625
+ childExited: true,
626
+ });
627
+ if (ownedCompletionInFlight) return;
628
+ terminateRunProcesses(meta);
629
+ meta.agentPid = undefined;
630
+ meta.workerPid = undefined;
631
+ meta.launchPid = undefined;
632
+ markRunUnknown(repoRoot, path, meta, invalidation.message);
633
+ }
634
+
635
+ function hasAgentJobEvent(repoRoot: string, runId: string, type: AgentJobEvent["type"]): boolean {
636
+ const path = eventPath(repoRoot, runId);
637
+ if (!existsSync(path)) return false;
638
+ return readFileSync(path, "utf-8")
639
+ .split(/\r?\n/)
640
+ .filter(Boolean)
641
+ .some((line) => {
642
+ try {
643
+ return readJsonLine<AgentJobEvent>(line).type === type;
644
+ } catch (_error) {
645
+ return false;
646
+ }
647
+ });
648
+ }
649
+
650
+ export function appendAgentJobEvent(
651
+ repoRoot: string,
652
+ runId: string,
653
+ event: Omit<AgentJobEvent, "at"> & { at?: string },
654
+ ): void {
655
+ const path = eventPath(repoRoot, runId);
656
+ mkdirSync(dirname(path), { recursive: true });
657
+ const at = event.at ?? new Date().toISOString();
658
+ const metaFile = metaPath(repoRoot, runId);
659
+ const meta = existsSync(metaFile) ? readJson<AgentJobMeta>(metaFile) : undefined;
660
+ const commandId = event.commandId ?? meta?.runId;
661
+ const terminal = ["run_succeeded", "run_failed", "run_cancelled"].includes(event.type);
662
+ const eventId = event.eventId ?? (commandId && (event.type === "run_started" || terminal)
663
+ ? `${commandId}:${event.type}`
664
+ : undefined);
665
+ if (eventId && !atomicCreateJson(eventReceiptPath(repoRoot, runId, eventId), { eventId, at })) return;
666
+ appendFileSync(
667
+ path,
668
+ `${JSON.stringify({ ...event, at, eventId, requestId: event.requestId ?? meta?.requestId, commandId, executionId: event.executionId ?? commandId })}\n`,
669
+ "utf-8",
670
+ );
671
+ try {
672
+ tryAppendControllerWorklogEvent(repoRoot, {
673
+ at,
674
+ category: "run",
675
+ action: event.type,
676
+ summary: event.message || event.type,
677
+ actor: meta?.agent ?? "agent-runner",
678
+ issueId: meta?.issueId,
679
+ taskId: meta?.taskId,
680
+ runId,
681
+ details: event.data,
682
+ });
683
+ } catch (_error) {
684
+ // The Run event remains authoritative even when the aggregate worklog is unavailable.
685
+ }
686
+ }
687
+
688
+
689
+ function activeRunIndexEntries(repoRoot: string): AgentRunIndexEntry[] {
690
+ let active = readRunIndex(repoRoot, 'active').runs;
691
+ if (active.length === 0 && existsSync(join(repoRoot, JOB_ROOT))) {
692
+ for (const meta of scanLegacyJobMeta(repoRoot, 5000)) updateRunIndexes(repoRoot, meta);
693
+ active = readRunIndex(repoRoot, 'active').runs;
694
+ }
695
+ return active;
696
+ }
697
+
698
+ function activeLocalRuns(repoRoot: string, options: { excludeRunId?: string } = {}): AgentJobMeta[] {
699
+ return activeRunIndexEntries(repoRoot).flatMap((entry) => {
700
+ try { return [getAgentJob(repoRoot, entry.runId)]; }
701
+ catch (_error) { return []; }
702
+ }).filter(
703
+ (entry) =>
704
+ entry.provider === "local" &&
705
+ ["queued", "starting", "running"].includes(entry.status) &&
706
+ entry.runId !== options.excludeRunId &&
707
+ (isAlive(entry.workerPid) || isAlive(entry.launchPid) || ["queued", "starting"].includes(entry.status)),
708
+ );
709
+ }
710
+
711
+ function activeRuns(repoRoot: string): AgentJobMeta[] {
712
+ return activeRunIndexEntries(repoRoot).flatMap((entry) => {
713
+ try { return [readJson<AgentJobMeta>(metaPath(repoRoot, entry.runId))]; }
714
+ catch (_error) { return []; }
715
+ }).filter((entry) => ["queued", "starting", "running"].includes(entry.status));
716
+ }
717
+
718
+ function assertNoTaskScopeConflict(
719
+ repoRoot: string,
720
+ issueId: string,
721
+ task: ControllerTask,
722
+ ): void {
723
+ const policy = taskExecutionPolicy(task);
724
+ if (policy.executionClass === "read_only" || task.allowedPaths.length === 0) return;
725
+ const candidates = new Map<string, AgentJobMeta>();
726
+ for (const run of activeRuns(repoRoot)) candidates.set(run.runId, run);
727
+ for (const run of listPendingIntegrationRuns(repoRoot, 5000)) candidates.set(run.runId, run);
728
+ for (const run of candidates.values()) {
729
+ if (run.issueId === issueId && run.taskId === task.id) continue;
730
+ let otherScope = run.executionClass && run.allowedPaths
731
+ ? { executionClass: run.executionClass, allowedPaths: run.allowedPaths }
732
+ : undefined;
733
+ if (!otherScope) {
734
+ try {
735
+ const otherTask = getIssue(repoRoot, run.issueId).tasks.find((entry) => entry.id === run.taskId);
736
+ if (otherTask) {
737
+ otherScope = {
738
+ executionClass: taskExecutionPolicy(otherTask).executionClass,
739
+ allowedPaths: otherTask.allowedPaths,
740
+ };
741
+ }
742
+ } catch (_error) {
743
+ continue;
744
+ }
745
+ }
746
+ if (!otherScope || !executionScopesConflict(
747
+ { executionClass: policy.executionClass, allowedPaths: task.allowedPaths },
748
+ otherScope,
749
+ )) continue;
750
+ const currentHeadResult = spawnSync("git", ["rev-parse", "HEAD"], { cwd: repoRoot, encoding: "utf-8" });
751
+ const currentHead = currentHeadResult.status === 0 ? currentHeadResult.stdout.trim() : "";
752
+ const baseDrift = Boolean(run.baseRevision && currentHead && currentHead !== run.baseRevision);
753
+ throw new Error(
754
+ `DRIFT_PREVENTION_BLOCKED: task-local launch overlaps unresolved Run ${run.issueId}/${run.taskId} (${run.runId})${baseDrift ? ` whose base ${run.baseRevision} differs from target ${currentHead}` : ""}`,
755
+ );
756
+ }
757
+ }
758
+
759
+ function cleanupEphemeralIssueAfterRun(repoRoot: string, issueId: string): void {
760
+ try {
761
+ const issue = getIssue(repoRoot, issueId);
762
+ if (issue.ephemeral) removeEphemeralIssue(repoRoot, issue.id);
763
+ } catch (_error) {
764
+ // Ephemeral cleanup is idempotent. Durable Run evidence is retained.
765
+ }
766
+ }
767
+
768
+ function reconcileLatestTerminalRun(repoRoot: string, meta: AgentJobMeta): void {
769
+ if (!["succeeded", "failed", "cancelled", "unknown"].includes(meta.status)) return;
770
+ try {
771
+ const issue = getIssue(repoRoot, meta.issueId);
772
+ const task = issue.tasks.find((entry) => entry.id === meta.taskId);
773
+ if (!task) return;
774
+ const state = resolveEffectiveTaskState({ issue, task, runs: readTaskRunEvidence(repoRoot, task) });
775
+ if (state.latestRunId !== meta.runId) return;
776
+ if (["done", "cancelled", "superseded"].includes(task.status)) return;
777
+
778
+ if (meta.status === "succeeded") {
779
+ if (!["review", "integrated", "verifying", "verified"].includes(task.status)) {
780
+ updateTask(repoRoot, issue.id, task.id, {
781
+ status: "review",
782
+ runId: meta.runId,
783
+ transition: "run_sync",
784
+ note: `${meta.runId} succeeded and entered task-local continuation.`,
785
+ });
786
+ }
787
+ const continuation = continueTaskAfterSuccessfulRun(repoRoot, meta);
788
+ if (continuation.status === "done") cleanupEphemeralIssueAfterRun(repoRoot, issue.id);
789
+ return;
790
+ }
791
+
792
+ updateTask(repoRoot, issue.id, task.id, {
793
+ status: "blocked",
794
+ runId: meta.runId,
795
+ transition: "run_sync",
796
+ note: `${meta.runId} ended as ${meta.status}; explicit retry is required.${meta.error ? ` ${meta.error}` : ""}`,
797
+ });
798
+ // Failed/unknown/cancelled ephemeral Runs remain retryable evidence. Keep
799
+ // their hidden Issue/Task metadata until a successful continuation reaches
800
+ // done or an explicit lifecycle cleanup removes it.
801
+ } catch (_error) {
802
+ // Run evidence remains authoritative if Issue metadata changed concurrently.
803
+ }
804
+ }
805
+
806
+ function reconcileIncompleteAutoIntegration(
807
+ repoRoot: string,
808
+ path: string,
809
+ meta: AgentJobMeta,
810
+ ): void {
811
+ const cleanupWasPending = Boolean(meta.integratedSessionId);
812
+ const message = meta.autoIntegrationError?.trim() || (cleanupWasPending
813
+ ? "automatic worktree cleanup did not finish after integration; the isolated worktree was preserved for a safe retry"
814
+ : "automatic worktree integration did not finish after the worker exited; the isolated worktree was preserved for manual integration");
815
+ const progressedAt = new Date().toISOString();
816
+ meta.status = "waiting_for_user";
817
+ meta.closureState = "preserved";
818
+ meta.closureUpdatedAt = progressedAt;
819
+ meta.preservationReason = cleanupWasPending
820
+ ? "cleanup_failed"
821
+ : meta.integrationReviewPath
822
+ ? "integration_review_required"
823
+ : "integration_failed";
824
+ meta.preservationDetails = message;
825
+ meta.autoIntegrationError = message;
826
+ meta.lastHeartbeatAt = progressedAt;
827
+ delete meta.finishedAt;
828
+ meta.progress = {
829
+ phase: "waiting",
830
+ currentActivity: `实现完成,但自动集成需要处理:${message}`,
831
+ lastActivityAt: progressedAt,
832
+ activityCount: (meta.progress?.activityCount ?? 0) + 1,
833
+ };
834
+ writeAgentMeta(repoRoot, path, meta);
835
+ appendAgentJobEvent(repoRoot, meta.runId, {
836
+ type: "run_waiting",
837
+ message: "Automatic worktree integration did not finish; the worktree was preserved for manual integration.",
838
+ data: { error: message, recovered: true },
839
+ });
840
+ try {
841
+ updateTask(repoRoot, meta.issueId, meta.taskId, {
842
+ status: "review",
843
+ runId: meta.runId,
844
+ transition: "run_sync",
845
+ note: `Automatic integration did not finish for ${meta.runId}: ${message}`,
846
+ });
847
+ } catch (_error) {
848
+ // Run evidence remains authoritative if task metadata changed concurrently.
849
+ }
850
+ }
851
+
852
+ function resolveExecutionMode(
853
+ repoRoot: string,
854
+ provider: "local" | "github",
855
+ isolate: boolean | undefined,
856
+ options: { excludeRunId?: string } = {},
857
+ ): AgentExecutionMode {
858
+ if (provider === "github") return "github";
859
+ if (isolate === true) return "worktree";
860
+ if (isolate === false) {
861
+ if (activeLocalRuns(repoRoot, options).length > 0)
862
+ throw new Error(
863
+ "cannot run directly in the current workspace while another local Task Run is active; use automatic or worktree isolation",
864
+ );
865
+ return "workspace";
866
+ }
867
+ return activeLocalRuns(repoRoot, options).length > 0 ? "worktree" : "workspace";
868
+ }
869
+
870
+ function createWorktree(
871
+ repoRoot: string,
872
+ issueId: string,
873
+ task: ControllerTask,
874
+ runId: string,
875
+ ): { path: string; branch: string | null; baseRevision: string | null } {
876
+ const gitCheck = runProcess("git", ["rev-parse", "--is-inside-work-tree"], {
877
+ cwd: repoRoot,
878
+ timeoutMs: 10_000,
879
+ maxOutputBytes: 8 * 1024,
880
+ });
881
+ if (!gitCheck.ok || gitCheck.stdout.trim() !== "true")
882
+ return { path: repoRoot, branch: null, baseRevision: null };
883
+ const revision = runProcess("git", ["rev-parse", "HEAD"], {
884
+ cwd: repoRoot,
885
+ timeoutMs: 10_000,
886
+ maxOutputBytes: 8 * 1024,
887
+ });
888
+ if (!revision.ok)
889
+ throw new Error(
890
+ `failed to resolve task base revision: ${revision.error || revision.stderr}`,
891
+ );
892
+ const baseRevision = revision.stdout.trim();
893
+ const planned = plannedWorktreeLocation(repoRoot, issueId, task.id, runId);
894
+ const absoluteWorktree = planned.path;
895
+ const branch = planned.branch;
896
+ mkdirSync(dirname(absoluteWorktree), { recursive: true });
897
+ const result = runProcess(
898
+ "git",
899
+ ["worktree", "add", "-b", branch, absoluteWorktree, "HEAD"],
900
+ {
901
+ cwd: repoRoot,
902
+ timeoutMs: 60_000,
903
+ maxOutputBytes: 64 * 1024,
904
+ },
905
+ );
906
+ if (!result.ok)
907
+ throw new Error(
908
+ `failed to create task worktree: ${result.error || result.stderr}`,
909
+ );
910
+ return { path: absoluteWorktree, branch, baseRevision };
911
+ }
912
+
913
+ function currentBaseRevision(repoRoot: string): string | null {
914
+ const revision = runProcess("git", ["rev-parse", "HEAD"], {
915
+ cwd: repoRoot,
916
+ timeoutMs: 10_000,
917
+ maxOutputBytes: 8 * 1024,
918
+ });
919
+ return revision.ok ? revision.stdout.trim() || null : null;
920
+ }
921
+
922
+ function plannedWorktreeLocation(
923
+ repoRoot: string,
924
+ issueId: string,
925
+ taskId: string,
926
+ runId: string,
927
+ ): { path: string; branch: string } {
928
+ const suffix = runId.slice(-8);
929
+ const controllerHome = resolveRepoPreferredControllerHome(repoRoot);
930
+ const repoId = tryReadRepositoryIdentity(repoRoot).repoId;
931
+ return {
932
+ path: managedWorktreePath(
933
+ controllerHome,
934
+ repoId,
935
+ `${sanitize(issueId)}-${sanitize(taskId)}-${suffix}`,
936
+ listRepositories(controllerHome, { includeRemoved: true }),
937
+ ),
938
+ branch: `controller/${sanitize(issueId)}-${sanitize(taskId)}-${suffix}`,
939
+ };
940
+ }
941
+
942
+ export function taskPrompt(
943
+ issueTitle: string,
944
+ issueSummary: string,
945
+ task: ControllerTask,
946
+ repoRoot: string,
947
+ worktree: string,
948
+ provider: "local" | "github" = "local",
949
+ executionMode: AgentExecutionMode = provider === "github"
950
+ ? "github"
951
+ : "workspace",
952
+ ): string {
953
+ const executionRules =
954
+ provider === "github"
955
+ ? [
956
+ "- Work in a dedicated branch and open or update a draft pull request.",
957
+ "- Do not merge the pull request or close the parent issue.",
958
+ "- Keep the change limited to this Task and surface any missing product decision as a blocker.",
959
+ "- Finish with a concise PR summary: changed files, checks, residual risks, and blockers.",
960
+ ]
961
+ : [
962
+ "- Keep the change limited to this task. Do not broaden the requirement.",
963
+ executionMode === "worktree"
964
+ ? "- Work only inside the isolated worktree. forge will integrate and clean it automatically after a successful Run."
965
+ : "- Work directly in the current repository workspace. Do not create another worktree, commit, merge, or push.",
966
+ "- Finish with a concise report: changed files, checks, residual risks, and any blocker.",
967
+ ];
968
+ return [
969
+ "# forge Controller Task",
970
+ "",
971
+ `Issue: ${issueTitle}`,
972
+ issueSummary ? `Issue summary: ${issueSummary}` : "",
973
+ `Task: ${task.id} — ${task.title}`,
974
+ `Objective: ${task.objective}`,
975
+ task.github ? `GitHub task: ${task.github.url}` : "",
976
+ "",
977
+ "## Scope contract",
978
+ "",
979
+ `- Repository: ${repoRoot}`,
980
+ provider === "local"
981
+ ? `- Execution mode: ${executionMode === "worktree" ? `isolated worktree (${worktree})` : `current workspace (${worktree})`}`
982
+ : "- Execution: GitHub Copilot cloud agent session",
983
+ `- Allowed paths: ${task.allowedPaths.length ? task.allowedPaths.join(", ") : "derive the smallest safe scope; do not make unrelated changes"}`,
984
+ `- Forbidden paths: ${task.forbiddenPaths.length ? task.forbiddenPaths.join(", ") : "secrets, credentials, auth state, and unrelated files"}`,
985
+ `- Risk: ${task.risk}`,
986
+ "",
987
+ "## Acceptance criteria",
988
+ "",
989
+ ...(task.acceptanceCriteria.length
990
+ ? task.acceptanceCriteria.map((item) => `- ${item}`)
991
+ : ["- Complete the stated objective without unrelated changes."]),
992
+ "",
993
+ "## Required checks",
994
+ "",
995
+ ...(task.checks.length
996
+ ? task.checks.map((item) => `- ${item}`)
997
+ : [
998
+ "- Run the smallest relevant verification available in the repository.",
999
+ ]),
1000
+ "",
1001
+ "## Execution rules",
1002
+ "",
1003
+ "- Inspect repository instructions and the affected implementation before editing.",
1004
+ ...executionRules,
1005
+ ]
1006
+ .filter(Boolean)
1007
+ .join("\n");
1008
+ }
1009
+
1010
+ export interface StartTaskJobOptions {
1011
+ repoRoot: string;
1012
+ issueId: string;
1013
+ taskId: string;
1014
+ agent?: ControllerAgent;
1015
+ timeoutMs: number;
1016
+ isolate?: boolean;
1017
+ githubRepo?: string;
1018
+ baseRef?: string;
1019
+ model?: string;
1020
+ createPullRequest?: boolean;
1021
+ retryFromRunId?: string;
1022
+ supervisorInstructions?: string;
1023
+ requestId?: string;
1024
+ approveRisk?: boolean;
1025
+ approveDestructive?: boolean;
1026
+ executorPolicy?: LocalExecutorPolicy;
1027
+ }
1028
+
1029
+ export interface DispatchTaskAcceptance {
1030
+ accepted: true;
1031
+ reused: boolean;
1032
+ runId: string;
1033
+ issueId: string;
1034
+ taskId: string;
1035
+ agent: ControllerAgent;
1036
+ provider: "local" | "github";
1037
+ executionMode: AgentExecutionMode;
1038
+ status: AgentJobStatus;
1039
+ timeoutMs?: number;
1040
+ requestId?: string;
1041
+ statusTool: "get_task_run";
1042
+ }
1043
+
1044
+ function persistRequestIndex(
1045
+ repoRoot: string,
1046
+ requestId: string,
1047
+ value: { requestId: string; runId: string; issueId: string; taskId: string; createdAt: string },
1048
+ ): void {
1049
+ const path = requestIndexPath(repoRoot, requestId);
1050
+ if (!existsSync(path)) writeJson(path, value);
1051
+ }
1052
+
1053
+ function readRequestIndex(
1054
+ repoRoot: string,
1055
+ requestId: string,
1056
+ ): { requestId?: string; runId?: string; issueId?: string; taskId?: string } | undefined {
1057
+ const path = requestIndexPath(repoRoot, requestId);
1058
+ if (!existsSync(path)) return undefined;
1059
+ try {
1060
+ return readJson(path);
1061
+ } catch (_error) {
1062
+ return undefined;
1063
+ }
1064
+ }
1065
+
1066
+ function acceptedSummary(meta: AgentJobMeta, reused = false): DispatchTaskAcceptance {
1067
+ return {
1068
+ accepted: true,
1069
+ reused,
1070
+ runId: meta.runId,
1071
+ issueId: meta.issueId,
1072
+ taskId: meta.taskId,
1073
+ agent: meta.agent,
1074
+ provider: meta.provider,
1075
+ executionMode: meta.executionMode,
1076
+ status: meta.status,
1077
+ timeoutMs: meta.timeoutMs,
1078
+ requestId: meta.requestId,
1079
+ statusTool: "get_task_run",
1080
+ };
1081
+ }
1082
+
1083
+ function executorFailureMessage(meta: Pick<AgentJobMeta, "agent" | "error">, stdoutTail?: string, stderrTail?: string): string {
1084
+ return [meta.error, stderrTail, stdoutTail].filter((value): value is string => typeof value === "string" && value.trim().length > 0).join("\n");
1085
+ }
1086
+
1087
+ function attachExecutorFailureHealth(meta: AgentJobMeta, stdoutTail?: string, stderrTail?: string): AgentJobMeta {
1088
+ if (meta.status === "succeeded") {
1089
+ // Successful Runs must not retain stale failure classification.
1090
+ delete meta.executorHealth;
1091
+ if (meta.terminationReason && meta.terminationReason !== "cancelled") {
1092
+ delete meta.terminationReason;
1093
+ }
1094
+ if (meta.error) delete meta.error;
1095
+ return meta;
1096
+ }
1097
+ if (meta.executorHealth) return meta;
1098
+ const message = executorFailureMessage(meta, stdoutTail, stderrTail);
1099
+ const health = classifyExecutorFailure(meta.agent, message, { allowedPaths: meta.allowedPaths ?? [] });
1100
+ if (health) meta.executorHealth = health;
1101
+ return meta;
1102
+ }
1103
+
1104
+ /**
1105
+ * Normalize terminal success metadata so prior executorHealth / error /
1106
+ * terminationReason do not linger after a recovered or reconciled success.
1107
+ */
1108
+ export function normalizeSuccessfulAgentRunMeta(meta: AgentJobMeta): AgentJobMeta {
1109
+ if (meta.status !== "succeeded") return meta;
1110
+ delete meta.executorHealth;
1111
+ if (meta.error) delete meta.error;
1112
+ if (meta.terminationReason && meta.terminationReason !== "cancelled") {
1113
+ delete meta.terminationReason;
1114
+ }
1115
+ return meta;
1116
+ }
1117
+
1118
+ function failAcceptedTaskJob(
1119
+ repoRoot: string,
1120
+ runId: string,
1121
+ reason: unknown,
1122
+ ): AgentJobMeta {
1123
+ const current = getAgentJob(repoRoot, runId);
1124
+ if (!["starting", "queued", "running"].includes(current.status)) return current;
1125
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = current;
1126
+ meta.status = "failed";
1127
+ meta.error = reason instanceof Error ? reason.message : String(reason);
1128
+ meta.finishedAt = new Date().toISOString();
1129
+ meta.lastHeartbeatAt = meta.finishedAt;
1130
+ meta.terminationReason = "spawn_error";
1131
+ attachExecutorFailureHealth(meta);
1132
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), meta);
1133
+ appendAgentJobEvent(repoRoot, runId, {
1134
+ type: "run_failed",
1135
+ message: meta.error,
1136
+ });
1137
+ try {
1138
+ updateTask(repoRoot, meta.issueId, meta.taskId, {
1139
+ status: "blocked",
1140
+ runId: meta.runId,
1141
+ transition: "run_sync",
1142
+ note: `${meta.runId} failed before startup completed; explicit retry is required: ${meta.error}`,
1143
+ });
1144
+ } catch (_error) {
1145
+ /* Run metadata remains authoritative when task sync also fails. */
1146
+ }
1147
+ return meta;
1148
+ }
1149
+
1150
+ function acceptancePaths(repoRoot: string, runId: string) {
1151
+ const dir = jobDir(repoRoot, runId);
1152
+ return {
1153
+ dir,
1154
+ promptPath: join(dir, "prompt.md"),
1155
+ stdoutPath: join(dir, "stdout.log"),
1156
+ stderrPath: join(dir, "stderr.log"),
1157
+ resultPath: join(dir, "result.json"),
1158
+ eventsPath: join(dir, "events.jsonl"),
1159
+ };
1160
+ }
1161
+
1162
+ function scanLegacyJobMeta(repoRoot: string, limit = 50): AgentJobMeta[] {
1163
+ const boundedLimit = Math.min(Math.max(limit, 1), 5000);
1164
+ const root = join(repoRoot, JOB_ROOT);
1165
+ if (!existsSync(root)) return [];
1166
+ const createdAtFromRunId = (runId: string): number => {
1167
+ const match = runId.match(/-(\d{13})-[a-f0-9]+$/i);
1168
+ return match ? Number(match[1]) : 0;
1169
+ };
1170
+ return readdirSync(root, { withFileTypes: true })
1171
+ .filter((entry) => entry.isDirectory() && existsSync(join(root, entry.name, "meta.json")))
1172
+ .sort((a, b) => createdAtFromRunId(b.name) - createdAtFromRunId(a.name))
1173
+ .slice(0, boundedLimit)
1174
+ .flatMap((entry) => {
1175
+ const meta = readRawAgentMeta(repoRoot, entry.name);
1176
+ return meta ? [meta] : [];
1177
+ })
1178
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt));
1179
+ }
1180
+
1181
+ function findActiveTaskRun(repoRoot: string, issueId: string, taskId: string): AgentJobMeta | undefined {
1182
+ const indexPath = taskRunIndexPath(repoRoot, issueId, taskId);
1183
+ if (existsSync(indexPath)) {
1184
+ try {
1185
+ const index = readJson<{ activeRunId?: string }>(indexPath);
1186
+ if (index.activeRunId) {
1187
+ const active = getAgentJob(repoRoot, index.activeRunId);
1188
+ if (["queued", "starting", "running"].includes(active.status)) return active;
1189
+ }
1190
+ } catch (_error) {
1191
+ // Fall through to compatibility backfill.
1192
+ }
1193
+ }
1194
+ const active = scanLegacyJobMeta(repoRoot, 5000)
1195
+ .filter((entry) => entry.issueId === issueId && entry.taskId === taskId)
1196
+ .find((entry) => ["queued", "starting", "running"].includes(entry.status));
1197
+ if (active) updateRunIndexes(repoRoot, active);
1198
+ return active;
1199
+ }
1200
+
1201
+ function baseMeta(
1202
+ opts: StartTaskJobOptions,
1203
+ task: ControllerTask,
1204
+ runId: string,
1205
+ paths: {
1206
+ promptPath: string;
1207
+ stdoutPath: string;
1208
+ stderrPath: string;
1209
+ resultPath: string;
1210
+ eventsPath: string;
1211
+ },
1212
+ isolation: {
1213
+ path: string;
1214
+ branch: string | null;
1215
+ baseRevision: string | null;
1216
+ },
1217
+ provider: "local" | "github",
1218
+ executionMode: AgentExecutionMode,
1219
+ ): AgentJobMeta {
1220
+ const identity = tryReadRepositoryIdentity(opts.repoRoot);
1221
+ const plannedWorktree = provider === "local" && executionMode === "worktree"
1222
+ ? plannedWorktreeLocation(opts.repoRoot, opts.issueId, task.id, runId)
1223
+ : undefined;
1224
+ const executionRoot = plannedWorktree?.path ?? isolation.path;
1225
+ const branch = plannedWorktree?.branch ?? isolation.branch;
1226
+ const createdAt = new Date().toISOString();
1227
+ return {
1228
+ schemaVersion: 3,
1229
+ repoId: identity.repoId,
1230
+ checkoutId: identity.checkoutId,
1231
+ requestId: opts.requestId,
1232
+ supervisorInstructions: opts.supervisorInstructions?.trim() || undefined,
1233
+ runId,
1234
+ issueId: opts.issueId,
1235
+ taskId: opts.taskId,
1236
+ agent: opts.agent!,
1237
+ provider,
1238
+ executionMode,
1239
+ executionClass: taskExecutionPolicy(task).executionClass,
1240
+ allowedPaths: [...task.allowedPaths],
1241
+ status: "starting",
1242
+ repoRoot: opts.repoRoot,
1243
+ executionRoot,
1244
+ worktree: executionRoot,
1245
+ worktreePath: executionRoot,
1246
+ branch,
1247
+ baseRevision: isolation.baseRevision,
1248
+ promptPath: relative(opts.repoRoot, paths.promptPath).replace(/\\/g, "/"),
1249
+ stdoutPath: relative(opts.repoRoot, paths.stdoutPath).replace(/\\/g, "/"),
1250
+ stderrPath: relative(opts.repoRoot, paths.stderrPath).replace(/\\/g, "/"),
1251
+ resultPath: relative(opts.repoRoot, paths.resultPath).replace(/\\/g, "/"),
1252
+ eventsPath: relative(opts.repoRoot, paths.eventsPath).replace(/\\/g, "/"),
1253
+ timeoutMs: opts.timeoutMs,
1254
+ startupDeadlineAt: new Date(Date.now() + agentStartupTimeoutMs()).toISOString(),
1255
+ autoIntegrate: executionMode === "worktree",
1256
+ progress: {
1257
+ phase: "starting",
1258
+ currentActivity: "已受理,等待异步启动",
1259
+ lastActivityAt: new Date().toISOString(),
1260
+ activityCount: 0,
1261
+ },
1262
+ createdAt,
1263
+ resources: executionMode === "worktree" && executionRoot !== opts.repoRoot
1264
+ ? [managedResource({
1265
+ resourceId: `worktree:${runId}`,
1266
+ type: "worktree",
1267
+ owner: { kind: "agent_run", id: runId },
1268
+ createdAt,
1269
+ state: "active",
1270
+ path: executionRoot,
1271
+ ...(branch ? { branch } : {}),
1272
+ })]
1273
+ : undefined,
1274
+ };
1275
+ }
1276
+
1277
+ export function acceptTaskJob(opts: StartTaskJobOptions): DispatchTaskAcceptance {
1278
+ // Historical read-only compatibility: return an existing acceptance for the same requestId
1279
+ // or an already-active Task Run. Never create, launch, or mutate Task state here.
1280
+ if (opts.requestId) {
1281
+ const indexed = readRequestIndex(opts.repoRoot, opts.requestId);
1282
+ if (indexed?.runId) return acceptedSummary(getAgentJob(opts.repoRoot, indexed.runId), true);
1283
+ }
1284
+ if (!opts.requestId) {
1285
+ const existing = findActiveTaskRun(opts.repoRoot, opts.issueId, opts.taskId);
1286
+ if (existing) return acceptedSummary(existing, true);
1287
+ }
1288
+ throw new Error(
1289
+ "AGENT_RUN_RETIRED: New Agent Runs are disabled. Claim a WorkContract and start an external SuperController through Thin Launcher.",
1290
+ );
1291
+ }
1292
+
1293
+ export function startAcceptedTaskJob(repoRoot: string, runId: string): AgentJobMeta {
1294
+ // Legacy accepted Run records are inspectable, but Kernel-owned Agent
1295
+ // spawning has moved to Thin Launcher provider adapters.
1296
+ return getAgentJob(repoRoot, runId);
1297
+ }
1298
+
1299
+ export function startTaskJob(_opts: StartTaskJobOptions): AgentJobMeta {
1300
+ throw new Error(
1301
+ "AGENT_RUN_RETIRED: New Agent Runs are disabled. Claim a WorkContract and start an external SuperController through Thin Launcher.",
1302
+ );
1303
+ }
1304
+
1305
+ function mapGitHubState(state: string): AgentJobStatus {
1306
+ switch (state) {
1307
+ case "completed":
1308
+ return "succeeded";
1309
+ case "failed":
1310
+ case "timed_out":
1311
+ return "failed";
1312
+ case "cancelled":
1313
+ return "cancelled";
1314
+ case "idle":
1315
+ case "waiting_for_user":
1316
+ return "waiting_for_user";
1317
+ case "queued":
1318
+ return "queued";
1319
+ case "in_progress":
1320
+ return "running";
1321
+ default:
1322
+ return "unknown";
1323
+ }
1324
+ }
1325
+
1326
+ export function getAgentJob(
1327
+ repoRoot: string,
1328
+ runId: string,
1329
+ ): AgentJobMeta & {
1330
+ stdoutTail: string;
1331
+ stderrTail: string;
1332
+ timing: { elapsedMs: number; remainingMs: number | null; overdue: boolean };
1333
+ } {
1334
+ const path = metaPath(repoRoot, runId);
1335
+ if (!existsSync(path)) throw new Error(`agent job not found: ${runId}`);
1336
+ const meta = normalizeAgentMeta(repoRoot, runId, readJson<AgentJobMeta>(path));
1337
+ let metaMutated = false;
1338
+
1339
+ if (
1340
+ meta.provider === "github" &&
1341
+ meta.github &&
1342
+ ["queued", "starting", "running", "waiting_for_user", "unknown"].includes(meta.status)
1343
+ ) {
1344
+ try {
1345
+ const session = getGitHubAgentSession(
1346
+ repoRoot,
1347
+ meta.github.owner,
1348
+ meta.github.repo,
1349
+ meta.github.taskId,
1350
+ );
1351
+ const previous = meta.status;
1352
+ meta.status = mapGitHubState(session.state);
1353
+ meta.github = {
1354
+ ...meta.github,
1355
+ state: session.state,
1356
+ url: session.url ?? meta.github.url,
1357
+ pullRequestUrl: session.pullRequestUrl ?? meta.github.pullRequestUrl,
1358
+ raw: session.raw,
1359
+ };
1360
+ if (
1361
+ meta.status === "succeeded" ||
1362
+ meta.status === "failed" ||
1363
+ meta.status === "cancelled"
1364
+ )
1365
+ meta.finishedAt = meta.finishedAt ?? new Date().toISOString();
1366
+ writeAgentMeta(repoRoot, path, meta);
1367
+ if (previous !== meta.status) {
1368
+ const eventType =
1369
+ meta.status === "succeeded"
1370
+ ? "run_succeeded"
1371
+ : meta.status === "failed"
1372
+ ? "run_failed"
1373
+ : meta.status === "waiting_for_user"
1374
+ ? "run_waiting"
1375
+ : meta.status === "cancelled"
1376
+ ? "run_cancelled"
1377
+ : "log_updated";
1378
+ appendAgentJobEvent(repoRoot, runId, {
1379
+ type: eventType,
1380
+ message: `GitHub session state changed to ${session.state}.`,
1381
+ data: {
1382
+ url: meta.github.url,
1383
+ pullRequestUrl: meta.github.pullRequestUrl,
1384
+ },
1385
+ });
1386
+ if (["succeeded", "failed", "cancelled", "unknown"].includes(meta.status)) {
1387
+ reconcileLatestTerminalRun(repoRoot, meta);
1388
+ } else {
1389
+ try {
1390
+ updateTask(repoRoot, meta.issueId, meta.taskId, {
1391
+ status: meta.status === "waiting_for_user" ? "blocked" : "running",
1392
+ runId: meta.runId,
1393
+ transition: "run_sync",
1394
+ note: `GitHub Run ${meta.runId} synchronized to ${meta.status}.`,
1395
+ });
1396
+ } catch (_error) {
1397
+ /* Run evidence remains authoritative if task metadata changed concurrently. */
1398
+ }
1399
+ }
1400
+ }
1401
+ } catch (error) {
1402
+ meta.error = error instanceof Error ? error.message : String(error);
1403
+ const beforeReason = meta.executorHealth?.reason;
1404
+ attachExecutorFailureHealth(meta);
1405
+ metaMutated = metaMutated || beforeReason !== meta.executorHealth?.reason;
1406
+ writeAgentMeta(repoRoot, path, meta);
1407
+ }
1408
+ } else if (meta.provider === "local") {
1409
+ const resultAbsolute = join(repoRoot, meta.resultPath);
1410
+ const autoIntegrationInFlight = meta.status === "running" &&
1411
+ meta.autoIntegrate === true &&
1412
+ meta.executionMode === "worktree" &&
1413
+ isAlive(meta.workerPid);
1414
+ if (
1415
+ ["queued", "starting", "running"].includes(meta.status) &&
1416
+ existsSync(resultAbsolute) &&
1417
+ !autoIntegrationInFlight
1418
+ ) {
1419
+ const result = readJson<{
1420
+ ok: boolean;
1421
+ exitCode: number | null;
1422
+ error?: string;
1423
+ finishedAt: string;
1424
+ }>(resultAbsolute);
1425
+ const previous = meta.status;
1426
+ meta.exitCode = result.exitCode;
1427
+ meta.error = result.error;
1428
+ if (
1429
+ result.ok &&
1430
+ meta.autoIntegrate === true &&
1431
+ meta.executionMode === "worktree" &&
1432
+ !meta.integratedSessionId
1433
+ ) {
1434
+ reconcileIncompleteAutoIntegration(repoRoot, path, meta);
1435
+ } else {
1436
+ meta.status = result.ok ? "succeeded" : "failed";
1437
+ meta.finishedAt = result.finishedAt;
1438
+ if (!result.ok) attachExecutorFailureHealth(meta);
1439
+ writeAgentMeta(repoRoot, path, meta);
1440
+ if (previous !== meta.status) {
1441
+ appendAgentJobEvent(repoRoot, runId, {
1442
+ type: result.ok ? "run_succeeded" : "run_failed",
1443
+ message: result.ok
1444
+ ? "Local worker finished."
1445
+ : (result.error ?? `exit ${result.exitCode}`),
1446
+ });
1447
+ reconcileLatestTerminalRun(repoRoot, meta);
1448
+ }
1449
+ }
1450
+ } else {
1451
+ reconcileLocalRunOwnership(repoRoot, path, meta);
1452
+ }
1453
+ if (
1454
+ ["queued", "starting"].includes(meta.status) &&
1455
+ meta.startupDeadlineAt &&
1456
+ Date.now() > Date.parse(meta.startupDeadlineAt) &&
1457
+ !isAlive(meta.workerPid) &&
1458
+ !isAlive(meta.launchPid)
1459
+ ) {
1460
+ meta.status = "unknown";
1461
+ meta.error = meta.error ?? "worker did not start before the startup deadline";
1462
+ meta.finishedAt = meta.finishedAt ?? new Date().toISOString();
1463
+ attachExecutorFailureHealth(meta);
1464
+ writeAgentMeta(repoRoot, path, meta);
1465
+ appendAgentJobEvent(repoRoot, runId, { type: "run_failed", message: meta.error });
1466
+ reconcileLatestTerminalRun(repoRoot, meta);
1467
+ } else if (meta.status === "running" && !isAlive(meta.workerPid)) {
1468
+ meta.status = "unknown";
1469
+ meta.error =
1470
+ meta.error ??
1471
+ "worker process is no longer running and no result file was produced";
1472
+ meta.finishedAt = meta.finishedAt ?? new Date().toISOString();
1473
+ attachExecutorFailureHealth(meta);
1474
+ writeAgentMeta(repoRoot, path, meta);
1475
+ appendAgentJobEvent(repoRoot, runId, { type: "run_failed", message: meta.error });
1476
+ reconcileLatestTerminalRun(repoRoot, meta);
1477
+ }
1478
+ }
1479
+ const tail = (relativePath: string): string => {
1480
+ const absolute = join(repoRoot, relativePath);
1481
+ if (!existsSync(absolute)) return "";
1482
+ const content = readFileSync(absolute, "utf-8");
1483
+ return content.slice(-32 * 1024);
1484
+ };
1485
+ const startMs = meta.startedAt
1486
+ ? Date.parse(meta.startedAt)
1487
+ : Date.parse(meta.createdAt);
1488
+ const endMs = meta.finishedAt ? Date.parse(meta.finishedAt) : Date.now();
1489
+ const deadlineMs = meta.deadlineAt
1490
+ ? Date.parse(meta.deadlineAt)
1491
+ : meta.timeoutMs
1492
+ ? startMs + meta.timeoutMs
1493
+ : NaN;
1494
+ const remainingMs =
1495
+ Number.isFinite(deadlineMs) && !meta.finishedAt
1496
+ ? Math.max(0, deadlineMs - Date.now())
1497
+ : null;
1498
+ const beforeReason = meta.executorHealth?.reason;
1499
+ if (meta.status === "succeeded") {
1500
+ const beforeHealth = meta.executorHealth;
1501
+ const beforeError = meta.error;
1502
+ const beforeTermination = meta.terminationReason;
1503
+ normalizeSuccessfulAgentRunMeta(meta);
1504
+ if (
1505
+ beforeHealth !== undefined
1506
+ || beforeError !== undefined
1507
+ || beforeTermination !== meta.terminationReason
1508
+ ) {
1509
+ metaMutated = true;
1510
+ }
1511
+ } else {
1512
+ attachExecutorFailureHealth(meta, tail(meta.stdoutPath), tail(meta.stderrPath));
1513
+ if (beforeReason !== meta.executorHealth?.reason) metaMutated = true;
1514
+ }
1515
+ if (metaMutated) writeAgentMeta(repoRoot, path, meta);
1516
+ return {
1517
+ ...meta,
1518
+ stdoutTail: tail(meta.stdoutPath),
1519
+ stderrTail: tail(meta.stderrPath),
1520
+ timing: {
1521
+ elapsedMs: Math.max(0, endMs - startMs),
1522
+ remainingMs,
1523
+ overdue:
1524
+ Number.isFinite(deadlineMs) &&
1525
+ !meta.finishedAt &&
1526
+ Date.now() > deadlineMs,
1527
+ },
1528
+ };
1529
+ }
1530
+
1531
+ export function reconcileAgentJobs(repoRoot: string): {
1532
+ scanned: number;
1533
+ terminalized: number;
1534
+ } {
1535
+ const runs = activeRunIndexEntries(repoRoot);
1536
+ let terminalized = 0;
1537
+ for (const entry of runs) {
1538
+ try {
1539
+ const path = metaPath(repoRoot, entry.runId);
1540
+ const before = readJson<AgentJobMeta>(path);
1541
+ if (before.provider !== "local") continue;
1542
+ const previous = before.status;
1543
+ getAgentJob(repoRoot, entry.runId);
1544
+ const after = readJson<AgentJobMeta>(path);
1545
+ if (
1546
+ previous !== after.status &&
1547
+ ["succeeded", "failed", "cancelled", "unknown"].includes(after.status)
1548
+ ) {
1549
+ terminalized += 1;
1550
+ }
1551
+ } catch (_error) {
1552
+ // Missing state is pruned lazily by index rebuilds.
1553
+ }
1554
+ }
1555
+ return { scanned: runs.length, terminalized };
1556
+ }
1557
+
1558
+ export function listPendingIntegrationRuns(repoRoot: string, limit = 500): AgentJobMeta[] {
1559
+ const bounded = Math.max(1, Math.min(limit, 5000));
1560
+ let pending = readRunIndex(repoRoot, 'pending-integration');
1561
+ if (!existsSync(runIndexPath(repoRoot, 'pending-integration')) && existsSync(join(repoRoot, JOB_ROOT))) {
1562
+ for (const meta of scanLegacyJobMeta(repoRoot, bounded)) updateRunIndexes(repoRoot, meta);
1563
+ pending = readRunIndex(repoRoot, 'pending-integration');
1564
+ }
1565
+ return pending.runs.slice(0, bounded).flatMap((entry) => {
1566
+ try {
1567
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = getAgentJob(repoRoot, entry.runId);
1568
+ if (meta.closureState && [
1569
+ 'ready_to_integrate', 'integration_pending', 'integrating', 'integration_blocked',
1570
+ 'integrated', 'cleanup_pending', 'cleaning', 'cleanup_blocked', 'preserved',
1571
+ ].includes(meta.closureState)) return [meta];
1572
+ return [];
1573
+ } catch (_error) { return []; }
1574
+ });
1575
+ }
1576
+
1577
+ export function recordAgentJobIntegrationEvidence(
1578
+ repoRoot: string,
1579
+ runId: string,
1580
+ evidence: NonNullable<AgentJobMeta['integrationEvidence']>,
1581
+ ): AgentJobMeta {
1582
+ const current = getAgentJob(repoRoot, runId);
1583
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = current;
1584
+ meta.integrationEvidence = evidence;
1585
+ meta.integratedAt = meta.integratedAt ?? evidence.recordedAt;
1586
+ meta.closureState = 'cleanup_pending';
1587
+ meta.closureUpdatedAt = evidence.recordedAt;
1588
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), meta);
1589
+ appendAgentJobEvent(repoRoot, runId, {
1590
+ type: 'run_activity',
1591
+ message: `Durable integration evidence recorded for ${evidence.targetRevision}; cleanup is pending.`,
1592
+ data: { integrationEvidence: evidence },
1593
+ });
1594
+ return meta;
1595
+ }
1596
+
1597
+ export function listAgentJobs(repoRoot: string, limit = 50): AgentJobMeta[] {
1598
+ const boundedLimit = Math.min(Math.max(limit, 1), 5000);
1599
+ const recentIndex = readRunIndex(repoRoot, 'recent');
1600
+ if (recentIndex.runs.length > 0) {
1601
+ return recentIndex.runs.slice(0, boundedLimit).flatMap((entry) => {
1602
+ const meta = readRawAgentMeta(repoRoot, entry.runId);
1603
+ return meta ? [meta] : [];
1604
+ });
1605
+ }
1606
+
1607
+ // Backward-compatible one-time scan for repositories created before run indexes.
1608
+ const legacy = scanLegacyJobMeta(repoRoot, boundedLimit);
1609
+ for (const meta of legacy) updateRunIndexes(repoRoot, meta);
1610
+ return legacy;
1611
+ }
1612
+
1613
+ export function listActiveAgentJobSnapshots(repoRoot: string, limit = 50): AgentJobMeta[] {
1614
+ const boundedLimit = Math.min(Math.max(limit, 1), 500);
1615
+ return readRunIndex(repoRoot, 'active').runs.slice(0, boundedLimit).flatMap((entry) => {
1616
+ const meta = readRawAgentMeta(repoRoot, entry.runId);
1617
+ return meta ? [meta] : [];
1618
+ });
1619
+ }
1620
+
1621
+ export function getAgentJobEvents(
1622
+ repoRoot: string,
1623
+ runId: string,
1624
+ limit = 200,
1625
+ options: {
1626
+ sinceIndex?: number;
1627
+ includeHeartbeats?: boolean;
1628
+ } = {},
1629
+ ): AgentJobEvent[] {
1630
+ getAgentJob(repoRoot, runId);
1631
+ const path = eventPath(repoRoot, runId);
1632
+ if (!existsSync(path)) return [];
1633
+ const events = readFileSync(path, "utf-8")
1634
+ .split(/\r?\n/)
1635
+ .filter(Boolean)
1636
+ .map((line) => readJsonLine<AgentJobEvent>(line))
1637
+ .map((event, index) => ({ ...event, data: { ...(event.data ?? {}), eventIndex: index } }))
1638
+ .filter((event) => options.sinceIndex === undefined || Number(event.data?.eventIndex) > options.sinceIndex);
1639
+
1640
+ const filtered = options.includeHeartbeats === true
1641
+ ? events
1642
+ : events.filter((event, index, list) => {
1643
+ if (!["run_heartbeat", "log_updated"].includes(event.type)) return true;
1644
+ for (let cursor = index + 1; cursor < list.length; cursor += 1) {
1645
+ if (["run_heartbeat", "log_updated"].includes(list[cursor].type)) continue;
1646
+ return false;
1647
+ }
1648
+ return true;
1649
+ });
1650
+
1651
+ return filtered.slice(-Math.min(Math.max(limit, 1), 1000));
1652
+ }
1653
+
1654
+ function readJsonLine<T>(line: string): T {
1655
+ return JSON.parse(line) as T;
1656
+ }
1657
+
1658
+ export function getAgentJobLog(
1659
+ repoRoot: string,
1660
+ runId: string,
1661
+ follow = false,
1662
+ maxBytes = 256 * 1024,
1663
+ ): {
1664
+ runId: string;
1665
+ provider: string;
1666
+ log: string;
1667
+ url?: string;
1668
+ pullRequestUrl?: string;
1669
+ } {
1670
+ const run = getAgentJob(repoRoot, runId);
1671
+ if (run.provider === "github" && run.github) {
1672
+ const log = getGitHubAgentSessionLog(
1673
+ repoRoot,
1674
+ run.github.owner,
1675
+ run.github.repo,
1676
+ run.github.taskId,
1677
+ follow,
1678
+ );
1679
+ return {
1680
+ runId,
1681
+ provider: "github",
1682
+ log,
1683
+ url: run.github.url,
1684
+ pullRequestUrl: run.github.pullRequestUrl,
1685
+ };
1686
+ }
1687
+ const readLog = (relativePath: string): string => {
1688
+ const absolute = join(repoRoot, relativePath);
1689
+ if (!existsSync(absolute)) return "";
1690
+ const bounded = Math.max(4 * 1024, Math.min(maxBytes, 1024 * 1024));
1691
+ return readFileSync(absolute, "utf-8").slice(-bounded);
1692
+ };
1693
+ return {
1694
+ runId,
1695
+ provider: "local",
1696
+ log: [readLog(run.stdoutPath), readLog(run.stderrPath)]
1697
+ .filter(Boolean)
1698
+ .join("\n"),
1699
+ };
1700
+ }
1701
+
1702
+ export function dispatchAcceptedTaskJob(
1703
+ repoRoot: string,
1704
+ runId: string,
1705
+ ): void {
1706
+ // Retained solely for callers reading legacy accepted Run records. Provider
1707
+ // process spawning belongs to Thin Launcher adapters, never the Kernel.
1708
+ void repoRoot;
1709
+ void runId;
1710
+ }
1711
+
1712
+ export function cancelAgentJob(repoRoot: string, runId: string): AgentJobMeta {
1713
+ const current = getAgentJob(repoRoot, runId);
1714
+ if (current.provider === "github")
1715
+ throw new Error(
1716
+ "GitHub cloud sessions must currently be cancelled from the GitHub Agents UI; the public agent-task API does not expose a stable cancellation contract here.",
1717
+ );
1718
+ if (current.status === "cancelled") {
1719
+ if (current.cleanupPending) requestCancellationCleanup(repoRoot, runId);
1720
+ return current;
1721
+ }
1722
+ if (!["queued", "starting", "running", "unknown", "waiting_for_user"].includes(current.status))
1723
+ return current;
1724
+
1725
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = current;
1726
+ const requestedAt = new Date().toISOString();
1727
+ meta.status = "cancelled";
1728
+ meta.terminationReason = "cancelled";
1729
+ meta.cancellationRequestedAt = requestedAt;
1730
+ meta.cancellationPids = cancellationProcessIds(current);
1731
+ meta.cleanupPending = true;
1732
+ meta.cleanupStartedAt = undefined;
1733
+ meta.cleanupFinishedAt = undefined;
1734
+ meta.cleanupError = undefined;
1735
+ meta.agentPid = undefined;
1736
+ meta.workerPid = undefined;
1737
+ meta.launchPid = undefined;
1738
+ meta.finishedAt = requestedAt;
1739
+ meta.lastHeartbeatAt = requestedAt;
1740
+ meta.progress = {
1741
+ phase: "finalizing",
1742
+ currentActivity: "Cancellation persisted; process cleanup continues independently.",
1743
+ lastActivityAt: requestedAt,
1744
+ activityCount: (meta.progress?.activityCount ?? 0) + 1,
1745
+ };
1746
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), meta);
1747
+ appendAgentJobEvent(repoRoot, runId, {
1748
+ type: "run_cancelled",
1749
+ message: "Local Run cancellation persisted; process cleanup scheduled.",
1750
+ data: { cleanupPending: true, processCount: meta.cancellationPids.length },
1751
+ });
1752
+ requestCancellationCleanup(repoRoot, runId);
1753
+ return meta;
1754
+ }
1755
+
1756
+ export function retryAgentJob(
1757
+ _repoRoot: string,
1758
+ _runId: string,
1759
+ _options: { timeoutMs?: number; isolate?: boolean; supervisorInstructions?: string; executorPolicy?: LocalExecutorPolicy } = {},
1760
+ ): AgentJobMeta {
1761
+ // Fail closed before any Task mutation, worktree, log, provider, or request-index write.
1762
+ throw new Error(
1763
+ "AGENT_RUN_RETIRED: Kernel-managed Agent Run retry is disabled. Create or claim a WorkContract and start an external SuperController through Thin Launcher.",
1764
+ );
1765
+ }
1766
+
1767
+ export function markAgentJobClosure(
1768
+ repoRoot: string,
1769
+ runId: string,
1770
+ input: {
1771
+ state: AgentJobClosureState;
1772
+ preservationReason?: AgentJobPreservationReason;
1773
+ details?: string;
1774
+ worktreeCleaned?: boolean;
1775
+ branchDeleted?: boolean;
1776
+ },
1777
+ ): AgentJobMeta {
1778
+ const current = getAgentJob(repoRoot, runId);
1779
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = current;
1780
+ const changedAt = new Date().toISOString();
1781
+ meta.closureState = input.state;
1782
+ meta.closureUpdatedAt = changedAt;
1783
+ if (input.worktreeCleaned) meta.worktreeCleanedAt = meta.worktreeCleanedAt ?? changedAt;
1784
+ if (input.branchDeleted) meta.cleanupBranchDeletedAt = meta.cleanupBranchDeletedAt ?? changedAt;
1785
+ if (meta.resources) {
1786
+ meta.resources = meta.resources.map((resource) => ({
1787
+ ...resource,
1788
+ lastSeenAt: changedAt,
1789
+ ...(input.worktreeCleaned && resource.type === "worktree"
1790
+ ? { state: "removed" as const, cleanupAfter: undefined }
1791
+ : input.state === "preserved" || input.state === "cleanup_blocked"
1792
+ ? { state: "retained" as const, retentionReason: input.details }
1793
+ : {}),
1794
+ }));
1795
+ }
1796
+ if (input.state === "preserved" || input.state === "integration_blocked" || input.state === "cleanup_blocked") {
1797
+ meta.status = "waiting_for_user";
1798
+ delete meta.finishedAt;
1799
+ meta.preservationReason = input.preservationReason ?? "unknown_worktree_state";
1800
+ meta.preservationDetails = input.details ?? (input.state === "cleanup_blocked"
1801
+ ? "Run cleanup is blocked because resource safety could not be proven."
1802
+ : input.state === "integration_blocked"
1803
+ ? "Run integration is blocked because target safety could not be proven."
1804
+ : "Run closure was preserved because cleanup safety could not be proven.");
1805
+ meta.autoIntegrationError = meta.preservationDetails;
1806
+ } else if (input.state === "completed") {
1807
+ delete meta.preservationReason;
1808
+ delete meta.preservationDetails;
1809
+ delete meta.autoIntegrationError;
1810
+ delete meta.integrationReviewPath;
1811
+ } else {
1812
+ delete meta.preservationReason;
1813
+ delete meta.preservationDetails;
1814
+ }
1815
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), meta);
1816
+ appendAgentJobEvent(repoRoot, runId, {
1817
+ type: input.state === "preserved" || input.state === "integration_blocked" || input.state === "cleanup_blocked" ? "run_waiting" : "run_activity",
1818
+ message: input.details ?? `Run closure moved to ${input.state}.`,
1819
+ data: {
1820
+ closureState: input.state,
1821
+ preservationReason: meta.preservationReason,
1822
+ worktreeCleanedAt: meta.worktreeCleanedAt,
1823
+ cleanupBranchDeletedAt: meta.cleanupBranchDeletedAt,
1824
+ },
1825
+ });
1826
+ return meta;
1827
+ }
1828
+
1829
+ export function markAgentJobReviewedCompletion(
1830
+ repoRoot: string,
1831
+ runId: string,
1832
+ options: {
1833
+ changeOutcome?: AgentJobMeta["changeOutcome"];
1834
+ changedFiles?: string[];
1835
+ worktreeCleaned?: boolean;
1836
+ branchDeleted?: boolean;
1837
+ integrationEvidence?: AgentJobMeta["integrationEvidence"];
1838
+ cleanupEvidence?: AgentJobMeta["cleanupEvidence"];
1839
+ } = {},
1840
+ ): AgentJobMeta {
1841
+ const current = getAgentJob(repoRoot, runId);
1842
+ if (current.provider !== "local") return current;
1843
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = current;
1844
+ const completedAt = new Date().toISOString();
1845
+ const integrationEvidence = options.integrationEvidence ?? meta.integrationEvidence;
1846
+ const cleanupEvidence = options.cleanupEvidence ?? meta.cleanupEvidence;
1847
+ const cleanupHasMaintenanceWarnings = Boolean(
1848
+ cleanupEvidence
1849
+ && (cleanupEvidence.maintenanceWarnings?.length ?? 0) > 0
1850
+ && (cleanupEvidence.resourceBlockers?.length ?? 0) === 0,
1851
+ );
1852
+ const integrationMissing = [
1853
+ integrationEvidence?.runId === runId ? undefined : "runId",
1854
+ integrationEvidence?.reachable ? undefined : "reachable",
1855
+ integrationEvidence?.targetRevision ? undefined : "targetRevision",
1856
+ ].filter((field): field is string => Boolean(field));
1857
+ const cleanupMissing = [
1858
+ cleanupEvidence?.runId === runId ? undefined : "runId",
1859
+ cleanupEvidence?.worktreeRemovedOrNotCreated || cleanupHasMaintenanceWarnings ? undefined : "worktreeRemovedOrNotCreated",
1860
+ cleanupEvidence?.branchDeletedOrRetained || cleanupHasMaintenanceWarnings ? undefined : "branchDeletedOrRetained",
1861
+ cleanupEvidence?.leasesReleased ? undefined : "leasesReleased",
1862
+ cleanupEvidence?.editSessionClosedOrNotCreated ? undefined : "editSessionClosedOrNotCreated",
1863
+ cleanupEvidence?.noActiveProcess ? undefined : "noActiveProcess",
1864
+ cleanupEvidence?.noDirtyDiff ? undefined : "noDirtyDiff",
1865
+ (cleanupEvidence?.resourceBlockers?.length ?? 0) === 0 ? undefined : "resourceBlockers",
1866
+ ].filter((field): field is string => Boolean(field));
1867
+ const formatMissing = (label: string, fields: string[]): string => {
1868
+ const shown = fields.slice(0, 5);
1869
+ const suffix = fields.length > shown.length ? `, and ${fields.length - shown.length} more` : "";
1870
+ return `Run ${label} evidence is incomplete: ${shown.join(", ")}${suffix}.`;
1871
+ };
1872
+ const integrationProven = integrationMissing.length === 0;
1873
+ const cleanupProven = Boolean(cleanupEvidence
1874
+ && cleanupMissing.length === 0);
1875
+ if (!integrationProven || !cleanupProven || !cleanupEvidence) {
1876
+ return markAgentJobClosure(repoRoot, runId, {
1877
+ state: !integrationProven ? "integration_blocked" : "cleanup_blocked",
1878
+ preservationReason: !integrationProven ? "unmerged_branch" : "cleanup_failed",
1879
+ details: !integrationProven
1880
+ ? formatMissing("integration", integrationMissing)
1881
+ : formatMissing("cleanup", cleanupMissing),
1882
+ worktreeCleaned: options.worktreeCleaned,
1883
+ branchDeleted: options.branchDeleted,
1884
+ });
1885
+ }
1886
+ meta.status = "succeeded";
1887
+ meta.finishedAt = meta.finishedAt ?? completedAt;
1888
+ meta.lastHeartbeatAt = completedAt;
1889
+ if (meta.resources) {
1890
+ meta.resources = meta.resources.map((resource) => ({
1891
+ ...resource,
1892
+ lastSeenAt: completedAt,
1893
+ ...(options.worktreeCleaned && resource.type === "worktree"
1894
+ ? { state: "removed" as const, cleanupAfter: undefined }
1895
+ : {}),
1896
+ }));
1897
+ }
1898
+ if (options.changeOutcome) meta.changeOutcome = options.changeOutcome;
1899
+ if (options.changedFiles) meta.changedFiles = options.changedFiles;
1900
+ if (options.worktreeCleaned) meta.worktreeCleanedAt = meta.worktreeCleanedAt ?? completedAt;
1901
+ if (options.branchDeleted) meta.cleanupBranchDeletedAt = meta.cleanupBranchDeletedAt ?? completedAt;
1902
+ meta.integrationEvidence = integrationEvidence;
1903
+ meta.cleanupEvidence = {
1904
+ ...cleanupEvidence,
1905
+ runId,
1906
+ runTerminal: true,
1907
+ };
1908
+ meta.closureState = "completed";
1909
+ meta.closureUpdatedAt = completedAt;
1910
+ delete meta.preservationReason;
1911
+ delete meta.preservationDetails;
1912
+ meta.progress = {
1913
+ phase: "completed",
1914
+ currentActivity: "Run review, integration, and completion orchestration finished",
1915
+ lastActivityAt: completedAt,
1916
+ activityCount: (meta.progress?.activityCount ?? 0) + 1,
1917
+ };
1918
+ delete meta.autoIntegrationError;
1919
+ delete meta.integrationReviewPath;
1920
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), meta);
1921
+ appendAgentJobEvent(repoRoot, runId, {
1922
+ type: "run_succeeded",
1923
+ message: "Run completion was finalized by the completion orchestrator.",
1924
+ data: { changeOutcome: meta.changeOutcome, changedFiles: meta.changedFiles, worktreeCleanedAt: meta.worktreeCleanedAt },
1925
+ });
1926
+ return meta;
1927
+ }
1928
+
1929
+ export function markAgentJobIntegrated(
1930
+ repoRoot: string,
1931
+ runId: string,
1932
+ sessionId: string,
1933
+ options: {
1934
+ changedFiles?: string[];
1935
+ changeOutcome?: AgentJobMeta["changeOutcome"];
1936
+ } = {},
1937
+ ): AgentJobMeta {
1938
+ const current = getAgentJob(repoRoot, runId);
1939
+ if (current.provider !== "local")
1940
+ throw new Error(
1941
+ "GitHub cloud session changes are reviewed and merged through their pull request, not local edit-session integration.",
1942
+ );
1943
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = current;
1944
+ meta.integratedSessionId = sessionId;
1945
+ meta.integratedAt = new Date().toISOString();
1946
+ if (options.changedFiles) meta.changedFiles = [...options.changedFiles];
1947
+ if (options.changeOutcome) meta.changeOutcome = options.changeOutcome;
1948
+ meta.closureState = "integrated";
1949
+ meta.closureUpdatedAt = meta.integratedAt;
1950
+ delete meta.preservationReason;
1951
+ delete meta.preservationDetails;
1952
+ delete meta.integrationReviewPath;
1953
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), meta);
1954
+ appendAgentJobEvent(repoRoot, runId, {
1955
+ type: "run_integrated",
1956
+ message: `Integrated through edit session ${sessionId}.`,
1957
+ });
1958
+ return markAgentJobClosure(repoRoot, runId, {
1959
+ state: "cleanup_pending",
1960
+ details: `Integration ${sessionId} completed; cleanup finalizer is pending.`,
1961
+ });
1962
+ }
1963
+
1964
+ export function markAgentJobIntegrationReview(
1965
+ repoRoot: string,
1966
+ runId: string,
1967
+ reviewPath: string,
1968
+ ): AgentJobMeta {
1969
+ const current = getAgentJob(repoRoot, runId);
1970
+ if (current.provider !== "local") return current;
1971
+ const { stdoutTail: _stdout, stderrTail: _stderr, ...meta } = current;
1972
+ const changedAt = new Date().toISOString();
1973
+ meta.status = "waiting_for_user";
1974
+ delete meta.finishedAt;
1975
+ meta.integrationReviewPath = reviewPath;
1976
+ meta.closureState = "preserved";
1977
+ meta.closureUpdatedAt = changedAt;
1978
+ meta.preservationReason = "integration_review_required";
1979
+ meta.preservationDetails = `Integration review is required: ${reviewPath}`;
1980
+ meta.autoIntegrationError = meta.preservationDetails;
1981
+ writeAgentMeta(repoRoot, metaPath(repoRoot, runId), meta);
1982
+ appendAgentJobEvent(repoRoot, runId, {
1983
+ type: "run_activity",
1984
+ message: `Integration review packet recorded at ${reviewPath}.`,
1985
+ data: { reviewPath },
1986
+ });
1987
+ return meta;
1988
+ }
1989
+
1990
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
1991
+ const command = process.argv[2];
1992
+ const repoRoot = process.argv[3];
1993
+ const runId = process.argv[4];
1994
+ if ((command === "launch" || command === "cancel-cleanup") && (!repoRoot || !runId)) process.exit(1);
1995
+
1996
+ if (command === "cancel-cleanup" && repoRoot && runId) {
1997
+ try {
1998
+ completeAgentJobCancellation(repoRoot, runId);
1999
+ process.exit(0);
2000
+ } catch (_error) {
2001
+ process.exit(1);
2002
+ }
2003
+ }
2004
+
2005
+ if (command === "launch" && repoRoot && runId) {
2006
+ void (async () => {
2007
+ try {
2008
+ startAcceptedTaskJob(repoRoot, runId);
2009
+ while (true) {
2010
+ const current = getAgentJob(repoRoot, runId);
2011
+ if (
2012
+ !["queued", "starting", "running"].includes(current.status) ||
2013
+ !isAlive(current.workerPid)
2014
+ ) {
2015
+ break;
2016
+ }
2017
+ await new Promise((resolve) => setTimeout(resolve, 1_000));
2018
+ }
2019
+ process.exit(0);
2020
+ } catch (error) {
2021
+ try {
2022
+ failAcceptedTaskJob(repoRoot, runId, error);
2023
+ } catch (_nested) {
2024
+ /* Best effort launcher failure persistence. */
2025
+ }
2026
+ process.exit(1);
2027
+ }
2028
+ })();
2029
+ }
2030
+ }