@fitlab-ai/agent-infra 0.9.14 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  8. package/dist/lib/defaults.json +103 -103
  9. package/dist/lib/fs/durable-write.js +39 -0
  10. package/dist/lib/fs/file-lock.js +111 -0
  11. package/dist/lib/host-control/audit.js +19 -0
  12. package/dist/lib/host-control/client.js +142 -0
  13. package/dist/lib/host-control/command.js +49 -0
  14. package/dist/lib/host-control/path.js +160 -0
  15. package/dist/lib/host-control/secure-fs.js +140 -0
  16. package/dist/lib/host-control/server.js +159 -0
  17. package/dist/lib/host-control/service.js +112 -0
  18. package/dist/lib/init.js +11 -2
  19. package/dist/lib/internal/cli-route-inventory.js +4 -2
  20. package/dist/lib/internal/codex-lifecycle.js +6 -6
  21. package/dist/lib/internal/demo-promotion.js +322 -0
  22. package/dist/lib/internal/demo-transcript.js +454 -0
  23. package/dist/lib/internal/host-control.js +28 -0
  24. package/dist/lib/internal/manual-validation.js +331 -0
  25. package/dist/lib/internal/platform-pr.js +18 -4
  26. package/dist/lib/internal/release-workflow.js +63 -84
  27. package/dist/lib/internal/sandbox-control.js +149 -2
  28. package/dist/lib/internal/task-artifact.js +16 -263
  29. package/dist/lib/internal/task-create.js +6 -5
  30. package/dist/lib/internal/task-event.js +37 -37
  31. package/dist/lib/internal/task-lifecycle.js +2 -2
  32. package/dist/lib/internal/task-operation-registry.js +101 -4
  33. package/dist/lib/internal/task-review.js +11 -56
  34. package/dist/lib/internal/task-validate.js +30 -10
  35. package/dist/lib/owned-path.js +33 -0
  36. package/dist/lib/platform/check-status.js +11 -0
  37. package/dist/lib/platform/comment-safety.js +4 -1
  38. package/dist/lib/platform/completion-backfill.js +1 -1
  39. package/dist/lib/platform/github-data.js +241 -0
  40. package/dist/lib/platform/github-provider.js +15 -20
  41. package/dist/lib/platform/issue-comments.js +18 -12
  42. package/dist/lib/platform/issues.js +2 -180
  43. package/dist/lib/platform/pr-change-report.js +5 -4
  44. package/dist/lib/platform/pr-checks.js +12 -65
  45. package/dist/lib/platform/pr-summary.js +117 -33
  46. package/dist/lib/platform/pull-requests.js +5 -108
  47. package/dist/lib/platform/snapshots.js +2 -0
  48. package/dist/lib/platform/task-identities.js +2 -9
  49. package/dist/lib/platform/verification-sync.js +132 -143
  50. package/dist/lib/pr-review/evidence-grading.js +4 -8
  51. package/dist/lib/release/workflow.js +1 -1
  52. package/dist/lib/sandbox/commands/create.js +7 -2
  53. package/dist/lib/sandbox/commands/rm.js +19 -1511
  54. package/dist/lib/sandbox/control/audit.js +203 -0
  55. package/dist/lib/sandbox/control/client.js +93 -54
  56. package/dist/lib/sandbox/control/executor.js +129 -65
  57. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  58. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  59. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  60. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  61. package/dist/lib/sandbox/control/protocol.js +55 -4
  62. package/dist/lib/sandbox/control/server.js +518 -127
  63. package/dist/lib/sandbox/control/state.js +64 -0
  64. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  65. package/dist/lib/sandbox/control/workflow-executor.js +89 -0
  66. package/dist/lib/sandbox/index.js +1 -2
  67. package/dist/lib/sandbox/recovery.js +28 -4
  68. package/dist/lib/sandbox/removal.js +822 -0
  69. package/dist/lib/sandbox/task-cleanup.js +217 -0
  70. package/dist/lib/sandbox/workspace-identity.js +17 -1
  71. package/dist/lib/sandbox/workspace-view.js +13 -14
  72. package/dist/lib/task/activity-intent.js +2 -1
  73. package/dist/lib/task/activity-log.js +30 -24
  74. package/dist/lib/task/artifact-command.js +79 -0
  75. package/dist/lib/task/artifact-lifecycle.js +30 -28
  76. package/dist/lib/task/artifact-name.js +36 -0
  77. package/dist/lib/task/artifact-operations.js +29 -289
  78. package/dist/lib/task/artifact-receipts.js +3 -15
  79. package/dist/lib/task/artifact-recovery.js +430 -0
  80. package/dist/lib/task/artifact-repair-intent.js +134 -29
  81. package/dist/lib/task/artifact-schema.js +6 -14
  82. package/dist/lib/task/capabilities.js +12 -19
  83. package/dist/lib/task/codex-orchestration.js +71 -25
  84. package/dist/lib/task/commit-identity.js +28 -0
  85. package/dist/lib/task/commit-operation.js +33 -20
  86. package/dist/lib/task/control-authority.js +403 -6
  87. package/dist/lib/task/control-recovery.js +140 -0
  88. package/dist/lib/task/create-service.js +1 -8
  89. package/dist/lib/task/create.js +62 -9
  90. package/dist/lib/task/decision-details.js +2 -46
  91. package/dist/lib/task/delegation-receipts.js +64 -3
  92. package/dist/lib/task/events.js +275 -22
  93. package/dist/lib/task/finalization-state.js +9 -0
  94. package/dist/lib/task/human-override.js +8 -14
  95. package/dist/lib/task/implementation-inputs.js +3 -3
  96. package/dist/lib/task/intermediate-cleanup.js +441 -0
  97. package/dist/lib/task/invalidation.js +18 -65
  98. package/dist/lib/task/ledger-intents.js +7 -22
  99. package/dist/lib/task/ledger.js +1 -3
  100. package/dist/lib/task/lifecycle-recovery.js +549 -0
  101. package/dist/lib/task/lifecycle.js +31 -4
  102. package/dist/lib/task/local-artifact-finalization.js +197 -197
  103. package/dist/lib/task/manual-validation-completion.js +26 -0
  104. package/dist/lib/task/manual-validation-evidence.js +130 -0
  105. package/dist/lib/task/manual-validation-receipt.js +105 -0
  106. package/dist/lib/task/manual-validation-shared.js +40 -0
  107. package/dist/lib/task/manual-validation-transaction.js +243 -0
  108. package/dist/lib/task/markdown.js +47 -0
  109. package/dist/lib/task/orchestration.js +66 -19
  110. package/dist/lib/task/pr-delivery-fact.js +12 -56
  111. package/dist/lib/task/qualification-audit.js +20 -34
  112. package/dist/lib/task/recovery-warning.js +19 -0
  113. package/dist/lib/task/review-artifacts.js +2 -18
  114. package/dist/lib/task/review-command.js +26 -0
  115. package/dist/lib/task/review-finalization.js +175 -216
  116. package/dist/lib/task/review-fingerprint.js +2 -2
  117. package/dist/lib/task/sections.js +34 -56
  118. package/dist/lib/task/short-id.js +15 -7
  119. package/dist/lib/task/task-execution-lock.js +1 -1
  120. package/dist/lib/task/verification-engine.js +119 -337
  121. package/dist/lib/task/verification.js +1 -1
  122. package/dist/lib/task/workflow-command.js +16 -0
  123. package/dist/lib/task/workflow-warning-intents.js +0 -16
  124. package/dist/lib/task/workflow-warnings.js +28 -2
  125. package/dist/lib/update.js +2 -0
  126. package/dist/lib/version.js +1 -11
  127. package/dist/lifecycle-build-manifest.json +3 -3
  128. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  129. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
  130. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  131. package/lib/fs/durable-write.ts +30 -0
  132. package/lib/fs/file-lock.ts +113 -0
  133. package/lib/host-control/audit.ts +22 -0
  134. package/lib/host-control/client.ts +172 -0
  135. package/lib/host-control/command.ts +60 -0
  136. package/lib/host-control/path.ts +167 -0
  137. package/lib/host-control/secure-fs.ts +135 -0
  138. package/lib/host-control/server.ts +180 -0
  139. package/lib/host-control/service.ts +133 -0
  140. package/lib/init.ts +15 -2
  141. package/lib/internal/cli-route-inventory.ts +4 -2
  142. package/lib/internal/codex-lifecycle.ts +6 -6
  143. package/lib/internal/demo-promotion.ts +354 -0
  144. package/lib/internal/demo-transcript.ts +419 -0
  145. package/lib/internal/host-control.ts +28 -0
  146. package/lib/internal/manual-validation.ts +315 -0
  147. package/lib/internal/platform-pr.ts +15 -4
  148. package/lib/internal/release-workflow.ts +74 -82
  149. package/lib/internal/sandbox-control.ts +162 -1
  150. package/lib/internal/task-artifact.ts +14 -217
  151. package/lib/internal/task-create.ts +5 -5
  152. package/lib/internal/task-event.ts +20 -11
  153. package/lib/internal/task-lifecycle.ts +4 -3
  154. package/lib/internal/task-operation-registry.ts +111 -4
  155. package/lib/internal/task-review.ts +9 -54
  156. package/lib/internal/task-validate.ts +26 -10
  157. package/lib/owned-path.ts +26 -0
  158. package/lib/platform/check-status.ts +9 -0
  159. package/lib/platform/comment-safety.ts +5 -0
  160. package/lib/platform/completion-backfill.ts +1 -1
  161. package/lib/platform/github-data.ts +322 -0
  162. package/lib/platform/github-provider.ts +15 -20
  163. package/lib/platform/issue-comments.ts +21 -14
  164. package/lib/platform/issues.ts +5 -246
  165. package/lib/platform/pr-change-report.ts +7 -5
  166. package/lib/platform/pr-checks.ts +13 -77
  167. package/lib/platform/pr-summary.ts +123 -35
  168. package/lib/platform/pull-requests.ts +6 -173
  169. package/lib/platform/snapshots.ts +53 -0
  170. package/lib/platform/task-identities.ts +3 -14
  171. package/lib/platform/verification-sync.ts +150 -156
  172. package/lib/pr-review/evidence-grading.ts +4 -9
  173. package/lib/release/workflow.ts +3 -3
  174. package/lib/sandbox/commands/create.ts +7 -6
  175. package/lib/sandbox/commands/rm.ts +21 -1895
  176. package/lib/sandbox/control/audit.ts +253 -0
  177. package/lib/sandbox/control/client.ts +109 -66
  178. package/lib/sandbox/control/executor.ts +134 -74
  179. package/lib/sandbox/control/finalization-response.ts +29 -0
  180. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  181. package/lib/sandbox/control/lifecycle.ts +45 -3
  182. package/lib/sandbox/control/native-file-lock.ts +6 -107
  183. package/lib/sandbox/control/protocol.ts +80 -6
  184. package/lib/sandbox/control/server.ts +567 -132
  185. package/lib/sandbox/control/state.ts +86 -0
  186. package/lib/sandbox/control/task-workflow.ts +95 -0
  187. package/lib/sandbox/control/workflow-executor.ts +92 -0
  188. package/lib/sandbox/index.ts +1 -2
  189. package/lib/sandbox/recovery.ts +28 -5
  190. package/lib/sandbox/removal.ts +1142 -0
  191. package/lib/sandbox/task-cleanup.ts +209 -0
  192. package/lib/sandbox/workspace-identity.ts +19 -3
  193. package/lib/sandbox/workspace-view.ts +17 -15
  194. package/lib/task/activity-intent.ts +2 -2
  195. package/lib/task/activity-log.ts +27 -19
  196. package/lib/task/artifact-command.ts +85 -0
  197. package/lib/task/artifact-lifecycle.ts +31 -26
  198. package/lib/task/artifact-name.ts +55 -0
  199. package/lib/task/artifact-operations.ts +34 -310
  200. package/lib/task/artifact-receipts.ts +3 -14
  201. package/lib/task/artifact-recovery.ts +519 -0
  202. package/lib/task/artifact-repair-intent.ts +162 -33
  203. package/lib/task/artifact-schema.ts +5 -34
  204. package/lib/task/capabilities.ts +13 -22
  205. package/lib/task/codex-orchestration.ts +85 -24
  206. package/lib/task/commit-identity.ts +31 -0
  207. package/lib/task/commit-operation.ts +37 -20
  208. package/lib/task/control-authority.ts +540 -10
  209. package/lib/task/control-recovery.ts +186 -0
  210. package/lib/task/create-service.ts +1 -10
  211. package/lib/task/create.ts +63 -8
  212. package/lib/task/decision-details.ts +3 -71
  213. package/lib/task/delegation-receipts.ts +73 -2
  214. package/lib/task/events.ts +309 -24
  215. package/lib/task/finalization-state.ts +11 -0
  216. package/lib/task/human-override.ts +8 -16
  217. package/lib/task/implementation-inputs.ts +3 -3
  218. package/lib/task/intermediate-cleanup.ts +493 -0
  219. package/lib/task/invalidation.ts +18 -62
  220. package/lib/task/ledger-intents.ts +7 -24
  221. package/lib/task/ledger.ts +1 -3
  222. package/lib/task/lifecycle-recovery.ts +691 -0
  223. package/lib/task/lifecycle.ts +30 -5
  224. package/lib/task/local-artifact-finalization.ts +244 -234
  225. package/lib/task/manual-validation-completion.ts +40 -0
  226. package/lib/task/manual-validation-evidence.ts +209 -0
  227. package/lib/task/manual-validation-receipt.ts +156 -0
  228. package/lib/task/manual-validation-shared.ts +50 -0
  229. package/lib/task/manual-validation-transaction.ts +304 -0
  230. package/lib/task/markdown.ts +70 -0
  231. package/lib/task/orchestration.ts +84 -19
  232. package/lib/task/pr-delivery-fact.ts +15 -62
  233. package/lib/task/qualification-audit.ts +22 -33
  234. package/lib/task/recovery-warning.ts +21 -0
  235. package/lib/task/review-artifacts.ts +1 -20
  236. package/lib/task/review-command.ts +24 -0
  237. package/lib/task/review-finalization.ts +183 -292
  238. package/lib/task/review-fingerprint.ts +2 -2
  239. package/lib/task/sections.ts +33 -54
  240. package/lib/task/short-id.ts +27 -5
  241. package/lib/task/task-execution-lock.ts +1 -1
  242. package/lib/task/verification-engine.ts +113 -383
  243. package/lib/task/verification-types.ts +15 -0
  244. package/lib/task/verification.ts +1 -1
  245. package/lib/task/workflow-command.ts +17 -0
  246. package/lib/task/workflow-warning-intents.ts +0 -13
  247. package/lib/task/workflow-warnings.ts +21 -2
  248. package/lib/update.ts +6 -0
  249. package/lib/version.ts +1 -15
  250. package/package.json +6 -6
  251. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  252. package/templates/.agents/rules/create-issue.en.md +1 -1
  253. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  254. package/templates/.agents/rules/issue-sync.en.md +4 -2
  255. package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
  256. package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
  257. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
  258. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  259. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  260. package/templates/.agents/scripts/package.json +3 -0
  261. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  262. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  263. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  264. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  265. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  266. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  267. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  268. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  269. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  270. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  271. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  272. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
  273. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  274. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  275. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  276. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  277. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
  278. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
  279. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  280. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  281. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  282. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  283. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  284. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  285. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  286. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  287. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  288. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  289. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  290. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  291. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  292. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  293. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  294. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  295. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  296. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  297. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  298. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  299. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  300. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  301. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  302. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  303. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  304. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  305. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  306. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  307. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  308. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  309. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
  310. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  311. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  312. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  313. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  314. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  315. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  316. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  317. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  318. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  319. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  320. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  321. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  322. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  323. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  324. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  325. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  326. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  327. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  328. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  329. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  330. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  331. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  332. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  333. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  334. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  335. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  336. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  337. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  338. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  339. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  340. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  341. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  342. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  343. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  344. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  345. package/templates/.agents/templates/task.en.md +1 -1
  346. package/templates/.agents/templates/task.zh-CN.md +1 -1
  347. package/templates/.claude/commands/import-issue.en.md +1 -1
  348. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  349. package/templates/.claude/commands/restore-task.en.md +1 -1
  350. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
  351. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  352. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  353. package/dist/lib/platform/adapters.js +0 -56
  354. package/lib/platform/adapters.ts +0 -197
@@ -1,4 +1,6 @@
1
1
  import path from 'node:path';
2
+ import crypto from 'node:crypto';
3
+ import fs from 'node:fs';
2
4
  import { normalizeAgentToken } from '../agent-clients/tokens.js';
3
5
  import { isAgentClientId } from '../agent-clients/types.js';
4
6
  import { consumeHumanOverride, failureId, overrideDryRunConflict } from './human-override.js';
@@ -6,9 +8,358 @@ import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegatio
6
8
  import { prepareCodexOrchestrationDelegation } from './codex-orchestration.js';
7
9
  import { applyTaskFinalization } from './finalization.js';
8
10
  import { applyTaskLifecycle, taskLifecycleFailure } from './lifecycle.js';
11
+ import { recoverStartedLifecycleUnderLock, recoveryFailure } from './lifecycle-recovery.js';
12
+ import { locateActivityLog } from './activity-log.js';
9
13
  import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
10
14
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
11
15
  import { verifyTaskEvent } from './verification.js';
16
+ import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.js';
17
+ import { findArtifactRecoveryIntentsByOperation, writeArtifactRecoveryIntent } from './artifact-repair-intent.js';
18
+ import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
19
+ const lifecycleAuthorityPhases = [
20
+ 'orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'
21
+ ];
22
+ const lifecycleAuthorityFamilies = ['analysis', 'plan', 'code'];
23
+ const lifecycleAuthorityArtifact = /^(?:analysis|plan|code)(?:-r[1-9]\d*)?\.md$/u;
24
+ const lifecycleAuthorityDigest = (value) => crypto.createHash('sha256').update(value, 'utf8').digest('hex');
25
+ const lifecycleAuthorityBuildDigest = (value) => lifecycleAuthorityDigest(JSON.stringify(value));
26
+ export function lifecycleRecoveryAttestationDigest(value) {
27
+ return lifecycleAuthorityDigest(JSON.stringify(value));
28
+ }
29
+ function authorityExactKeys(value, keys) {
30
+ return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
31
+ }
32
+ export function validateLifecycleAuthorityRequest(value) {
33
+ if (!value || typeof value !== 'object' || Array.isArray(value))
34
+ throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
35
+ const request = value;
36
+ if (!authorityExactKeys(request, [
37
+ 'artifact', 'authorityRef', 'expectedBuildIdentityDigest', 'expectedControlGeneration',
38
+ 'expectedControllerInstanceDigest', 'expectedHookDefinitionHash', 'family', 'lifecycleRequestId',
39
+ 'operationId', 'phase', 'requestId', 'round', 'taskId', 'version'
40
+ ])
41
+ || request.version !== 1
42
+ || typeof request.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.requestId)
43
+ || typeof request.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.operationId)
44
+ || !lifecycleAuthorityPhases.includes(request.phase)
45
+ || typeof request.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(request.taskId)
46
+ || !lifecycleAuthorityFamilies.includes(request.family)
47
+ || typeof request.artifact !== 'string' || !lifecycleAuthorityArtifact.test(request.artifact)
48
+ || !Number.isSafeInteger(request.round) || request.round < 1
49
+ || typeof request.lifecycleRequestId !== 'string' || !request.lifecycleRequestId || /[\r\n]/u.test(request.lifecycleRequestId)
50
+ || typeof request.authorityRef !== 'string' || !request.authorityRef || /[\r\n]/u.test(request.authorityRef)
51
+ || typeof request.expectedControlGeneration !== 'string' || !request.expectedControlGeneration
52
+ || typeof request.expectedControllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedControllerInstanceDigest)
53
+ || typeof request.expectedBuildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedBuildIdentityDigest)
54
+ || typeof request.expectedHookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedHookDefinitionHash)) {
55
+ throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
56
+ }
57
+ const expectedArtifactFamily = request.artifact;
58
+ if (!expectedArtifactFamily.startsWith(request.family))
59
+ throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
60
+ return request;
61
+ }
62
+ export function validateLifecycleRecoveryAttestation(value, now = Date.now()) {
63
+ if (!value || typeof value !== 'object' || Array.isArray(value))
64
+ throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
65
+ const attestation = value;
66
+ if (!authorityExactKeys(attestation, [
67
+ 'artifact', 'attestationId', 'authorityRefDigest', 'buildIdentityDigest', 'controlGeneration',
68
+ 'controllerInstanceDigest', 'expiresAt', 'family', 'hookDefinitionHash', 'issuedAt',
69
+ 'lifecycleRequestId', 'operationId', 'phase', 'requestId', 'round', 'sessionId',
70
+ 'taskId', 'toolUseId', 'turnId', 'version'
71
+ ])
72
+ || attestation.version !== 1
73
+ || typeof attestation.attestationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.attestationId)
74
+ || typeof attestation.authorityRefDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.authorityRefDigest)
75
+ || typeof attestation.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.operationId)
76
+ || !lifecycleAuthorityPhases.includes(attestation.phase)
77
+ || typeof attestation.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.requestId)
78
+ || typeof attestation.lifecycleRequestId !== 'string' || !attestation.lifecycleRequestId
79
+ || typeof attestation.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(attestation.taskId)
80
+ || !lifecycleAuthorityFamilies.includes(attestation.family)
81
+ || typeof attestation.artifact !== 'string' || !lifecycleAuthorityArtifact.test(attestation.artifact)
82
+ || !Number.isSafeInteger(attestation.round) || attestation.round < 1
83
+ || typeof attestation.controlGeneration !== 'string' || !attestation.controlGeneration
84
+ || typeof attestation.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.controllerInstanceDigest)
85
+ || typeof attestation.sessionId !== 'string' || !attestation.sessionId
86
+ || typeof attestation.turnId !== 'string' || !attestation.turnId
87
+ || typeof attestation.toolUseId !== 'string' || !attestation.toolUseId
88
+ || typeof attestation.buildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.buildIdentityDigest)
89
+ || typeof attestation.hookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.hookDefinitionHash)
90
+ || !Number.isSafeInteger(attestation.issuedAt) || !Number.isSafeInteger(attestation.expiresAt)
91
+ || attestation.expiresAt <= attestation.issuedAt
92
+ || attestation.expiresAt <= now) {
93
+ throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
94
+ }
95
+ return attestation;
96
+ }
97
+ const authorityOperationMap = new WeakMap();
98
+ const authorityRecords = new Map();
99
+ const consumedAuthorityPhases = new Set();
100
+ export function issueLifecycleRecoveryAttestation(input, options = {}) {
101
+ let request;
102
+ try {
103
+ request = validateLifecycleAuthorityRequest(input);
104
+ }
105
+ catch (error) {
106
+ return { version: 1, requestId: String(input?.requestId ?? ''), operationId: String(input?.operationId ?? ''), phase: input?.phase ?? 'artifact.finalize-local', status: 'rejected', attestation: null, error: { code: 'LIFECYCLE_AUTHORITY_REQUEST_INVALID', message: error instanceof Error ? error.message : String(error) } };
107
+ }
108
+ const binding = options.controllerBinding ?? null;
109
+ if (!binding)
110
+ return authorityRejected(request, 'LOCAL_EXECUTION_AUTHORITY_UNAVAILABLE', 'current controller authority is unavailable');
111
+ if (binding.controlGeneration !== request.expectedControlGeneration
112
+ || binding.instanceDigest !== request.expectedControllerInstanceDigest) {
113
+ return authorityRejected(request, 'LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the request');
114
+ }
115
+ const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(process.cwd());
116
+ if (lifecycleAuthorityBuildDigest(buildIdentity) !== request.expectedBuildIdentityDigest) {
117
+ return authorityRejected(request, 'LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the request');
118
+ }
119
+ const store = options.capabilityStore ?? createCodexCapabilityStore();
120
+ const registry = options.operationAttestations ?? authorityOperationMap.get(store) ?? new Map();
121
+ authorityOperationMap.set(store, registry);
122
+ const key = `${request.requestId}\0${request.operationId}\0${request.phase}`;
123
+ const prior = registry.get(key);
124
+ if (prior)
125
+ return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'already-completed', attestation: prior, error: null };
126
+ try {
127
+ const expected = {
128
+ taskId: request.taskId,
129
+ hookDefinitionHash: request.expectedHookDefinitionHash,
130
+ buildIdentity,
131
+ controller: binding
132
+ };
133
+ const record = store.reserveReference(request.authorityRef, request.operationId, expected);
134
+ if (!record.sessionId || !record.turnId || !record.toolUseId)
135
+ throw new Error('CODEX_CAPABILITY_IDENTITY_INVALID: capability hook attestation is incomplete');
136
+ const now = (options.now ?? Date.now)();
137
+ const attestation = validateLifecycleRecoveryAttestation({
138
+ version: 1,
139
+ attestationId: crypto.randomUUID(),
140
+ authorityRefDigest: lifecycleAuthorityDigest(request.authorityRef),
141
+ operationId: request.operationId,
142
+ phase: request.phase,
143
+ requestId: request.requestId,
144
+ lifecycleRequestId: request.lifecycleRequestId,
145
+ taskId: request.taskId,
146
+ family: request.family,
147
+ artifact: request.artifact,
148
+ round: request.round,
149
+ controlGeneration: binding.controlGeneration,
150
+ controllerInstanceDigest: binding.instanceDigest,
151
+ sessionId: record.sessionId,
152
+ turnId: record.turnId,
153
+ toolUseId: record.toolUseId,
154
+ buildIdentityDigest: lifecycleAuthorityBuildDigest(buildIdentity),
155
+ hookDefinitionHash: request.expectedHookDefinitionHash,
156
+ issuedAt: now,
157
+ expiresAt: Math.min(record.expiresAt, now + 30_000)
158
+ }, now);
159
+ store.reserveRecoveryPhase(request.authorityRef, request.operationId, request.phase, request.requestId, expected);
160
+ registry.set(key, attestation);
161
+ authorityRecords.set(attestation.attestationId, { store, capabilityRef: request.authorityRef, expected });
162
+ return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'issued', attestation, error: null };
163
+ }
164
+ catch (error) {
165
+ return authorityRejected(request, error instanceof Error && error.name.startsWith('CODEX_') ? error.name : 'LIFECYCLE_AUTHORITY_REJECTED', error instanceof Error ? error.message : String(error));
166
+ }
167
+ }
168
+ export function consumeLifecycleRecoveryAttestation(value, now = Date.now()) {
169
+ const attestation = validateLifecycleRecoveryAttestation(value, now);
170
+ if (consumedAuthorityPhases.has(attestation.attestationId))
171
+ return;
172
+ const record = authorityRecords.get(attestation.attestationId);
173
+ if (!record)
174
+ throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_UNKNOWN');
175
+ record.store.consumeRecoveryPhase(record.capabilityRef, attestation.operationId, attestation.phase, attestation.requestId, record.expected);
176
+ if (attestation.phase === 'task-event.completed') {
177
+ record.store.consumeReference(record.capabilityRef, attestation.operationId, record.expected);
178
+ }
179
+ consumedAuthorityPhases.add(attestation.attestationId);
180
+ }
181
+ export function queryLifecycleRecoveryOperation(operationId, options = {}) {
182
+ if (!/^[a-f0-9-]{16,64}$/u.test(operationId)) {
183
+ throw new Error('LIFECYCLE_RECOVERY_OPERATION_INVALID');
184
+ }
185
+ const store = options.capabilityStore ?? createCodexCapabilityStore();
186
+ const registry = authorityOperationMap.get(store);
187
+ const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
188
+ const intents = options.repoRoot ? findArtifactRecoveryIntentsByOperation(options.repoRoot, operationId) : [];
189
+ const capabilities = store.findByRecoveryOperation(operationId);
190
+ const phaseMap = new Map();
191
+ for (const capability of capabilities) {
192
+ for (const phase of capability.recoveryPhases) {
193
+ phaseMap.set(`${phase.phase}\0${phase.requestId}`, {
194
+ phase: phase.phase,
195
+ requestId: phase.requestId,
196
+ attestationId: null,
197
+ state: phase.state === 'consumed' ? 'consumed' : 'observed'
198
+ });
199
+ }
200
+ }
201
+ for (const attestation of known) {
202
+ const key = `${attestation.phase}\0${attestation.requestId}`;
203
+ const durable = phaseMap.get(key);
204
+ phaseMap.set(key, {
205
+ phase: attestation.phase,
206
+ requestId: attestation.requestId,
207
+ attestationId: attestation.attestationId,
208
+ state: durable?.state === 'consumed' || consumedAuthorityPhases.has(attestation.attestationId)
209
+ ? 'consumed' : 'issued'
210
+ });
211
+ }
212
+ for (const intent of intents) {
213
+ if (!intent.phase)
214
+ continue;
215
+ const key = `${intent.phase}\0${intent.requestId}`;
216
+ if (phaseMap.has(key))
217
+ continue;
218
+ phaseMap.set(key, {
219
+ phase: intent.phase,
220
+ requestId: intent.requestId,
221
+ attestationId: null,
222
+ state: intent.state === 'consumed' ? 'consumed' : 'observed'
223
+ });
224
+ }
225
+ const capabilityState = capabilities.some((record) => record.recoveryState === 'consumed')
226
+ ? 'consumed'
227
+ : capabilities.some((record) => record.recoveryState === 'reserved') ? 'reserved' : 'unknown';
228
+ const committed = capabilityState === 'consumed' || intents.some((intent) => intent.state === 'consumed');
229
+ return {
230
+ version: 1,
231
+ operationId,
232
+ status: committed ? 'committed' : capabilityState === 'reserved' || phaseMap.size > 0 ? 'in-progress' : 'unknown',
233
+ capabilityState,
234
+ phases: Object.freeze([...phaseMap.values()])
235
+ };
236
+ }
237
+ function lifecycleRecoveryCompensationError(code, message) {
238
+ const error = new Error(`${code}: ${message}`);
239
+ error.name = code;
240
+ return error;
241
+ }
242
+ function lifecycleRecoveryEventCommitted(selector, repoRoot) {
243
+ const resolved = resolveTaskRef(selector.taskId, { repoRoot });
244
+ if (!resolved.ok || resolved.taskId !== selector.taskId)
245
+ return false;
246
+ let content;
247
+ try {
248
+ content = fs.readFileSync(resolved.taskMdPath, 'utf8');
249
+ }
250
+ catch {
251
+ return false;
252
+ }
253
+ const section = locateActivityLog(content);
254
+ if (!section)
255
+ return false;
256
+ const labels = { analysis: 'Analyze Task', plan: 'Plan Task', code: 'Code Task' };
257
+ const prefix = `${labels[selector.family]} (Round ${selector.round}`;
258
+ return section.entries.some((entry) => {
259
+ if (entry.step.endsWith(' [started]') || !entry.step.startsWith(prefix))
260
+ return false;
261
+ const delimiter = entry.step[prefix.length];
262
+ return (delimiter === ',' || delimiter === ')') && entry.note.endsWith(`→ ${selector.artifact}`);
263
+ });
264
+ }
265
+ function lifecycleRecoveryIntentMatches(intent, selector) {
266
+ return intent.taskId === selector.taskId
267
+ && intent.family === selector.family
268
+ && intent.artifact === selector.artifact
269
+ && (intent.state === 'commit-started' || intent.state === 'consumed');
270
+ }
271
+ /**
272
+ * Completes only the durable tail of a committed task event after a process
273
+ * restart. The task event itself is the commit boundary and is never replayed.
274
+ */
275
+ export function recoverLifecycleRecoveryOperation(input, options) {
276
+ const selector = validateLifecycleAuthorityRequest(input);
277
+ if (selector.phase !== 'task-event.completed') {
278
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_UNSUPPORTED', 'only task-event.completed supports durable compensation');
279
+ }
280
+ const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(options.repoRoot);
281
+ if (selector.expectedBuildIdentityDigest !== lifecycleAuthorityBuildDigest(buildIdentity)) {
282
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the recovery request');
283
+ }
284
+ if (selector.expectedControlGeneration !== options.controllerBinding.controlGeneration
285
+ || selector.expectedControllerInstanceDigest !== options.controllerBinding.instanceDigest) {
286
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the recovery request');
287
+ }
288
+ const store = options.capabilityStore ?? createCodexCapabilityStore();
289
+ const expected = {
290
+ taskId: selector.taskId,
291
+ hookDefinitionHash: selector.expectedHookDefinitionHash,
292
+ buildIdentity,
293
+ controller: options.controllerBinding
294
+ };
295
+ const capabilities = store.findByRecoveryOperation(selector.operationId);
296
+ if (capabilities.length !== 1 || capabilities[0].capabilityRefDigest !== lifecycleAuthorityDigest(selector.authorityRef)) {
297
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not identify this operation');
298
+ }
299
+ const capability = capabilities[0];
300
+ const build = verifyLifecycleBuildIdentity(capability.buildIdentity, buildIdentity);
301
+ const expiredReserved = capability.status === 'expired' && capability.recoveryState === 'reserved';
302
+ if (!build.ok
303
+ || capability.taskId !== selector.taskId
304
+ || capability.hookDefinitionHash !== selector.expectedHookDefinitionHash
305
+ || capability.controller?.controlGeneration !== options.controllerBinding.controlGeneration
306
+ || capability.controller?.instanceDigest !== options.controllerBinding.instanceDigest
307
+ || capability.recoveryState === 'unreserved'
308
+ || capability.recoveryOperationId !== selector.operationId
309
+ || (!['attested', 'consumed'].includes(capability.status) && !expiredReserved)) {
310
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not match the recovery request');
311
+ }
312
+ const phase = capability.recoveryPhases.find((entry) => entry.phase === selector.phase);
313
+ if (!phase) {
314
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_MISSING', 'durable task completion phase is missing');
315
+ }
316
+ const intents = findArtifactRecoveryIntentsByOperation(options.repoRoot, selector.operationId)
317
+ .filter((intent) => intent.taskId === selector.taskId
318
+ && intent.family === selector.family
319
+ && intent.artifact === selector.artifact);
320
+ if (intents.length !== 1) {
321
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not identify this task event');
322
+ }
323
+ const intent = intents[0];
324
+ if (!lifecycleRecoveryIntentMatches(intent, selector)
325
+ || intent.phase !== selector.phase
326
+ || intent.requestId !== selector.lifecycleRequestId
327
+ || intent.authorityDigest === null) {
328
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not match the recovery request');
329
+ }
330
+ if (!lifecycleRecoveryEventCommitted(selector, options.repoRoot)) {
331
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_COMMIT_UNCONFIRMED', 'task completion is not durably present; compensation is not allowed');
332
+ }
333
+ if (capability.recoveryState === 'reserved') {
334
+ if (phase.state === 'issued') {
335
+ store.consumeRecoveryPhase(selector.authorityRef, selector.operationId, selector.phase, phase.requestId, expected, { allowExpiredReserved: true });
336
+ }
337
+ store.consumeReference(selector.authorityRef, selector.operationId, expected, { allowExpiredReserved: true });
338
+ }
339
+ else if (phase.state !== 'consumed') {
340
+ throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_STATE_INVALID', 'consumed capability has an unconsumed completion phase');
341
+ }
342
+ if (intent.state !== 'consumed') {
343
+ writeArtifactRecoveryIntent(options.repoRoot, {
344
+ ...intent,
345
+ state: 'consumed',
346
+ updatedAt: (options.now ?? Date.now)()
347
+ }, { expected: intent });
348
+ }
349
+ return queryLifecycleRecoveryOperation(selector.operationId, { repoRoot: options.repoRoot, capabilityStore: store });
350
+ }
351
+ function authorityRejected(request, code, message) {
352
+ return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'rejected', attestation: null, error: { code, message } };
353
+ }
354
+ export function taskControlOperationKey(operation) {
355
+ if (operation.family === 'task-finalization')
356
+ return 'complete';
357
+ if (operation.family === 'task-lifecycle')
358
+ return operation.request.intent;
359
+ return operation.intent === 'route'
360
+ ? operation.input && operation.input.completed === true ? 'route.clean-completion' : 'route.read'
361
+ : operation.intent;
362
+ }
12
363
  function invalidContext(message) {
13
364
  throw new Error(`TASK_CONTROL_CONTEXT_INVALID: ${message}`);
14
365
  }
@@ -31,7 +382,11 @@ export function createDirectHostExecutionContext(params) {
31
382
  mode: 'direct-host',
32
383
  repoRoot: absolute('repoRoot', params.repoRoot),
33
384
  ...(params.runtimeDir === undefined ? {} : { runtimeDir: absolute('runtimeDir', params.runtimeDir) }),
34
- ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) })
385
+ ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
386
+ ...(params.lifecycleRecoveryAttestation === undefined ? {} : {
387
+ lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
388
+ ? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
389
+ })
35
390
  };
36
391
  }
37
392
  export function createSandboxExecutorExecutionContext(params) {
@@ -53,7 +408,11 @@ export function createSandboxExecutorExecutionContext(params) {
53
408
  manifestPath: absolute('manifestPath', params.manifestPath),
54
409
  requestId: params.requestId,
55
410
  ...(params.diagnosticLog === undefined ? {} : { diagnosticLog: params.diagnosticLog }),
56
- ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) })
411
+ ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
412
+ ...(params.lifecycleRecoveryAttestation === undefined ? {} : {
413
+ lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
414
+ ? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
415
+ })
57
416
  };
58
417
  }
59
418
  export function assertTaskControlExecutionContext(context) {
@@ -64,6 +423,8 @@ export function assertTaskControlExecutionContext(context) {
64
423
  if (context.runtimeDir !== undefined)
65
424
  absolute('runtimeDir', context.runtimeDir);
66
425
  binding(context.controllerBinding);
426
+ if (context.lifecycleRecoveryAttestation)
427
+ validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
67
428
  return;
68
429
  }
69
430
  if (context.mode !== 'task-bound-sandbox' || !/^TASK-\d{8}-\d{6}$/u.test(context.taskId)
@@ -78,6 +439,8 @@ export function assertTaskControlExecutionContext(context) {
78
439
  invalidContext('runtimeDir is not bound to the manifest control root');
79
440
  }
80
441
  binding(context.controllerBinding);
442
+ if (context.lifecycleRecoveryAttestation)
443
+ validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
81
444
  }
82
445
  function operationTaskId(context, taskRef) {
83
446
  if (context.source === 'sandbox-executor' && taskRef !== context.taskId) {
@@ -111,6 +474,9 @@ async function applyLifecycleWithAuthority(context, request) {
111
474
  return taskLifecycleFailure(request, { code: 'LIFECYCLE_PAYLOAD_INVALID', message: conflict.message });
112
475
  }
113
476
  const execute = async () => {
477
+ if (request.intent === 'recover-started') {
478
+ return recoverStartedLifecycleUnderLock(request, { repoRoot: context.repoRoot });
479
+ }
114
480
  const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
115
481
  if (lifecycleResult.status !== 'failed' || !request.overrideTicket)
116
482
  return lifecycleResult;
@@ -144,6 +510,9 @@ async function applyLifecycleWithAuthority(context, request) {
144
510
  catch (error) {
145
511
  if (!(error instanceof TaskExecutionLockError))
146
512
  throw error;
513
+ if (request.intent === 'recover-started') {
514
+ return recoveryFailure(request, 'owner-unknown', error.code, error.message);
515
+ }
147
516
  return taskLifecycleFailure(request, { code: error.code, message: error.message }, taskId);
148
517
  }
149
518
  }
@@ -166,12 +535,16 @@ function orchestration(context, operation) {
166
535
  client: input.client,
167
536
  requestedModel: input.requestedModel,
168
537
  requestedReasoningEffort: input.requestedReasoningEffort,
169
- ...(input.capabilityToken === undefined ? {} : { capabilityToken: input.capabilityToken })
538
+ ...(input.capabilityRef === undefined ? {} : { capabilityRef: input.capabilityRef }),
539
+ ...(context.lifecycleRecoveryAttestation === undefined
540
+ ? {}
541
+ : { lifecycleRecoveryAttestation: context.lifecycleRecoveryAttestation })
170
542
  };
171
543
  if (prepareInput.client === 'codex') {
172
544
  return prepareCodexOrchestrationDelegation(operation.taskRef, prepareInput, {
173
545
  repoRoot: context.repoRoot,
174
546
  orchestrationOptions: options,
547
+ deferLifecycleRecoveryConsumption: context.source === 'sandbox-executor',
175
548
  ...(context.controllerBinding ? { controllerBinding: context.controllerBinding } : {})
176
549
  });
177
550
  }
@@ -250,13 +623,13 @@ function operationInvalid(message) {
250
623
  }
251
624
  const LIFECYCLE_FLAGS = new Set([
252
625
  '--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
253
- '--override-ticket', '--override-target', '--override-scope', '--dry-run'
626
+ '--stage', '--round', '--artifact', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
254
627
  ]);
255
628
  const FINALIZATION_FLAGS = new Set(['--agent']);
256
629
  const ORCHESTRATION_FLAGS = new Set([
257
630
  '--agent', '--max-steps', '--executor-model', '--executor-reasoning-effort', '--reviewer-model',
258
631
  '--reviewer-reasoning-effort', '--client', '--requested-model', '--requested-reasoning-effort',
259
- '--capability-token', '--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
632
+ '--capability-ref', '--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
260
633
  '--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
261
634
  '--model-fallback-reason', '--reasoning-effort-fallback-reason', '--exit-code', '--after-fingerprint',
262
635
  '--changed-paths', '--code', '--message', '--recoverable', '--git-worktree-root'
@@ -326,8 +699,32 @@ export function parseTaskControlOperation(family, args) {
326
699
  ...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
327
700
  ...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
328
701
  ...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
702
+ ...(value(values, '--stage') ? { stage: value(values, '--stage') } : {}),
703
+ ...(value(values, '--round') ? { round: Number(value(values, '--round')) } : {}),
704
+ ...(value(values, '--artifact') ? { artifact: value(values, '--artifact') } : {}),
329
705
  ...(values['--dry-run'] === true ? { dryRun: true } : {})
330
706
  };
707
+ if (intent === 'recover-started') {
708
+ const recoveryFlags = new Set(['--agent', '--stage', '--round', '--artifact', '--reason']);
709
+ for (const flag of Object.keys(values)) {
710
+ if (!recoveryFlags.has(flag))
711
+ operationInvalid(`option '${flag}' is not supported for recover-started`);
712
+ }
713
+ required(values, ['--stage', '--round', '--artifact', '--reason']);
714
+ const stage = value(values, '--stage');
715
+ if (!['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(stage ?? '')) {
716
+ operationInvalid('--stage must be a supported lifecycle stage');
717
+ }
718
+ const round = Number(value(values, '--round'));
719
+ if (!Number.isSafeInteger(round) || round < 1)
720
+ operationInvalid('--round must be a positive integer');
721
+ if (value(values, '--reason').includes('\n') || value(values, '--reason').includes('\r')) {
722
+ operationInvalid('--reason must be a single line');
723
+ }
724
+ }
725
+ else if (values['--stage'] !== undefined || values['--round'] !== undefined || values['--artifact'] !== undefined) {
726
+ operationInvalid('--stage, --round, and --artifact are only supported for recover-started');
727
+ }
331
728
  return { family, request: input };
332
729
  }
333
730
  const values = parseValues(args, 2, ORCHESTRATION_FLAGS);
@@ -372,7 +769,7 @@ export function parseTaskControlOperation(family, args) {
372
769
  if (parsedIntent === 'prepare') {
373
770
  input.requestedModel = value(values, '--requested-model');
374
771
  input.requestedReasoningEffort = value(values, '--requested-reasoning-effort');
375
- input.capabilityToken = value(values, '--capability-token');
772
+ input.capabilityRef = value(values, '--capability-ref');
376
773
  }
377
774
  if (parsedIntent === 'await-activation') {
378
775
  required(values, ['--stage', '--round', '--artifact', '--role']);
@@ -0,0 +1,140 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { isCompletionEvidence } from './orchestration.js';
3
+ import { TASK_WORKFLOW_OPERATIONS } from './workflow-command.js';
4
+ import { sameRecoveryWarning } from './recovery-warning.js';
5
+ /** Normalize the command result once at the control transport boundary. */
6
+ export function parseControlOutput(output) {
7
+ if (output === null)
8
+ return null;
9
+ try {
10
+ const value = JSON.parse(output);
11
+ if (!value || typeof value !== 'object' || Array.isArray(value))
12
+ return null;
13
+ return value.result && typeof value.result === 'object' && !Array.isArray(value.result) ? value.result : value;
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ export const SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES = Object.freeze([
20
+ 'completed', 'evidence-written', 'publish-authorized'
21
+ ]);
22
+ const LIFECYCLE_INTENTS = ['block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'];
23
+ const ORCHESTRATION_INTENTS = [
24
+ 'begin-or-resume', 'route.read', 'route.clean-completion', 'status', 'prepare', 'dispatch',
25
+ 'await-activation', 'recover-prepared', 'hook-start', 'hook-stop', 'advance', 'pause'
26
+ ];
27
+ export const SANDBOX_CONTROL_RECOVERY_OPERATIONS = Object.freeze([
28
+ ...LIFECYCLE_INTENTS.map((intent) => ({ family: 'task-lifecycle', intent, class: 'lifecycle-mutation' })),
29
+ { family: 'task-finalization', intent: 'complete', class: 'finalization' },
30
+ ...ORCHESTRATION_INTENTS.map((intent) => ({
31
+ family: 'task-orchestration',
32
+ intent,
33
+ class: intent === 'route.clean-completion' ? 'route.clean-completion' : intent === 'route.read' || intent === 'status' ? 'read-only' : 'orchestration'
34
+ })),
35
+ { family: 'task-create', intent: 'create', class: 'task-create' },
36
+ ...TASK_WORKFLOW_OPERATIONS.map((intent) => ({ family: 'task-workflow', intent, class: intent === 'artifact-inspect' || intent === 'decision-next-id' ? 'read-only' : 'workflow' })),
37
+ ...['open', 'close', 'verify'].map((intent) => ({ family: 'codex-controller', intent, class: 'codex-controller' }))
38
+ ]);
39
+ export function digestControlRecoveryIntent(family, intent) {
40
+ return createHash('sha256').update(`${family}\0${intent}`, 'utf8').digest('hex');
41
+ }
42
+ export function findSandboxControlRecoveryOperation(family, intent) {
43
+ return SANDBOX_CONTROL_RECOVERY_OPERATIONS.find((operation) => operation.family === family && operation.intent === intent) ?? null;
44
+ }
45
+ function bindingMatchesResult(binding, result) {
46
+ return result.requestId === binding.requestId
47
+ && result.generation === binding.generation
48
+ && (result.taskId === undefined || result.taskId === binding.taskId)
49
+ && (result.intentDigest === undefined || result.intentDigest === binding.intentDigest);
50
+ }
51
+ function domainEvidenceMatches(operation, result, domain) {
52
+ if (!domain)
53
+ return false;
54
+ if (domain.consistent !== true)
55
+ return false;
56
+ if (operation.class === 'route.clean-completion') {
57
+ const completion = domain.completionEvidence;
58
+ const snapshot = domain.snapshot;
59
+ const lastReviewedCommit = domain.lastReviewedCommit;
60
+ return result.status === 'completed'
61
+ && domain.status === 'completed'
62
+ && domain.pendingDelegation === null
63
+ && isCompletionEvidence(completion)
64
+ && snapshot?.head === completion.head
65
+ && snapshot.headTree === completion.headTree
66
+ && snapshot.worktreeTree === completion.worktreeTree
67
+ && lastReviewedCommit === completion.lastReviewedCommit;
68
+ }
69
+ if (operation.family === 'task-lifecycle' && operation.intent === 'recover-started') {
70
+ const retryRequired = domain.recoveryState === 'retry-required'
71
+ && sameRecoveryWarning(domain.warning, result.warning);
72
+ return result.targetState === 'active'
73
+ && ['applied', 'no-op'].includes(String(result.status))
74
+ && domain.recovery === true
75
+ && domain.targetState === 'active'
76
+ && (domain.recoveryState === 'released' || retryRequired);
77
+ }
78
+ if (operation.class === 'read-only')
79
+ return result.changed === false && domain.snapshotValid === true;
80
+ return true;
81
+ }
82
+ export function classifySandboxControlRecovery(input) {
83
+ const result = input.terminalResult ?? null;
84
+ if (input.evidenceConflict)
85
+ return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_EVIDENCE_CONFLICT' };
86
+ if (input.explicitRejection && !input.startedCommitted) {
87
+ return { outcome: 'not-executed', responseReconstructable: true, reasonCode: 'RECOVERY_EXPLICIT_REJECTION' };
88
+ }
89
+ if (!input.startedCommitted) {
90
+ return { outcome: 'not-executed', responseReconstructable: false, reasonCode: 'RECOVERY_NOT_STARTED' };
91
+ }
92
+ if (!result)
93
+ return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_TERMINAL_RESULT_MISSING' };
94
+ if (!bindingMatchesResult(input.binding, result)) {
95
+ return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_RESULT_BINDING_MISMATCH' };
96
+ }
97
+ if (!SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES.every((phase) => input.criticalPhases?.includes(phase))) {
98
+ return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_CRITICAL_AUDIT_INCOMPLETE' };
99
+ }
100
+ const status = result.status;
101
+ const failed = status === 'failed' || result.error !== null && result.error !== undefined;
102
+ if (failed) {
103
+ if (input.domain && input.domain.consistent === false) {
104
+ return { outcome: 'failure', responseReconstructable: true, reasonCode: 'RECOVERY_TERMINAL_FAILURE' };
105
+ }
106
+ return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_FAILURE_EVIDENCE_INCOMPLETE' };
107
+ }
108
+ if (result.changed === null) {
109
+ return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_RESULT_CHANGE_FLAG_MISSING' };
110
+ }
111
+ if (!domainEvidenceMatches(input.operation, result, input.domain)) {
112
+ if (input.journal?.exists && (input.journal.completedSteps?.length ?? 0) > 0) {
113
+ return { outcome: 'in-progress', responseReconstructable: false, reasonCode: 'RECOVERY_DOMAIN_EVIDENCE_INCOMPLETE' };
114
+ }
115
+ return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_DOMAIN_EVIDENCE_MISSING' };
116
+ }
117
+ if (input.operation.family === 'task-lifecycle' && input.journal?.exists) {
118
+ if (input.journal.failure) {
119
+ return { outcome: 'failure', responseReconstructable: true, reasonCode: 'RECOVERY_LIFECYCLE_JOURNAL_FAILURE' };
120
+ }
121
+ return {
122
+ outcome: input.journal.completedSteps && input.journal.completedSteps.length > 0 ? 'in-progress' : 'unknown',
123
+ responseReconstructable: false,
124
+ reasonCode: 'RECOVERY_LIFECYCLE_JOURNAL_PRESENT'
125
+ };
126
+ }
127
+ return {
128
+ outcome: 'success',
129
+ responseReconstructable: true,
130
+ reasonCode: input.operation.class === 'route.clean-completion' && result.changed === false
131
+ ? 'RECOVERY_ROUTE_COMPLETION_NOOP'
132
+ : input.operation.intent === 'recover-started' && input.domain?.recoveryState === 'retry-required'
133
+ ? 'RECOVERY_RELEASE_RETRY_REQUIRED'
134
+ : 'RECOVERY_TERMINAL_AND_DOMAIN_MATCH'
135
+ };
136
+ }
137
+ export function operationRecoveryBinding(requestId, generation, taskId, family, intent) {
138
+ return { requestId, generation, taskId, intentDigest: digestControlRecoveryIntent(family, intent) };
139
+ }
140
+ //# sourceMappingURL=control-recovery.js.map