@fitlab-ai/agent-infra 0.9.2 → 0.9.4

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 (428) hide show
  1. package/README.md +12 -9
  2. package/README.zh-CN.md +12 -9
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +134 -108
  6. package/dist/lib/agent-clients/adapter.js +137 -4
  7. package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
  8. package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
  9. package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
  10. package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
  11. package/dist/lib/agent-clients/adapters/claude-code.js +27 -4
  12. package/dist/lib/agent-clients/adapters/codex.js +9 -0
  13. package/dist/lib/agent-clients/adapters/index.js +2 -2
  14. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
  15. package/dist/lib/agent-clients/adapters/opencode.js +24 -3
  16. package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  17. package/dist/lib/agent-clients/config.js +42 -4
  18. package/dist/lib/agent-clients/next-steps.js +11 -2
  19. package/dist/lib/agent-clients/reconcile.js +2 -1
  20. package/dist/lib/agent-clients/registry.js +16 -2
  21. package/dist/lib/agent-clients/schema.js +18 -0
  22. package/dist/lib/agent-clients/tokens.js +49 -0
  23. package/dist/lib/agent-clients/types.js +1 -1
  24. package/dist/lib/defaults.json +1 -1
  25. package/dist/lib/git/workflow.js +54 -1
  26. package/dist/lib/internal/agent-client.js +61 -4
  27. package/dist/lib/internal/git-workflow.js +6 -1
  28. package/dist/lib/internal/platform-checks.js +3 -3
  29. package/dist/lib/internal/platform-comment.js +29 -2
  30. package/dist/lib/internal/platform-issue.js +7 -0
  31. package/dist/lib/internal/platform-pr-review.js +132 -0
  32. package/dist/lib/internal/platform-pr.js +20 -2
  33. package/dist/lib/internal/platform-release-notes.js +52 -16
  34. package/dist/lib/internal/pr-review-grade.js +140 -0
  35. package/dist/lib/internal/release-workflow.js +13 -6
  36. package/dist/lib/internal/sandbox-control.js +24 -0
  37. package/dist/lib/internal/task-activity.js +105 -0
  38. package/dist/lib/internal/task-event.js +17 -1
  39. package/dist/lib/internal/task-ledger.js +12 -3
  40. package/dist/lib/internal/task-lifecycle.js +26 -1
  41. package/dist/lib/internal/task-orchestration.js +167 -18
  42. package/dist/lib/internal/task-review.js +8 -3
  43. package/dist/lib/platform/adapters.js +8 -1
  44. package/dist/lib/platform/completion-backfill.js +122 -0
  45. package/dist/lib/platform/github-client.js +19 -2
  46. package/dist/lib/platform/issue-comments.js +3 -2
  47. package/dist/lib/platform/merged-pr-equivalence.js +115 -5
  48. package/dist/lib/platform/pr-checks.js +65 -42
  49. package/dist/lib/platform/pr-review.js +166 -0
  50. package/dist/lib/platform/pull-requests.js +371 -4
  51. package/dist/lib/platform/release-notes.js +85 -3
  52. package/dist/lib/platform/releases.js +13 -5
  53. package/dist/lib/pr-review/evidence-grading.js +229 -0
  54. package/dist/lib/process-data/index.js +239 -0
  55. package/dist/lib/process-data/normalize.js +198 -0
  56. package/dist/lib/process-data/privacy.js +33 -0
  57. package/dist/lib/process-data/reconcile.js +95 -0
  58. package/dist/lib/process-data/repair.js +58 -0
  59. package/dist/lib/process-data/sources.js +345 -0
  60. package/dist/lib/process-data/store.js +282 -0
  61. package/dist/lib/process-data/types.js +2 -0
  62. package/dist/lib/run/index.js +4 -3
  63. package/dist/lib/run/tui.js +10 -13
  64. package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
  65. package/dist/lib/sandbox/capture.js +7 -2
  66. package/dist/lib/sandbox/commands/create.js +109 -248
  67. package/dist/lib/sandbox/commands/enter.js +15 -47
  68. package/dist/lib/sandbox/commands/list-running.js +16 -6
  69. package/dist/lib/sandbox/commands/ls.js +10 -6
  70. package/dist/lib/sandbox/commands/rebuild.js +4 -3
  71. package/dist/lib/sandbox/commands/refresh.js +4 -84
  72. package/dist/lib/sandbox/commands/rm.js +24 -3
  73. package/dist/lib/sandbox/commands/show.js +13 -4
  74. package/dist/lib/sandbox/commands/start.js +7 -4
  75. package/dist/lib/sandbox/config.js +2 -0
  76. package/dist/lib/sandbox/constants.js +6 -0
  77. package/dist/lib/sandbox/control/client.js +44 -0
  78. package/dist/lib/sandbox/control/protocol.js +35 -0
  79. package/dist/lib/sandbox/control/server.js +109 -0
  80. package/dist/lib/sandbox/dockerfile.js +36 -13
  81. package/dist/lib/sandbox/mounts.js +17 -4
  82. package/dist/lib/sandbox/recovery.js +128 -5
  83. package/dist/lib/sandbox/redaction.js +12 -0
  84. package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
  85. package/dist/lib/sandbox/workspace-identity.js +95 -0
  86. package/dist/lib/sandbox/workspace-view.js +103 -0
  87. package/dist/lib/task/activity-intent.js +307 -0
  88. package/dist/lib/task/artifact-lifecycle.js +2 -1
  89. package/dist/lib/task/commands/log.js +15 -14
  90. package/dist/lib/task/commit-finalization.js +267 -0
  91. package/dist/lib/task/commit-intent.js +165 -0
  92. package/dist/lib/task/decision-intents.js +31 -17
  93. package/dist/lib/task/delegation-receipts.js +25 -4
  94. package/dist/lib/task/events.js +67 -28
  95. package/dist/lib/task/finalization-artifacts.js +35 -0
  96. package/dist/lib/task/implementation-inputs.js +54 -7
  97. package/dist/lib/task/ledger-intents.js +35 -1
  98. package/dist/lib/task/lifecycle-execution.js +46 -0
  99. package/dist/lib/task/orchestration.js +753 -54
  100. package/dist/lib/task/resolve-ref.js +41 -1
  101. package/dist/lib/task/review-finalization.js +29 -9
  102. package/dist/lib/task/task-execution-lock.js +231 -0
  103. package/dist/lib/task/verification-checks.js +1 -1
  104. package/dist/lib/task/verification-engine.js +218 -2
  105. package/dist/lib/task/verification.js +4 -3
  106. package/dist/lib/task/workspace-snapshot.js +17 -5
  107. package/lib/agent-clients/adapter.ts +201 -4
  108. package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
  109. package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
  110. package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
  111. package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
  112. package/lib/agent-clients/adapters/claude-code.ts +31 -4
  113. package/lib/agent-clients/adapters/codex.ts +9 -0
  114. package/lib/agent-clients/adapters/index.ts +2 -2
  115. package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
  116. package/lib/agent-clients/adapters/opencode.ts +27 -3
  117. package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  118. package/lib/agent-clients/config.ts +49 -4
  119. package/lib/agent-clients/next-steps.ts +15 -2
  120. package/lib/agent-clients/reconcile.ts +2 -1
  121. package/lib/agent-clients/registry.ts +19 -1
  122. package/lib/agent-clients/schema.ts +18 -0
  123. package/lib/agent-clients/tokens.ts +53 -0
  124. package/lib/agent-clients/types.ts +15 -1
  125. package/lib/defaults.json +1 -1
  126. package/lib/git/workflow.ts +66 -2
  127. package/lib/internal/agent-client.ts +59 -4
  128. package/lib/internal/git-workflow.ts +5 -1
  129. package/lib/internal/platform-checks.ts +4 -4
  130. package/lib/internal/platform-comment.ts +17 -2
  131. package/lib/internal/platform-issue.ts +4 -0
  132. package/lib/internal/platform-pr-review.ts +104 -0
  133. package/lib/internal/platform-pr.ts +14 -1
  134. package/lib/internal/platform-release-notes.ts +59 -16
  135. package/lib/internal/pr-review-grade.ts +130 -0
  136. package/lib/internal/release-workflow.ts +13 -6
  137. package/lib/internal/sandbox-control.ts +24 -0
  138. package/lib/internal/task-activity.ts +91 -0
  139. package/lib/internal/task-event.ts +9 -1
  140. package/lib/internal/task-ledger.ts +12 -3
  141. package/lib/internal/task-lifecycle.ts +26 -1
  142. package/lib/internal/task-orchestration.ts +123 -14
  143. package/lib/internal/task-review.ts +8 -3
  144. package/lib/platform/adapters.ts +29 -2
  145. package/lib/platform/completion-backfill.ts +134 -0
  146. package/lib/platform/github-client.ts +20 -2
  147. package/lib/platform/issue-comments.ts +3 -2
  148. package/lib/platform/merged-pr-equivalence.ts +127 -5
  149. package/lib/platform/pr-checks.ts +73 -46
  150. package/lib/platform/pr-review.ts +201 -0
  151. package/lib/platform/pull-requests.ts +444 -3
  152. package/lib/platform/release-notes.ts +100 -4
  153. package/lib/platform/releases.ts +16 -4
  154. package/lib/pr-review/evidence-grading.ts +318 -0
  155. package/lib/process-data/index.ts +224 -0
  156. package/lib/process-data/normalize.ts +202 -0
  157. package/lib/process-data/privacy.ts +41 -0
  158. package/lib/process-data/reconcile.ts +110 -0
  159. package/lib/process-data/repair.ts +64 -0
  160. package/lib/process-data/sources.ts +354 -0
  161. package/lib/process-data/store.ts +315 -0
  162. package/lib/process-data/types.ts +135 -0
  163. package/lib/run/index.ts +5 -3
  164. package/lib/run/tui.ts +14 -12
  165. package/lib/sandbox/agent-client-reconciler.ts +44 -2
  166. package/lib/sandbox/capture.ts +9 -2
  167. package/lib/sandbox/commands/create.ts +126 -280
  168. package/lib/sandbox/commands/enter.ts +13 -59
  169. package/lib/sandbox/commands/list-running.ts +23 -6
  170. package/lib/sandbox/commands/ls.ts +22 -6
  171. package/lib/sandbox/commands/rebuild.ts +4 -3
  172. package/lib/sandbox/commands/refresh.ts +4 -126
  173. package/lib/sandbox/commands/rm.ts +24 -3
  174. package/lib/sandbox/commands/show.ts +30 -4
  175. package/lib/sandbox/commands/start.ts +6 -4
  176. package/lib/sandbox/config.ts +4 -0
  177. package/lib/sandbox/constants.ts +8 -0
  178. package/lib/sandbox/control/client.ts +55 -0
  179. package/lib/sandbox/control/protocol.ts +75 -0
  180. package/lib/sandbox/control/server.ts +117 -0
  181. package/lib/sandbox/dockerfile.ts +66 -13
  182. package/lib/sandbox/mounts.ts +24 -4
  183. package/lib/sandbox/recovery.ts +163 -6
  184. package/lib/sandbox/redaction.ts +14 -0
  185. package/lib/sandbox/runtimes/base.dockerfile +24 -74
  186. package/lib/sandbox/tool-types.ts +16 -0
  187. package/lib/sandbox/workspace-identity.ts +123 -0
  188. package/lib/sandbox/workspace-view.ts +143 -0
  189. package/lib/server/server.schema.json +2 -2
  190. package/lib/task/activity-intent.ts +417 -0
  191. package/lib/task/artifact-lifecycle.ts +2 -1
  192. package/lib/task/commands/log.ts +15 -14
  193. package/lib/task/commit-finalization.ts +356 -0
  194. package/lib/task/commit-intent.ts +230 -0
  195. package/lib/task/decision-intents.ts +32 -12
  196. package/lib/task/delegation-receipts.ts +34 -5
  197. package/lib/task/events.ts +75 -30
  198. package/lib/task/finalization-artifacts.ts +58 -0
  199. package/lib/task/implementation-inputs.ts +62 -8
  200. package/lib/task/ledger-intents.ts +43 -3
  201. package/lib/task/lifecycle-execution.ts +106 -0
  202. package/lib/task/orchestration.ts +1019 -72
  203. package/lib/task/resolve-ref.ts +46 -1
  204. package/lib/task/review-finalization.ts +40 -9
  205. package/lib/task/task-execution-lock.ts +296 -0
  206. package/lib/task/verification-checks.ts +1 -1
  207. package/lib/task/verification-engine.ts +249 -2
  208. package/lib/task/verification.ts +5 -3
  209. package/lib/task/workspace-snapshot.ts +26 -5
  210. package/package.json +3 -3
  211. package/templates/.agents/QUICKSTART.en.md +25 -8
  212. package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
  213. package/templates/.agents/README.en.md +10 -7
  214. package/templates/.agents/README.zh-CN.md +10 -7
  215. package/templates/.agents/hooks/lifecycle-delegation.js +15 -4
  216. package/templates/.agents/rules/README.en.md +1 -1
  217. package/templates/.agents/rules/README.zh-CN.md +1 -1
  218. package/templates/.agents/rules/create-issue.github.en.md +4 -2
  219. package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
  220. package/templates/.agents/rules/human-decision-context.en.md +11 -2
  221. package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
  222. package/templates/.agents/rules/issue-fields.github.en.md +3 -1
  223. package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
  224. package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
  225. package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
  226. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
  227. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
  228. package/templates/.agents/rules/issue-sync.github.en.md +7 -3
  229. package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
  230. package/templates/.agents/rules/lifecycle-orchestration.en.md +5 -2
  231. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +5 -2
  232. package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
  233. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
  234. package/templates/.agents/rules/next-step-output.en.md +3 -1
  235. package/templates/.agents/rules/next-step-output.zh-CN.md +3 -1
  236. package/templates/.agents/rules/no-mid-flow-questions.en.md +17 -0
  237. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +17 -0
  238. package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
  239. package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
  240. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +3 -3
  241. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
  242. package/templates/.agents/rules/pr-sync.en.md +3 -1
  243. package/templates/.agents/rules/pr-sync.github.en.md +11 -1
  244. package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
  245. package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
  246. package/templates/.agents/rules/release-commands.en.md +1 -1
  247. package/templates/.agents/rules/release-commands.github.en.md +12 -2
  248. package/templates/.agents/rules/release-commands.github.zh-CN.md +12 -2
  249. package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
  250. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  251. package/templates/.agents/rules/task-management.en.md +13 -0
  252. package/templates/.agents/rules/task-management.zh-CN.md +13 -0
  253. package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -7
  254. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -7
  255. package/templates/.agents/skills/block-task/SKILL.en.md +7 -5
  256. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +7 -5
  257. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -4
  258. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -4
  259. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
  260. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
  261. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
  262. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
  263. package/templates/.agents/skills/code-task/SKILL.en.md +10 -7
  264. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +12 -9
  265. package/templates/.agents/skills/commit/SKILL.en.md +23 -11
  266. package/templates/.agents/skills/commit/SKILL.zh-CN.md +29 -11
  267. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  268. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  269. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
  270. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -0
  271. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  272. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  273. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  274. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  275. package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
  276. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
  277. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +6 -4
  278. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
  279. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  280. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  281. package/templates/.agents/skills/complete-task/SKILL.en.md +22 -7
  282. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +22 -7
  283. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  284. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  285. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
  286. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
  287. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -5
  288. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -5
  289. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  290. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  291. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
  292. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
  293. package/templates/.agents/skills/create-task/SKILL.en.md +5 -3
  294. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -3
  295. package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
  296. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
  297. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -3
  298. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -3
  299. package/templates/.agents/skills/plan-task/SKILL.en.md +8 -5
  300. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
  301. package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
  302. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
  303. package/templates/.agents/skills/release/SKILL.en.md +16 -10
  304. package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
  305. package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
  306. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
  307. package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -4
  308. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -4
  309. package/templates/.agents/skills/review-code/SKILL.en.md +11 -8
  310. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -8
  311. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  312. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  313. package/templates/.agents/skills/review-plan/SKILL.en.md +7 -4
  314. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -4
  315. package/templates/.agents/skills/review-pr/SKILL.en.md +186 -0
  316. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +186 -0
  317. package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
  318. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
  319. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
  320. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
  321. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
  322. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
  323. package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
  324. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
  325. package/templates/.agents/skills/run-task/SKILL.en.md +8 -8
  326. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +8 -8
  327. package/templates/.agents/skills/run-task/config/verify.en.json +2 -1
  328. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +2 -1
  329. package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
  330. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
  331. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +98 -107
  332. package/templates/.agents/skills/watch-pr/SKILL.en.md +13 -13
  333. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +13 -13
  334. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
  335. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
  336. package/templates/.agents/templates/task.en.md +2 -2
  337. package/templates/.agents/templates/task.zh-CN.md +2 -2
  338. package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
  339. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
  340. package/templates/.agents/workflows/code-review.en.yaml +1 -1
  341. package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
  342. package/templates/.agents/workflows/feature-development.en.yaml +4 -4
  343. package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
  344. package/templates/.agents/workflows/refactoring.en.yaml +3 -3
  345. package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
  346. package/templates/.claude/commands/post-release.en.md +1 -0
  347. package/templates/.claude/commands/post-release.zh-CN.md +1 -0
  348. package/templates/.claude/commands/review-pr.en.md +8 -0
  349. package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
  350. package/templates/.claude/commands/watch-pr.en.md +1 -1
  351. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  352. package/templates/.claude/rules/tool-preferences.en.md +20 -0
  353. package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
  354. package/templates/.opencode/commands/post-release.en.md +2 -0
  355. package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
  356. package/templates/.opencode/commands/review-pr.en.md +11 -0
  357. package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
  358. package/templates/.opencode/commands/watch-pr.en.md +1 -1
  359. package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
  360. package/dist/lib/agent-clients/adapters/gemini-cli.js +0 -64
  361. package/lib/agent-clients/adapters/gemini-cli.ts +0 -65
  362. package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
  363. package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
  364. package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
  365. package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
  366. package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
  367. package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
  368. package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
  369. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
  370. package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
  371. package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
  372. package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
  373. package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
  374. package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
  375. package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
  376. package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
  377. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
  378. package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
  379. package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
  380. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
  381. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
  382. package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
  383. package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
  384. package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
  385. package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
  386. package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
  387. package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
  388. package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
  389. package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
  390. package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
  391. package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
  392. package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
  393. package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
  394. package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
  395. package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
  396. package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
  397. package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
  398. package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
  399. package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
  400. package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
  401. package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
  402. package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
  403. package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
  404. package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
  405. package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
  406. package/templates/.gemini/commands/_project_/release.en.toml +0 -8
  407. package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
  408. package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
  409. package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
  410. package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
  411. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
  412. package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
  413. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
  414. package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
  415. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
  416. package/templates/.gemini/commands/_project_/run-task.en.toml +0 -8
  417. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +0 -8
  418. package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
  419. package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
  420. package/templates/.gemini/commands/_project_/test.en.toml +0 -6
  421. package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
  422. package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
  423. package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
  424. package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
  425. package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
  426. package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
  427. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
  428. package/templates/.gemini/settings.json +0 -3
@@ -1,14 +1,28 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
+ import { execFileSync, spawnSync } from 'node:child_process';
4
5
  import { parseTypedTaskFrontmatter } from './frontmatter.js';
5
6
  import { parseReviewSummary } from './review-artifacts.js';
6
7
  import { inspectArtifactDirectory } from './artifact-lifecycle.js';
7
8
  import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
8
9
  import { resolveTaskRef } from './resolve-ref.js';
9
10
  import { activateDelegation, completeDelegationStage, consumeDelegation, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
10
- import { getAgentClientCapability } from '../agent-clients/registry.js';
11
- import { captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
11
+ import { normalizeAgentClients } from '../agent-clients/config.js';
12
+ import { inspectPlatformPullRequest } from '../platform/pull-requests.js';
13
+ import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
14
+ import { isAgentClientId } from '../agent-clients/types.js';
15
+ import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
16
+ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
17
+ import { CommitIntentError, commitIntentPath, createCommitIntent, digest, readCommitIntent, removeCommitIntent, removeCommitIntentByDigest, serialize, updateCommitIntent } from './commit-intent.js';
18
+ import { inspectCommitFinalization, planCommitTaskFinalization } from './commit-finalization.js';
19
+ import { captureTaskWriteMetadata, writeTask } from './write.js';
20
+ function supportsLifecycleDelegation(client) {
21
+ return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
22
+ && getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
23
+ && getAgentClientDelegationEvidence(client).actualModel !== 'unavailable'
24
+ && getAgentClientDelegationEvidence(client).actualReasoningEffort !== 'unavailable';
25
+ }
12
26
  function orchestrationPath(taskDir) {
13
27
  return path.join(taskDir, 'orchestration.json');
14
28
  }
@@ -16,7 +30,22 @@ function readRun(taskDir) {
16
30
  const file = orchestrationPath(taskDir);
17
31
  if (!fs.existsSync(file))
18
32
  return null;
19
- return JSON.parse(fs.readFileSync(file, 'utf8'));
33
+ const run = JSON.parse(fs.readFileSync(file, 'utf8'));
34
+ if (run.schemaVersion !== 2 || !isV2Policy(run.modelPolicy))
35
+ return run;
36
+ return {
37
+ ...run,
38
+ modelPolicy: {
39
+ executor: {
40
+ model: run.modelPolicy.executor.model,
41
+ reasoningEffort: run.modelPolicy.executor.reasoningEffort
42
+ },
43
+ reviewer: {
44
+ model: run.modelPolicy.reviewer.model,
45
+ reasoningEffort: run.modelPolicy.reviewer.reasoningEffort
46
+ }
47
+ }
48
+ };
20
49
  }
21
50
  function atomicWrite(file, value) {
22
51
  const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
@@ -26,39 +55,270 @@ function atomicWrite(file, value) {
26
55
  function saveRun(taskDir, run) {
27
56
  atomicWrite(orchestrationPath(taskDir), run);
28
57
  }
29
- function withUpdatedRun(run, updates) {
30
- return Object.freeze({ ...run, ...updates, updatedAt: new Date().toISOString() });
58
+ function withUpdatedRun(run, updates, now) {
59
+ return Object.freeze({ ...run, ...updates, updatedAt: (now ?? (() => new Date().toISOString()))() });
60
+ }
61
+ function failed(code, message, taskId = null, details = {}) {
62
+ return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message, ...details } };
63
+ }
64
+ function commitIntentFailed(code, message, taskId = null) {
65
+ return { status: 'failed', changed: false, taskId, intent: null, error: { code, message } };
66
+ }
67
+ function repositoryHead(repoRoot) {
68
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
69
+ cwd: repoRoot,
70
+ encoding: 'utf8',
71
+ stdio: ['ignore', 'pipe', 'pipe']
72
+ }).trim();
73
+ }
74
+ function isAncestor(repoRoot, ancestor, descendant) {
75
+ return spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], {
76
+ cwd: repoRoot,
77
+ stdio: 'ignore'
78
+ }).status === 0;
79
+ }
80
+ function commitIntentView(intent, currentHead) {
81
+ return {
82
+ mode: intent.mode,
83
+ phase: intent.phase,
84
+ baselineHead: intent.baselineHead,
85
+ currentHead,
86
+ committedHead: intent.committedHead,
87
+ pushEvidence: intent.pushEvidence,
88
+ runId: intent.orchestration?.runId ?? null,
89
+ receiptId: intent.orchestration?.receiptId ?? null
90
+ };
91
+ }
92
+ function commitFinalizationView(inspection) {
93
+ return {
94
+ disposition: inspection.disposition,
95
+ code: inspection.code,
96
+ message: inspection.message,
97
+ currentHead: inspection.currentHead,
98
+ committedHead: inspection.committedHead,
99
+ needsAnchor: inspection.needsAnchor,
100
+ needsLog: inspection.needsLog
101
+ };
102
+ }
103
+ function validCommitRun(run, taskId) {
104
+ return run !== null
105
+ && [1, 2].includes(run.schemaVersion)
106
+ && run.taskId === taskId
107
+ && ['running', 'paused', 'completed'].includes(run.status)
108
+ && Array.isArray(run.receipts)
109
+ && Object.prototype.hasOwnProperty.call(run, 'pendingDelegation')
110
+ && typeof run.commitAuthorization === 'object'
111
+ && run.commitAuthorization !== null
112
+ && Object.prototype.hasOwnProperty.call(run.commitAuthorization, 'issuedAt')
113
+ && Object.prototype.hasOwnProperty.call(run.commitAuthorization, 'consumedAt');
114
+ }
115
+ function mapCommitIntentError(error, taskId) {
116
+ if (error instanceof CommitIntentError)
117
+ return commitIntentFailed(error.code, error.message, taskId);
118
+ if (error instanceof TaskExecutionLockError)
119
+ return commitIntentFailed(error.code, error.message, taskId);
120
+ return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_INVALID', error instanceof Error ? error.message : String(error), taskId);
121
+ }
122
+ function validModel(value) {
123
+ return typeof value === 'string' && value.length > 0 && value.trim() === value;
124
+ }
125
+ function validateModelPolicy(policy) {
126
+ if (!policy
127
+ || !validModel(policy.executor?.model)
128
+ || !validModel(policy.executor?.reasoningEffort)
129
+ || !validModel(policy.reviewer?.model)
130
+ || !validModel(policy.reviewer?.reasoningEffort)) {
131
+ return { code: 'ORCHESTRATION_MODEL_POLICY_REQUIRED', message: 'executor and reviewer model and reasoning effort are required' };
132
+ }
133
+ return null;
134
+ }
135
+ function sameModelPolicy(left, right) {
136
+ return left.executor.model === right.executor.model
137
+ && left.executor.reasoningEffort === right.executor.reasoningEffort
138
+ && left.reviewer.model === right.reviewer.model
139
+ && left.reviewer.reasoningEffort === right.reviewer.reasoningEffort;
140
+ }
141
+ function isV2Policy(policy) {
142
+ return typeof policy?.executor === 'object' && typeof policy?.reviewer === 'object';
31
143
  }
32
- function failed(code, message, taskId = null) {
33
- return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message } };
144
+ function rolePolicy(run, role) {
145
+ return isV2Policy(run.modelPolicy) ? run.modelPolicy[role] : null;
146
+ }
147
+ function resolveProjectPolicy(repoRoot, client) {
148
+ const configPath = path.join(repoRoot, '.agents', '.airc.json');
149
+ if (!fs.existsSync(configPath))
150
+ return undefined;
151
+ const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
152
+ return normalizeAgentClients(raw).state[client].orchestration;
34
153
  }
35
154
  function beginOrResumeOrchestration(taskRef, options = {}) {
36
155
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
37
156
  if (!resolved.ok)
38
157
  return failed(resolved.code, resolved.message, resolved.taskId);
39
- const existing = readRun(resolved.taskDir);
158
+ if (!isAgentClientId(options.client)) {
159
+ return failed('ORCHESTRATION_PAYLOAD_INVALID', 'begin-or-resume requires a known client', resolved.taskId);
160
+ }
161
+ let existing;
162
+ try {
163
+ existing = readRun(resolved.taskDir);
164
+ }
165
+ catch (error) {
166
+ return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
167
+ }
40
168
  if (existing) {
41
- if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
42
- const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
43
- saveRun(resolved.taskDir, resumed);
44
- return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
169
+ if (![1, 2].includes(existing.schemaVersion)
170
+ || !Array.isArray(existing.receipts)
171
+ || !Object.prototype.hasOwnProperty.call(existing, 'pendingDelegation')) {
172
+ return failed('ORCHESTRATION_STATE_INVALID', 'orchestration state has an invalid schema', resolved.taskId);
173
+ }
174
+ if (existing.status === 'completed' && existing.schemaVersion === 1) {
175
+ return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
176
+ }
177
+ if (existing.schemaVersion === 2) {
178
+ const policyError = validateModelPolicy(isV2Policy(existing.modelPolicy) ? existing.modelPolicy : undefined);
179
+ if (policyError
180
+ || !existing.modelPolicySource
181
+ || !isAgentClientId(existing.modelPolicySource.client)
182
+ || !Array.isArray(existing.recoveryHistory)) {
183
+ return failed('ORCHESTRATION_STATE_INVALID', 'schemaVersion 2 orchestration state is incomplete', resolved.taskId);
184
+ }
185
+ if (existing.status === 'completed') {
186
+ return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
187
+ }
188
+ if (existing.modelPolicySource.client !== options.client) {
189
+ return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
190
+ }
191
+ if (options.modelPolicy) {
192
+ const suppliedError = validateModelPolicy(options.modelPolicy);
193
+ if (suppliedError)
194
+ return failed(suppliedError.code, suppliedError.message, resolved.taskId);
195
+ if (!sameModelPolicy(existing.modelPolicy, options.modelPolicy)) {
196
+ return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
197
+ }
198
+ }
199
+ if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
200
+ const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
201
+ saveRun(resolved.taskDir, resumed);
202
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
203
+ }
204
+ return { status: existing.status, changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
205
+ }
206
+ let policy = options.modelPolicy;
207
+ let sourceKind = 'explicit';
208
+ if (!policy) {
209
+ try {
210
+ policy = resolveProjectPolicy(resolved.repoRoot, options.client);
211
+ sourceKind = 'project-config';
212
+ }
213
+ catch (error) {
214
+ return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
215
+ }
216
+ }
217
+ const policyError = validateModelPolicy(policy);
218
+ if (policyError) {
219
+ return failed(policyError.code, policyError.message, resolved.taskId, {
220
+ client: options.client,
221
+ missingFields: ['executor.model', 'executor.reasoningEffort', 'reviewer.model', 'reviewer.reasoningEffort'],
222
+ modelSelectionContext: getAgentClientModelSelection(options.client)
223
+ });
224
+ }
225
+ if (existing.pendingDelegation !== null) {
226
+ const alreadyPaused = existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_DELEGATION_BUSY';
227
+ if (alreadyPaused)
228
+ return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
229
+ const paused = withUpdatedRun(existing, {
230
+ status: 'paused',
231
+ pause: { code: 'ORCHESTRATION_DELEGATION_BUSY', message: 'legacy run has a pending delegation', recoverable: false }
232
+ });
233
+ saveRun(resolved.taskDir, paused);
234
+ return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
235
+ }
236
+ if (existing.receipts.length > 0) {
237
+ const alreadyPaused = existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_HISTORICAL_EFFORT_UNVERIFIED';
238
+ if (alreadyPaused)
239
+ return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
240
+ const paused = withUpdatedRun(existing, {
241
+ status: 'paused',
242
+ pause: {
243
+ code: 'ORCHESTRATION_HISTORICAL_EFFORT_UNVERIFIED',
244
+ message: 'legacy receipts do not contain actual reasoning-effort evidence',
245
+ recoverable: false
246
+ }
247
+ });
248
+ saveRun(resolved.taskDir, paused);
249
+ return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
45
250
  }
46
- return { status: existing.status, changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
251
+ if (existing.modelPolicy
252
+ && typeof existing.modelPolicy.executor === 'string'
253
+ && (existing.modelPolicy.executor !== policy.executor.model
254
+ || existing.modelPolicy.reviewer !== policy.reviewer.model)) {
255
+ return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
256
+ }
257
+ const now = (options.now ?? (() => new Date().toISOString()))();
258
+ const source = { kind: sourceKind, client: options.client, resolvedAt: now };
259
+ const clearsModelPause = existing.pause?.code === 'ORCHESTRATION_MODEL_EVIDENCE_MISSING';
260
+ const resultingStatus = clearsModelPause ? 'running' : existing.status;
261
+ const recovery = {
262
+ code: 'MODEL_POLICY_SUPPLEMENTED',
263
+ recoveredAt: now,
264
+ previousSchemaVersion: 1,
265
+ previousStatus: existing.status,
266
+ previousPause: existing.pause,
267
+ policySource: source,
268
+ receiptCount: 0,
269
+ pendingDelegation: false,
270
+ resultingStatus
271
+ };
272
+ const recovered = Object.freeze({
273
+ ...existing,
274
+ schemaVersion: 2,
275
+ status: resultingStatus,
276
+ modelPolicy: policy,
277
+ modelPolicySource: source,
278
+ recoveryHistory: Object.freeze([recovery]),
279
+ pause: clearsModelPause ? null : existing.pause,
280
+ updatedAt: now
281
+ });
282
+ saveRun(resolved.taskDir, recovered);
283
+ return { status: resultingStatus, changed: true, taskId: resolved.taskId, run: recovered, next: null, error: null };
47
284
  }
285
+ let policy = options.modelPolicy;
286
+ let sourceKind = 'explicit';
287
+ if (!policy) {
288
+ try {
289
+ policy = resolveProjectPolicy(resolved.repoRoot, options.client);
290
+ sourceKind = 'project-config';
291
+ }
292
+ catch (error) {
293
+ return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
294
+ }
295
+ }
296
+ const policyError = validateModelPolicy(policy);
297
+ if (policyError)
298
+ return failed(policyError.code, policyError.message, resolved.taskId, {
299
+ client: options.client,
300
+ missingFields: ['executor.model', 'executor.reasoningEffort', 'reviewer.model', 'reviewer.reasoningEffort'],
301
+ modelSelectionContext: getAgentClientModelSelection(options.client)
302
+ });
48
303
  const now = (options.now ?? (() => new Date().toISOString()))();
304
+ const source = { kind: sourceKind, client: options.client, resolvedAt: now };
49
305
  const run = {
50
- schemaVersion: 1,
306
+ schemaVersion: 2,
51
307
  taskId: resolved.taskId,
52
308
  runId: (options.id ?? randomUUID)(),
53
309
  status: 'running',
54
310
  nextStage: null,
55
311
  stepCount: 0,
56
312
  maxSteps: options.maxSteps ?? 24,
313
+ modelPolicy: policy,
314
+ modelPolicySource: source,
315
+ recoveryHistory: [],
57
316
  baseline: '',
58
317
  pendingDelegation: null,
59
318
  receipts: [],
60
319
  pause: null,
61
320
  commitAuthorization: { issuedAt: null, consumedAt: null },
321
+ completionEvidence: null,
62
322
  createdAt: now,
63
323
  updatedAt: now
64
324
  };
@@ -146,24 +406,29 @@ function routeOrchestration(taskRef, options = {}) {
146
406
  catch (error) {
147
407
  return failed('ORCHESTRATION_TASK_READ_FAILED', String(error), resolved.taskId);
148
408
  }
409
+ let metadata;
149
410
  try {
150
- parseTypedTaskFrontmatter(content);
411
+ metadata = parseTypedTaskFrontmatter(content);
151
412
  }
152
413
  catch (error) {
153
414
  return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
154
415
  }
155
- const next = routeFromFacts(resolved.taskDir);
156
- if (!next)
416
+ const routed = routeFromFacts(resolved.taskDir);
417
+ if (!routed)
157
418
  return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
419
+ const run = readRun(resolved.taskDir);
420
+ const policy = run ? rolePolicy(run, routed.role) : null;
421
+ const next = {
422
+ ...routed,
423
+ requestedModel: policy?.model ?? null,
424
+ requestedReasoningEffort: policy?.reasoningEffort ?? null
425
+ };
158
426
  if (next.stage === 'commit') {
159
427
  const reviewRound = highestRound(resolved.taskDir, 'review-code');
160
428
  const review = parseReviewSummary(fs.readFileSync(path.join(resolved.taskDir, artifactName('review-code', reviewRound)), 'utf8'));
161
429
  if (!review.ok || review.summary.manualValidation === null) {
162
430
  return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
163
431
  }
164
- if (review.summary.manualValidation > 0) {
165
- return failed('ORCHESTRATION_MANUAL_VALIDATION_PENDING', 'manual validation must be completed before commit', resolved.taskId);
166
- }
167
432
  const rows = parseLedger(content);
168
433
  const ledgerError = validateLedgerRows(rows);
169
434
  if (ledgerError)
@@ -171,8 +436,390 @@ function routeOrchestration(taskRef, options = {}) {
171
436
  if (!summarizeLedgerStage(rows, 'code').canAdvance) {
172
437
  return failed('ORCHESTRATION_LEDGER_BLOCKED', 'code review ledger has unresolved findings or human decisions', resolved.taskId);
173
438
  }
439
+ if (run?.status === 'completed') {
440
+ return { status: 'completed', changed: false, taskId: resolved.taskId, run, next: null, error: null };
441
+ }
442
+ let config;
443
+ try {
444
+ config = JSON.parse(fs.readFileSync(path.join(resolved.repoRoot, '.agents', '.airc.json'), 'utf8'));
445
+ }
446
+ catch (error) {
447
+ return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
448
+ }
449
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
450
+ return failed('ORCHESTRATION_CONFIG_INVALID', 'project configuration must be an object', resolved.taskId);
451
+ }
452
+ const projectConfig = config;
453
+ if (!Object.hasOwn(projectConfig, 'prFlow') || projectConfig.prFlow === 'disabled') {
454
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
455
+ }
456
+ if (projectConfig.prFlow !== 'required') {
457
+ return failed('ORCHESTRATION_CONFIG_INVALID', "project configuration 'prFlow' must be 'required' or 'disabled'", resolved.taskId);
458
+ }
459
+ const prNumber = Number(metadata.pr_number);
460
+ if (!Number.isInteger(prNumber) || prNumber <= 0) {
461
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
462
+ }
463
+ if (!run) {
464
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
465
+ }
466
+ if (run.status !== 'running' || run.pendingDelegation !== null) {
467
+ return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'clean completion requires an idle running orchestration', resolved.taskId);
468
+ }
469
+ const capture = options.captureRepository ?? captureRepositorySnapshot;
470
+ let before;
471
+ try {
472
+ before = capture(resolved.repoRoot);
473
+ }
474
+ catch (error) {
475
+ return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
476
+ }
477
+ if (before.headTree !== before.worktreeTree) {
478
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
479
+ }
480
+ const lastReviewedCommit = String(metadata.last_reviewed_commit ?? '');
481
+ if (before.head !== lastReviewedCommit) {
482
+ return failed('ORCHESTRATION_REVIEWED_HEAD_MISMATCH', 'local HEAD does not match last_reviewed_commit', resolved.taskId);
483
+ }
484
+ const inspect = options.inspectPullRequest ?? inspectPlatformPullRequest;
485
+ let inspection;
486
+ try {
487
+ inspection = inspect(resolved.taskId, { cwd: resolved.repoRoot });
488
+ }
489
+ catch (error) {
490
+ return failed('ORCHESTRATION_PR_INSPECTION_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
491
+ }
492
+ if (inspection.status === 'blocked') {
493
+ return failed('ORCHESTRATION_PR_INSPECTION_BLOCKED', inspection.error?.message ?? 'pull request inspection is blocked', resolved.taskId);
494
+ }
495
+ if (inspection.status === 'failed') {
496
+ return failed('ORCHESTRATION_PR_INSPECTION_FAILED', inspection.error?.message ?? 'pull request inspection failed', resolved.taskId);
497
+ }
498
+ const inspectedPrNumber = inspection.task?.prNumber ?? inspection.prNumber;
499
+ const prHead = inspection.pullRequest?.head?.sha;
500
+ if (typeof prHead !== 'string') {
501
+ return failed('ORCHESTRATION_PR_INSPECTION_INVALID', 'pull request inspection returned incomplete identity evidence', resolved.taskId);
502
+ }
503
+ if (inspectedPrNumber !== prNumber || prHead !== before.head) {
504
+ return failed('ORCHESTRATION_PR_HEAD_MISMATCH', 'pull request identity does not match the reviewed local HEAD', resolved.taskId);
505
+ }
506
+ let after;
507
+ try {
508
+ after = capture(resolved.repoRoot);
509
+ }
510
+ catch (error) {
511
+ return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
512
+ }
513
+ if (after.headTree !== after.worktreeTree) {
514
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
515
+ }
516
+ if (after.head !== before.head || after.headTree !== before.headTree) {
517
+ return failed('ORCHESTRATION_REPOSITORY_CHANGED', 'repository changed while clean completion evidence was collected', resolved.taskId);
518
+ }
519
+ const observedAt = (options.now ?? (() => new Date().toISOString()))();
520
+ const completionEvidence = {
521
+ kind: 'reviewed-head-clean',
522
+ observedAt,
523
+ head: after.head,
524
+ headTree: after.headTree,
525
+ worktreeTree: after.worktreeTree,
526
+ lastReviewedCommit,
527
+ prNumber,
528
+ prHead
529
+ };
530
+ const completed = withUpdatedRun(run, {
531
+ status: 'completed',
532
+ nextStage: null,
533
+ pause: null,
534
+ completionEvidence
535
+ });
536
+ saveRun(resolved.taskDir, completed);
537
+ return { status: 'completed', changed: true, taskId: resolved.taskId, run: completed, next: null, error: null };
174
538
  }
175
- return { status: 'running', changed: false, taskId: resolved.taskId, run: readRun(resolved.taskDir), next, error: null };
539
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
540
+ }
541
+ function beginCommitIntent(taskRef, input, options = {}) {
542
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
543
+ if (!resolved.ok)
544
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
545
+ try {
546
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.begin', () => {
547
+ const currentHead = repositoryHead(resolved.repoRoot);
548
+ if (currentHead !== input.baselineHead) {
549
+ return commitIntentFailed('ORCHESTRATION_COMMIT_BASELINE_MISMATCH', 'baseline HEAD does not match the repository HEAD', resolved.taskId);
550
+ }
551
+ if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
552
+ try {
553
+ readCommitIntent(resolved.taskDir, resolved.taskId);
554
+ }
555
+ catch (error) {
556
+ return mapCommitIntentError(error, resolved.taskId);
557
+ }
558
+ return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists', resolved.taskId);
559
+ }
560
+ const runFile = orchestrationPath(resolved.taskDir);
561
+ let run = null;
562
+ let sourceRunBytes = null;
563
+ if (fs.existsSync(runFile)) {
564
+ sourceRunBytes = fs.readFileSync(runFile, 'utf8');
565
+ try {
566
+ run = readRun(resolved.taskDir);
567
+ }
568
+ catch (error) {
569
+ return commitIntentFailed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
570
+ }
571
+ if (!validCommitRun(run, resolved.taskId)) {
572
+ return commitIntentFailed('ORCHESTRATION_STATE_INVALID', 'orchestration state has an invalid schema', resolved.taskId);
573
+ }
574
+ }
575
+ const now = (options.now ?? (() => new Date().toISOString()))();
576
+ const failAndPause = (code, message) => {
577
+ if (run?.status === 'running') {
578
+ saveRun(resolved.taskDir, withUpdatedRun(run, {
579
+ status: 'paused', pause: { code, message, recoverable: true }
580
+ }, () => now));
581
+ }
582
+ return commitIntentFailed(code, message, resolved.taskId);
583
+ };
584
+ let orchestration = null;
585
+ if (!input.orchestrated) {
586
+ if (run?.pendingDelegation) {
587
+ return commitIntentFailed('ORCHESTRATION_STANDALONE_BUSY', 'standalone commit is blocked by a pending orchestration delegation', resolved.taskId);
588
+ }
589
+ }
590
+ else {
591
+ if (!run || run.status !== 'running') {
592
+ return commitIntentFailed('ORCHESTRATION_RUN_NOT_RUNNING', 'orchestrated commit requires a running orchestration', resolved.taskId);
593
+ }
594
+ const receipt = run.pendingDelegation;
595
+ if (!receipt
596
+ || receipt.status !== 'activated'
597
+ || receipt.stage !== 'commit'
598
+ || receipt.round !== 1
599
+ || receipt.artifact !== 'commit'
600
+ || receipt.role !== 'executor') {
601
+ return failAndPause('ORCHESTRATION_PROVENANCE_MISMATCH', 'orchestrated commit requires one matching activated commit delegation');
602
+ }
603
+ if (!run.commitAuthorization.issuedAt || run.commitAuthorization.consumedAt) {
604
+ return failAndPause('ORCHESTRATION_COMMIT_AUTHORIZATION_INVALID', 'orchestrated commit requires an unconsumed one-use authorization');
605
+ }
606
+ const planned = planOrchestrationStageCompletion(taskRef, {
607
+ stage: 'commit', round: 1, artifact: 'commit', role: 'executor', agent: input.agent
608
+ }, { ...options, now: () => now });
609
+ if (planned.result.status === 'failed' || !planned.plan?.updatedRun.pendingDelegation) {
610
+ return failAndPause(planned.result.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH', planned.result.error?.message ?? 'commit delegation could not be completed');
611
+ }
612
+ const plannedRunBytes = serialize(planned.plan.updatedRun);
613
+ orchestration = {
614
+ runId: run.runId,
615
+ receiptId: receipt.id,
616
+ authorizationIssuedAt: run.commitAuthorization.issuedAt,
617
+ sourceRunDigest: digest(sourceRunBytes),
618
+ plannedRunDigest: digest(plannedRunBytes),
619
+ completionUpdatedAt: planned.plan.updatedRun.updatedAt,
620
+ plannedReceipt: planned.plan.updatedRun.pendingDelegation
621
+ };
622
+ }
623
+ const created = createCommitIntent(resolved.taskDir, {
624
+ taskId: resolved.taskId,
625
+ mode: input.orchestrated ? 'orchestrated' : 'standalone',
626
+ phase: 'prepared',
627
+ baselineHead: input.baselineHead,
628
+ committedHead: null,
629
+ pushEvidence: null,
630
+ orchestration,
631
+ createdAt: now,
632
+ updatedAt: now
633
+ }, { token: options.token });
634
+ return {
635
+ status: 'ready', changed: true, taskId: resolved.taskId,
636
+ intent: commitIntentView(created.intent, currentHead), token: created.token, error: null
637
+ };
638
+ });
639
+ }
640
+ catch (error) {
641
+ return mapCommitIntentError(error, resolved.taskId);
642
+ }
643
+ }
644
+ function checkpointCommitIntent(taskRef, input, options = {}) {
645
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
646
+ if (!resolved.ok)
647
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
648
+ try {
649
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.checkpoint', () => {
650
+ const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
651
+ const currentHead = repositoryHead(resolved.repoRoot);
652
+ if (currentHead !== input.head) {
653
+ return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'checkpoint HEAD does not match the repository HEAD', resolved.taskId);
654
+ }
655
+ if (input.kind === 'committed' && !isAncestor(resolved.repoRoot, intent.baselineHead, input.head)) {
656
+ return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'committed HEAD does not descend from the baseline', resolved.taskId);
657
+ }
658
+ if (input.kind === 'pushed') {
659
+ const expectedHead = intent.committedHead ?? intent.baselineHead;
660
+ if (input.head !== expectedHead || !input.remote || !input.ref) {
661
+ return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_STATE_INVALID', 'pushed checkpoint requires matching remote, ref and HEAD', resolved.taskId);
662
+ }
663
+ }
664
+ const now = (options.now ?? (() => new Date().toISOString()))();
665
+ const updated = updateCommitIntent(resolved.taskDir, resolved.taskId, input.token, input.kind === 'committed'
666
+ ? { phase: 'committed', committedHead: input.head, updatedAt: now }
667
+ : {
668
+ phase: 'pushed', committedHead: intent.committedHead ?? intent.baselineHead, updatedAt: now,
669
+ pushEvidence: { remote: input.remote, ref: input.ref, head: input.head }
670
+ });
671
+ return {
672
+ status: 'ready', changed: true, taskId: resolved.taskId,
673
+ intent: commitIntentView(updated, currentHead), error: null
674
+ };
675
+ });
676
+ }
677
+ catch (error) {
678
+ return mapCommitIntentError(error, resolved.taskId);
679
+ }
680
+ }
681
+ function completeCommitOrchestration(resolved, intent, agent) {
682
+ if (intent.orchestration === null)
683
+ return null;
684
+ if (intent.orchestration.plannedReceipt.agent !== agent) {
685
+ return { code: 'ORCHESTRATION_PROVENANCE_MISMATCH', message: 'completion agent does not match the planned receipt' };
686
+ }
687
+ const runFile = orchestrationPath(resolved.taskDir);
688
+ if (!fs.existsSync(runFile)) {
689
+ return { code: 'ORCHESTRATION_RUN_MISSING', message: 'orchestration run disappeared' };
690
+ }
691
+ const currentBytes = fs.readFileSync(runFile, 'utf8');
692
+ const currentDigest = digest(currentBytes);
693
+ if (currentDigest === intent.orchestration.sourceRunDigest) {
694
+ const run = readRun(resolved.taskDir);
695
+ if (!run)
696
+ return { code: 'ORCHESTRATION_RUN_MISSING', message: 'orchestration run disappeared' };
697
+ const plannedRun = Object.freeze({
698
+ ...run,
699
+ pendingDelegation: intent.orchestration.plannedReceipt,
700
+ updatedAt: intent.orchestration.completionUpdatedAt
701
+ });
702
+ if (digest(serialize(plannedRun)) !== intent.orchestration.plannedRunDigest) {
703
+ return { code: 'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', message: 'planned orchestration bytes no longer match the intent' };
704
+ }
705
+ atomicWrite(runFile, plannedRun);
706
+ if (digest(fs.readFileSync(runFile)) !== intent.orchestration.plannedRunDigest) {
707
+ return { code: 'ORCHESTRATION_COMMIT_COMPLETE_PARTIAL', message: 'orchestration completion could not be verified' };
708
+ }
709
+ }
710
+ else if (currentDigest !== intent.orchestration.plannedRunDigest) {
711
+ return { code: 'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', message: 'orchestration state changed after commit begin' };
712
+ }
713
+ return null;
714
+ }
715
+ function finalizeCommitIntentUnlocked(resolved, inspection, agent, remove) {
716
+ if (inspection.disposition !== 'recoverable' || !inspection.intent) {
717
+ return commitIntentFailed(inspection.code ?? 'COMMIT_FINALIZATION_PENDING', inspection.message, resolved.taskId);
718
+ }
719
+ if (inspection.intent.orchestration !== null
720
+ && inspection.intent.orchestration.plannedReceipt.agent !== agent) {
721
+ return commitIntentFailed('ORCHESTRATION_PROVENANCE_MISMATCH', 'completion agent does not match the planned receipt', resolved.taskId);
722
+ }
723
+ const metadata = captureTaskWriteMetadata();
724
+ const taskPlan = planCommitTaskFinalization(resolved.taskDir, inspection, agent, metadata.timestamp);
725
+ const written = writeTask({
726
+ taskRef: resolved.taskId,
727
+ expectedState: 'active',
728
+ mutations: taskPlan.mutations
729
+ }, {
730
+ repoRoot: resolved.repoRoot,
731
+ metadataProvider: () => metadata
732
+ });
733
+ if (written.status === 'failed') {
734
+ return commitIntentFailed(written.error.code, written.error.message, resolved.taskId);
735
+ }
736
+ const orchestrationError = completeCommitOrchestration(resolved, inspection.intent, agent);
737
+ if (orchestrationError) {
738
+ return commitIntentFailed(orchestrationError.code, orchestrationError.message, resolved.taskId);
739
+ }
740
+ try {
741
+ remove();
742
+ }
743
+ catch (error) {
744
+ return commitIntentFailed('ORCHESTRATION_COMMIT_COMPLETE_PARTIAL', error instanceof Error ? error.message : String(error), resolved.taskId);
745
+ }
746
+ return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
747
+ }
748
+ function completeCommitIntent(taskRef, input, options = {}) {
749
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
750
+ if (!resolved.ok)
751
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
752
+ try {
753
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.complete', () => {
754
+ readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
755
+ const inspection = inspectCommitFinalization(resolved.taskDir, resolved.repoRoot, resolved.taskId);
756
+ return finalizeCommitIntentUnlocked(resolved, inspection, input.agent, () => {
757
+ removeCommitIntent(resolved.taskDir, resolved.taskId, input.token);
758
+ });
759
+ });
760
+ }
761
+ catch (error) {
762
+ return mapCommitIntentError(error, resolved.taskId);
763
+ }
764
+ }
765
+ function recoverCommitIntent(taskRef, input, options = {}) {
766
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
767
+ if (!resolved.ok)
768
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
769
+ try {
770
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.recover', () => {
771
+ const inspection = inspectCommitFinalization(resolved.taskDir, resolved.repoRoot, resolved.taskId);
772
+ if (inspection.disposition === 'prepared' && inspection.intentDigest) {
773
+ removeCommitIntentByDigest(resolved.taskDir, resolved.taskId, inspection.intentDigest);
774
+ return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
775
+ }
776
+ if (!inspection.intentDigest) {
777
+ return commitIntentFailed(inspection.code ?? 'COMMIT_FINALIZATION_EVIDENCE_MISSING', inspection.message, resolved.taskId);
778
+ }
779
+ return finalizeCommitIntentUnlocked(resolved, inspection, input.agent, () => {
780
+ removeCommitIntentByDigest(resolved.taskDir, resolved.taskId, inspection.intentDigest);
781
+ });
782
+ });
783
+ }
784
+ catch (error) {
785
+ return mapCommitIntentError(error, resolved.taskId);
786
+ }
787
+ }
788
+ function abortCommitIntent(taskRef, input, options = {}) {
789
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
790
+ if (!resolved.ok)
791
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
792
+ try {
793
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.abort', () => {
794
+ const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
795
+ const currentHead = repositoryHead(resolved.repoRoot);
796
+ if (intent.phase !== 'prepared'
797
+ || intent.pushEvidence !== null
798
+ || input.expectedHead !== intent.baselineHead
799
+ || currentHead !== intent.baselineHead) {
800
+ return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'commit intent has side effects or repository drift', resolved.taskId);
801
+ }
802
+ removeCommitIntent(resolved.taskDir, resolved.taskId, input.token);
803
+ return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
804
+ });
805
+ }
806
+ catch (error) {
807
+ return mapCommitIntentError(error, resolved.taskId);
808
+ }
809
+ }
810
+ function statusCommitIntent(taskRef, options = {}) {
811
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
812
+ if (!resolved.ok)
813
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
814
+ const inspection = inspectCommitFinalization(resolved.taskDir, resolved.repoRoot, resolved.taskId);
815
+ return {
816
+ status: 'ready',
817
+ changed: false,
818
+ taskId: resolved.taskId,
819
+ intent: inspection.intent ? commitIntentView(inspection.intent, inspection.currentHead) : null,
820
+ finalization: commitFinalizationView(inspection),
821
+ error: null
822
+ };
176
823
  }
177
824
  function statusOrchestration(taskRef, options = {}) {
178
825
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
@@ -183,21 +830,30 @@ function statusOrchestration(taskRef, options = {}) {
183
830
  return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
184
831
  return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
185
832
  }
186
- function prepareOrchestrationDelegation(taskRef, input, options = {}) {
833
+ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
187
834
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
188
835
  if (!resolved.ok)
189
836
  return failed(resolved.code, resolved.message, resolved.taskId);
190
- if (getAgentClientCapability(input.client, 'subagents').level === 'unsupported'
191
- || getAgentClientCapability(input.client, 'orchestration').level === 'unsupported') {
837
+ if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
838
+ try {
839
+ readCommitIntent(resolved.taskDir, resolved.taskId);
840
+ }
841
+ catch (error) {
842
+ return failed('ORCHESTRATION_COMMIT_INTENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
843
+ }
844
+ return failed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent blocks delegation preparation', resolved.taskId);
845
+ }
846
+ if (!(options.supportsLifecycleDelegation ?? supportsLifecycleDelegation)(input.client)) {
192
847
  return failed('ORCHESTRATION_CLIENT_UNSUPPORTED', `client '${input.client}' does not support lifecycle orchestration`, resolved.taskId);
193
848
  }
194
849
  const run = readRun(resolved.taskDir);
195
850
  if (!run || run.status !== 'running')
196
851
  return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
852
+ if (!isV2Policy(run.modelPolicy))
853
+ return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
197
854
  if (run.pendingDelegation)
198
855
  return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
199
- const repositoryPending = ['claude-code', 'codex']
200
- .flatMap((client) => matchingDelegations(client, () => true, options));
856
+ const repositoryPending = matchingDelegations(() => true, options);
201
857
  if (repositoryPending.length > 0) {
202
858
  return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
203
859
  }
@@ -207,9 +863,22 @@ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
207
863
  if (!routed.next)
208
864
  return routed;
209
865
  const next = routed.next;
866
+ if (!validModel(input.requestedModel)) {
867
+ return failed('ORCHESTRATION_REQUESTED_MODEL_REQUIRED', 'prepare requires the exact requested model identity', resolved.taskId);
868
+ }
869
+ if (!validModel(input.requestedReasoningEffort)) {
870
+ return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
871
+ }
872
+ const expectedPolicy = run.modelPolicy[next.role];
873
+ if (input.requestedModel !== expectedPolicy.model) {
874
+ return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
875
+ }
876
+ if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
877
+ return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
878
+ }
210
879
  let beforeFingerprint;
211
880
  try {
212
- beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(resolved.repoRoot);
881
+ beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(resolved.repoRoot, resolved.taskId);
213
882
  }
214
883
  catch (error) {
215
884
  return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
@@ -222,7 +891,9 @@ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
222
891
  round: next.round,
223
892
  artifact: next.artifact,
224
893
  client: input.client,
225
- requestedModel: input.requestedModel ?? null,
894
+ requestedModel: input.requestedModel,
895
+ requestedReasoningEffort: input.requestedReasoningEffort,
896
+ workspaceSnapshotScope: 'task',
226
897
  beforeFingerprint
227
898
  }, { id: options.id, now: options.now });
228
899
  const updated = withUpdatedRun(run, {
@@ -235,7 +906,20 @@ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
235
906
  saveRun(resolved.taskDir, updated);
236
907
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
237
908
  }
238
- function matchingDelegations(client, predicate, options) {
909
+ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
910
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
911
+ if (!resolved.ok)
912
+ return failed(resolved.code, resolved.message, resolved.taskId);
913
+ try {
914
+ return withTaskExecutionLock(resolved.repoRoot, '__repository__', 'task-orchestration.prepare.repository', () => withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.prepare.task', () => prepareOrchestrationDelegationUnlocked(taskRef, input, options)));
915
+ }
916
+ catch (error) {
917
+ if (error instanceof TaskExecutionLockError)
918
+ return failed(error.code, error.message, resolved.taskId);
919
+ throw error;
920
+ }
921
+ }
922
+ function matchingDelegations(predicate, options) {
239
923
  const repoRoot = options.repoRoot ?? process.cwd();
240
924
  const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
241
925
  if (!fs.existsSync(activeRoot))
@@ -247,13 +931,13 @@ function matchingDelegations(client, predicate, options) {
247
931
  if (!run?.pendingDelegation)
248
932
  return [];
249
933
  const receipt = run.pendingDelegation;
250
- return receipt.client === client && predicate(receipt)
934
+ return predicate(receipt)
251
935
  ? [{ taskId: entry.name, run }]
252
936
  : [];
253
937
  });
254
938
  }
255
939
  function uniqueMatchingDelegation(client, predicate, options) {
256
- const matches = matchingDelegations(client, predicate, options);
940
+ const matches = matchingDelegations((receipt) => receipt.client === client && predicate(receipt), options);
257
941
  if (matches.length === 0)
258
942
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
259
943
  if (matches.length > 1)
@@ -285,7 +969,8 @@ function sealMatchingOrchestrationDelegation(client, event, options = {}) {
285
969
  }
286
970
  const repoRoot = options.repoRoot ?? process.cwd();
287
971
  try {
288
- const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(repoRoot);
972
+ const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
973
+ const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(repoRoot, snapshotTaskId);
289
974
  const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(repoRoot, receipt.beforeFingerprint, afterFingerprint);
290
975
  return sealOrchestrationDelegation(matched.taskId, {
291
976
  childId: event.childId,
@@ -331,31 +1016,19 @@ function completeOrchestrationStage(taskRef, event, options = {}) {
331
1016
  saveRun(resolved.taskDir, updated);
332
1017
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
333
1018
  }
334
- function completeCommitOrchestrationStage(taskRef, agent, options = {}) {
1019
+ function inspectOrchestrationStage(taskRef, event, options = {}) {
335
1020
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
336
1021
  if (!resolved.ok)
337
1022
  return failed(resolved.code, resolved.message, resolved.taskId);
338
- const run = readRun(resolved.taskDir);
339
- if (!run)
340
- return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
341
- const receipt = run.pendingDelegation;
342
- if (!receipt || receipt.stage !== 'commit') {
343
- return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending commit delegation', resolved.taskId);
1023
+ let run;
1024
+ try {
1025
+ run = readRun(resolved.taskDir);
1026
+ }
1027
+ catch (error) {
1028
+ return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
344
1029
  }
345
- return completeOrchestrationStage(taskRef, {
346
- stage: receipt.stage,
347
- round: receipt.round,
348
- artifact: receipt.artifact,
349
- agent
350
- }, options);
351
- }
352
- function validateOrchestrationStage(taskRef, event, options = {}) {
353
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
354
- if (!resolved.ok)
355
- return failed(resolved.code, resolved.message, resolved.taskId);
356
- const run = readRun(resolved.taskDir);
357
1030
  if (!run)
358
- return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
1031
+ return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
359
1032
  const receipt = run.pendingDelegation;
360
1033
  if (!receipt
361
1034
  || receipt.status !== 'activated'
@@ -363,10 +1036,36 @@ function validateOrchestrationStage(taskRef, event, options = {}) {
363
1036
  || receipt.round !== event.round
364
1037
  || receipt.artifact !== event.artifact
365
1038
  || receipt.role !== event.role) {
366
- return pauseOrchestration(taskRef, 'ORCHESTRATION_PROVENANCE_MISMATCH', 'active run does not have one matching activated delegation', true, options);
1039
+ return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'active run does not have one matching activated delegation', resolved.taskId);
367
1040
  }
368
1041
  return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
369
1042
  }
1043
+ function planOrchestrationStageCompletion(taskRef, event, options = {}) {
1044
+ const inspected = inspectOrchestrationStage(taskRef, event, options);
1045
+ if (inspected.status === 'failed' || !inspected.run?.pendingDelegation) {
1046
+ return { result: inspected, plan: null };
1047
+ }
1048
+ const receipt = inspected.run.pendingDelegation;
1049
+ const completed = completeDelegationStage(receipt, event);
1050
+ if (!completed.ok) {
1051
+ return { result: failed(completed.code, completed.message, inspected.taskId), plan: null };
1052
+ }
1053
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1054
+ if (!resolved.ok)
1055
+ return { result: failed(resolved.code, resolved.message, resolved.taskId), plan: null };
1056
+ const updatedRun = withUpdatedRun(inspected.run, { pendingDelegation: completed.receipt }, options.now);
1057
+ return {
1058
+ result: { status: 'running', changed: true, taskId: resolved.taskId, run: updatedRun, next: null, error: null },
1059
+ plan: {
1060
+ taskId: resolved.taskId,
1061
+ taskDir: resolved.taskDir,
1062
+ updatedRun
1063
+ }
1064
+ };
1065
+ }
1066
+ function commitOrchestrationStageCompletion(plan) {
1067
+ saveRun(plan.taskDir, plan.updatedRun);
1068
+ }
370
1069
  function sealOrchestrationDelegation(taskRef, event, options = {}) {
371
1070
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
372
1071
  if (!resolved.ok)
@@ -416,5 +1115,5 @@ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
416
1115
  saveRun(resolved.taskDir, updated);
417
1116
  return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
418
1117
  }
419
- export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginOrResumeOrchestration, completeCommitOrchestrationStage, completeOrchestrationStage, orchestrationPath, pauseOrchestration, prepareOrchestrationDelegation, readRun, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration, validateOrchestrationStage };
1118
+ export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, abortCommitIntent, beginCommitIntent, beginOrResumeOrchestration, checkpointCommitIntent, commitOrchestrationStageCompletion, completeCommitIntent, completeOrchestrationStage, inspectOrchestrationStage, orchestrationPath, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, recoverCommitIntent, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusCommitIntent, statusOrchestration };
420
1119
  //# sourceMappingURL=orchestration.js.map