@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
@@ -0,0 +1,549 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.js';
4
+ import { managedDelegationRole } from './delegation-receipts.js';
5
+ import { normalizeAgentToken } from '../agent-clients/tokens.js';
6
+ import { artifactName, parseArtifactName } from './artifact-name.js';
7
+ import { locateActivityLog, pairEntries } from './activity-log.js';
8
+ import { captureTaskWriteMetadata, writeTask } from './write.js';
9
+ import { parseTypedTaskFrontmatter } from './frontmatter.js';
10
+ import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
11
+ import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.js';
12
+ import { recoverActivatedOrchestrationDelegationUnderLock, readRun } from './orchestration.js';
13
+ import { isRecoveryWarning, sameRecoveryWarning } from './recovery-warning.js';
14
+ const RECOVERY_NOTE_PREFIX = 'lifecycle-recovery:v1 ';
15
+ const RECOVERY_STAGES = ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'];
16
+ const ACTIONS = {
17
+ analysis: 'Analyze Task',
18
+ 'review-analysis': 'Review Analysis',
19
+ plan: 'Plan Task',
20
+ 'review-plan': 'Review Plan',
21
+ code: 'Code Task',
22
+ 'review-code': 'Review Code'
23
+ };
24
+ const RECOVERY_NOTE_KEYS = [
25
+ 'version', 'taskId', 'stage', 'round', 'artifact', 'startedAgent', 'receiptId', 'childId',
26
+ 'stopRevision', 'consumer', 'consumedAt', 'owner', 'reason'
27
+ ];
28
+ const RECOVERY_RELEASE_RETRY_WARNING = Object.freeze({
29
+ code: 'RECOVERY_RELEASE_RETRY_REQUIRED',
30
+ message: 'recovery receipt and Activity Log are complete but the protected lifecycle claim could not be released',
31
+ action: 'retry recover-started with the same selector and reason'
32
+ });
33
+ function text(value) {
34
+ return typeof value === 'string' && value.length > 0 && value.trim() === value && !/[\r\n]/u.test(value);
35
+ }
36
+ function isReleaseRetryWarning(value) {
37
+ return sameRecoveryWarning(value, RECOVERY_RELEASE_RETRY_WARNING);
38
+ }
39
+ function timestamp(value) {
40
+ return text(value) && Number.isFinite(Date.parse(value));
41
+ }
42
+ function recoveryConsumer(taskId, receiptId) {
43
+ return `lifecycle-recovery:${taskId}:${receiptId}`;
44
+ }
45
+ function actionPattern(stage, round) {
46
+ const escaped = ACTIONS[stage].replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
47
+ return new RegExp(`^${escaped} \\(Round ${round}(?:, (?:fix for review-code(?:-r[2-9]|-r[1-9]\\d+)?.md|decision II-[1-9]\\d*))?\\)$`, 'u');
48
+ }
49
+ function baseStep(step) {
50
+ return step.replace(/\s*\[(?:started|aborted)\]\s*$/u, '');
51
+ }
52
+ function isRecoveryStage(value) {
53
+ return RECOVERY_STAGES.includes(value);
54
+ }
55
+ function parseRecoveryNote(note) {
56
+ if (!note.startsWith(RECOVERY_NOTE_PREFIX))
57
+ return null;
58
+ let value;
59
+ try {
60
+ value = JSON.parse(note.slice(RECOVERY_NOTE_PREFIX.length));
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ if (!value || typeof value !== 'object' || Array.isArray(value))
66
+ return null;
67
+ const record = value;
68
+ if (Object.keys(record).sort().join(',') !== [...RECOVERY_NOTE_KEYS].sort().join(','))
69
+ return null;
70
+ if (record.version !== 1
71
+ || typeof record.taskId !== 'string' || !TASK_ID_RE.test(record.taskId)
72
+ || !isRecoveryStage(record.stage)
73
+ || !Number.isSafeInteger(record.round) || record.round < 1
74
+ || !text(record.artifact)
75
+ || !text(record.startedAgent)
76
+ || !text(record.receiptId)
77
+ || !text(record.childId)
78
+ || !Number.isSafeInteger(record.stopRevision) || record.stopRevision < 1
79
+ || !text(record.consumer)
80
+ || !timestamp(record.consumedAt)
81
+ || record.owner !== 'terminated'
82
+ || !text(record.reason))
83
+ return null;
84
+ const parsedArtifact = parseArtifactName(record.artifact);
85
+ if (!parsedArtifact || parsedArtifact.family !== record.stage || parsedArtifact.round !== record.round)
86
+ return null;
87
+ if (record.consumer !== recoveryConsumer(record.taskId, record.receiptId))
88
+ return null;
89
+ return record;
90
+ }
91
+ function renderRecoveryNote(note) {
92
+ return `${RECOVERY_NOTE_PREFIX}${JSON.stringify(note)}`;
93
+ }
94
+ function result(request, status, extra = {}) {
95
+ return {
96
+ status,
97
+ changed: status === 'applied',
98
+ targetState: 'active',
99
+ requestRef: request.taskRef,
100
+ intent: request.intent,
101
+ taskId: null,
102
+ stage: request.stage,
103
+ round: request.round,
104
+ artifact: request.artifact,
105
+ receiptId: null,
106
+ childId: null,
107
+ warning: null,
108
+ error: null,
109
+ ...extra
110
+ };
111
+ }
112
+ function failure(request, status, code, message, extra = {}) {
113
+ return result(request, status, { error: { code, message }, ...extra });
114
+ }
115
+ function recoveryFailure(request, status, code, message) {
116
+ return failure(request, status, code, message);
117
+ }
118
+ function normalizeRequest(request) {
119
+ if (!request || typeof request !== 'object'
120
+ || request.intent !== 'recover-started'
121
+ || typeof request.taskRef !== 'string' || !request.taskRef.trim()
122
+ || typeof request.agent !== 'string'
123
+ || !isRecoveryStage(request.stage)
124
+ || !Number.isSafeInteger(request.round) || request.round < 1
125
+ || typeof request.artifact !== 'string'
126
+ || typeof request.reason !== 'string' || !request.reason.trim() || /[\r\n]/u.test(request.reason))
127
+ return { code: 'RECOVERY_PAYLOAD_INVALID', message: 'recover-started requires a task, agent, stage, round, artifact, and single-line reason' };
128
+ const agent = normalizeAgentToken(request.agent);
129
+ if (!agent)
130
+ return { code: 'RECOVERY_PAYLOAD_INVALID', message: 'recovery agent is not a recognized agent token' };
131
+ const expectedArtifact = artifactName(request.stage, request.round);
132
+ if (request.artifact !== expectedArtifact) {
133
+ return { code: 'RECOVERY_PAYLOAD_INVALID', message: `artifact must be ${expectedArtifact} for ${request.stage} round ${request.round}` };
134
+ }
135
+ return { ...request, agent, reason: request.reason.trim() };
136
+ }
137
+ function targetRows(sectionEntries, stage, round) {
138
+ const pattern = actionPattern(stage, round);
139
+ const rows = pairEntries([...sectionEntries]).filter((row) => pattern.test(row.step));
140
+ const recoveryEntries = sectionEntries.filter((entry) => entry.step.endsWith(' [aborted]') && pattern.test(baseStep(entry.step)));
141
+ return { rows, recoveryEntries };
142
+ }
143
+ function validateStopRecord(record, receipt, consumer) {
144
+ const start = record.state.startEvidence;
145
+ const stop = record.state.stopEvidence;
146
+ const provenance = receipt.lifecycleProvenance;
147
+ const host = receipt.hostEvidence;
148
+ if (receipt.client !== 'codex'
149
+ || !provenance
150
+ || host?.kind !== 'codex-lifecycle-v2'
151
+ || !start
152
+ || !stop
153
+ || record.state.status !== 'stop-ready'
154
+ || stop.terminalStatus !== 'completed'
155
+ || stop.hookStopObserved !== true
156
+ || start.childThreadId !== receipt.childId
157
+ || start.parentThreadId !== receipt.parentId
158
+ || managedDelegationRole(start.nativeAgent) !== receipt.role
159
+ || start.hookDefinitionHash !== provenance.hookDefinitionHash
160
+ || host.hookDefinitionHash !== provenance.hookDefinitionHash
161
+ || host.capabilitySessionId !== provenance.capabilitySessionId
162
+ || host.capabilityTurnId !== provenance.capabilityTurnId
163
+ || host.capabilityToolUseId !== provenance.capabilityToolUseId
164
+ || host.spawnToolUseId !== start.spawnToolUseId
165
+ || !host.spawnObservedAt
166
+ || !record.spawnObservedAt
167
+ || host.spawnObservedAt !== record.spawnObservedAt
168
+ || host.controllerInstanceDigest !== provenance.controllerInstanceDigest
169
+ || host.controlGeneration !== provenance.controlGeneration
170
+ || typeof host.controllerInstanceDigest !== 'string'
171
+ || typeof host.controlGeneration !== 'string'
172
+ || host.startRevision < 1
173
+ || record.revision <= host.startRevision)
174
+ return { ok: false, code: 'RECOVERY_STOP_EVIDENCE_INVALID', message: 'Codex stop evidence does not match the managed activated receipt' };
175
+ if (record.state.child?.childThreadId !== receipt.childId || record.state.stop?.childThreadId !== receipt.childId) {
176
+ return { ok: false, code: 'RECOVERY_STOP_EVIDENCE_INVALID', message: 'Codex stop evidence child identity is inconsistent' };
177
+ }
178
+ if (record.consumer !== null && record.consumer !== consumer) {
179
+ return { ok: false, code: 'RECOVERY_CONSUMER_CONFLICT', message: `Codex lifecycle evidence was consumed by '${record.consumer}'` };
180
+ }
181
+ if (record.consumer === null)
182
+ return { ok: true, stopRevision: record.revision, consumedAt: '' };
183
+ if (receipt.status === 'activated' && record.consumer === consumer && record.consumedAt) {
184
+ return { ok: true, stopRevision: record.revision, consumedAt: record.consumedAt };
185
+ }
186
+ if (record.consumer !== consumer
187
+ || !record.consumedAt
188
+ || receipt.hostEvidence?.stopRevision !== record.revision
189
+ || receipt.hostEvidence.consumer !== consumer
190
+ || receipt.hostEvidence.consumedAt !== record.consumedAt)
191
+ return { ok: false, code: 'RECOVERY_REFERENCE_CONFLICT', message: 'recovery receipt references do not match the protected lifecycle claim' };
192
+ return { ok: true, stopRevision: record.revision, consumedAt: record.consumedAt };
193
+ }
194
+ function readLifecycleStore(options, repoRoot) {
195
+ if (options.lifecycleStore)
196
+ return options.lifecycleStore;
197
+ return createCodexLifecycleStore({
198
+ root: resolveAgentRuntimeStoreRoot({ repoRoot, store: 'lifecycle' }),
199
+ cliVersion: 'recovery',
200
+ now: options.now
201
+ });
202
+ }
203
+ function readStoredEvidence(store, childId) {
204
+ try {
205
+ return store.read(childId);
206
+ }
207
+ catch (error) {
208
+ const message = error instanceof Error ? error.message : String(error);
209
+ if (message.includes('was not found uniquely'))
210
+ return { missing: true };
211
+ return { error: error instanceof Error ? error : new Error(message) };
212
+ }
213
+ }
214
+ function taskNote(taskId, request, receipt, stopRevision, consumer, consumedAt) {
215
+ return {
216
+ version: 1,
217
+ taskId,
218
+ stage: request.stage,
219
+ round: request.round,
220
+ artifact: request.artifact,
221
+ startedAgent: request.agent,
222
+ receiptId: receipt.id,
223
+ childId: receipt.childId,
224
+ stopRevision,
225
+ consumer,
226
+ consumedAt,
227
+ owner: 'terminated',
228
+ reason: request.reason
229
+ };
230
+ }
231
+ function releaseResult(request, taskId, receipt, released, previousChange) {
232
+ if (released)
233
+ return result(request, 'applied', { taskId, receiptId: receipt.id, childId: receipt.childId });
234
+ return result(request, 'applied', {
235
+ changed: previousChange,
236
+ taskId,
237
+ receiptId: receipt.id,
238
+ childId: receipt.childId,
239
+ warning: RECOVERY_RELEASE_RETRY_WARNING
240
+ });
241
+ }
242
+ function isPostActivationAbortedReceipt(receipt, taskId, request) {
243
+ return receipt.status === 'aborted'
244
+ && receipt.activatedAt !== null
245
+ && receipt.agent === null
246
+ && receipt.taskId === taskId
247
+ && receipt.stage === request.stage
248
+ && receipt.round === request.round
249
+ && receipt.artifact === request.artifact;
250
+ }
251
+ function matchingPostActivationAbortedReceipts(run, taskId, request) {
252
+ return run.receipts.filter((receipt) => isPostActivationAbortedReceipt(receipt, taskId, request));
253
+ }
254
+ function sameRecoveryNote(left, right) {
255
+ return left.version === right.version
256
+ && left.taskId === right.taskId
257
+ && left.stage === right.stage
258
+ && left.round === right.round
259
+ && left.artifact === right.artifact
260
+ && left.startedAgent === right.startedAgent
261
+ && left.receiptId === right.receiptId
262
+ && left.childId === right.childId
263
+ && left.stopRevision === right.stopRevision
264
+ && left.consumer === right.consumer
265
+ && left.consumedAt === right.consumedAt
266
+ && left.owner === right.owner
267
+ && left.reason === right.reason;
268
+ }
269
+ function recoveryNoteMatchesRequest(note, taskId, request) {
270
+ return note.taskId === taskId
271
+ && note.stage === request.stage
272
+ && note.round === request.round
273
+ && note.artifact === request.artifact
274
+ && note.startedAgent === request.agent
275
+ && note.reason === request.reason;
276
+ }
277
+ function recoveryReferencesMatch(receipt, note, consumer) {
278
+ return receipt.childId === note.childId
279
+ && note.consumer === consumer
280
+ && receipt.hostEvidence?.stopRevision === note.stopRevision
281
+ && receipt.hostEvidence.consumer === consumer
282
+ && receipt.hostEvidence.consumedAt === note.consumedAt;
283
+ }
284
+ function recoveryRunIsStable(run) {
285
+ return run !== null && run.status === 'running' && run.pendingDelegation === null;
286
+ }
287
+ function recoveryTerminalFailureStatus(code) {
288
+ switch (code) {
289
+ case 'RECOVERY_LOG_CONFLICT':
290
+ case 'RECOVERY_NOTE_INVALID':
291
+ case 'RECOVERY_ORCHESTRATION_INVALID':
292
+ case 'RECOVERY_RECEIPT_INVALID':
293
+ case 'RECOVERY_REFERENCE_CONFLICT':
294
+ case 'RECOVERY_CONSUMER_CONFLICT':
295
+ return 'conflict';
296
+ default:
297
+ return 'owner-unknown';
298
+ }
299
+ }
300
+ function readRecoveryTerminalFacts(section, taskId, request, run, store) {
301
+ const targets = targetRows(section.entries, request.stage, request.round);
302
+ const paired = targets.rows.filter((row) => row.started !== '' && row.done !== '');
303
+ if (targets.recoveryEntries.length !== 1 || paired.length !== 1
304
+ || paired[0].done !== targets.recoveryEntries[0].time) {
305
+ return { ok: false, code: 'RECOVERY_LOG_CONFLICT', message: 'recovery Activity Log does not contain one completed started/aborted pair' };
306
+ }
307
+ const note = parseRecoveryNote(targets.recoveryEntries[0].note);
308
+ if (!note || !recoveryNoteMatchesRequest(note, taskId, request)) {
309
+ return { ok: false, code: 'RECOVERY_NOTE_INVALID', message: 'recovery Activity Log note does not match the selector' };
310
+ }
311
+ if (!recoveryRunIsStable(run)) {
312
+ return { ok: false, code: 'RECOVERY_ORCHESTRATION_INVALID', message: 'completed recovery state must be running with no pending delegation' };
313
+ }
314
+ const receipts = matchingPostActivationAbortedReceipts(run, taskId, request);
315
+ if (receipts.length !== 1 || receipts[0].id !== note.receiptId) {
316
+ return { ok: false, code: 'RECOVERY_RECEIPT_INVALID', message: 'there is not one matching post-activation aborted receipt' };
317
+ }
318
+ const receipt = receipts[0];
319
+ const consumer = recoveryConsumer(taskId, receipt.id);
320
+ if (!recoveryReferencesMatch(receipt, note, consumer)) {
321
+ return { ok: false, code: 'RECOVERY_REFERENCE_CONFLICT', message: 'recovery receipt and Activity Log references do not match' };
322
+ }
323
+ const stored = readStoredEvidence(store, note.childId);
324
+ if ('error' in stored)
325
+ return { ok: false, code: 'RECOVERY_STORE_UNKNOWN', message: stored.error.message };
326
+ if ('missing' in stored)
327
+ return { ok: true, facts: { note, receipt, consumer, stored: null } };
328
+ const evidence = validateStopRecord(stored, receipt, consumer);
329
+ if (!evidence.ok)
330
+ return evidence;
331
+ if (stored.consumer !== consumer || !stored.consumedAt
332
+ || evidence.stopRevision !== note.stopRevision || evidence.consumedAt !== note.consumedAt) {
333
+ return { ok: false, code: 'RECOVERY_REFERENCE_CONFLICT', message: 'complete recovery facts do not reference the protected recovery claim' };
334
+ }
335
+ return { ok: true, facts: { note, receipt, consumer, stored } };
336
+ }
337
+ function verifyRecoveryCommit(taskMdPath, taskDir, taskId, request, note, options) {
338
+ try {
339
+ const content = fs.readFileSync(taskMdPath, 'utf8');
340
+ parseTypedTaskFrontmatter(content);
341
+ const section = locateActivityLog(content);
342
+ if (!section)
343
+ return { ok: false, message: 'recovery Activity Log cannot be reread uniquely' };
344
+ const run = readRun(taskDir, options.orchestration);
345
+ const store = readLifecycleStore(options, options.repoRoot ?? path.resolve(taskDir, '../../../..'));
346
+ const facts = readRecoveryTerminalFacts(section, taskId, request, run, store);
347
+ if (!facts.ok)
348
+ return { ok: false, message: facts.message };
349
+ if (!sameRecoveryNote(facts.facts.note, note)) {
350
+ return { ok: false, message: 'recovery Activity Log note changed during commit verification' };
351
+ }
352
+ if (!facts.facts.stored)
353
+ return { ok: false, message: 'protected lifecycle claim disappeared before release' };
354
+ return { ok: true, receipt: facts.facts.receipt };
355
+ }
356
+ catch (error) {
357
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
358
+ }
359
+ }
360
+ function recoveryDomainFailure() {
361
+ return { consistent: false, recovery: true, targetState: 'active' };
362
+ }
363
+ export function readLifecycleRecoveryDomainEvidence(repoRoot, requestInput, terminalResult, options = {}) {
364
+ const terminalStateValid = terminalResult.status === 'no-op'
365
+ ? terminalResult.changed === false
366
+ : terminalResult.status === 'applied'
367
+ && (terminalResult.changed === true || (terminalResult.changed === false && isReleaseRetryWarning(terminalResult.warning)));
368
+ const normalized = normalizeRequest(requestInput);
369
+ if ('code' in normalized || terminalResult.targetState !== 'active'
370
+ || !terminalStateValid)
371
+ return recoveryDomainFailure();
372
+ const request = normalized;
373
+ const resolved = resolveTaskRef(request.taskRef, { repoRoot });
374
+ if (!resolved.ok)
375
+ return recoveryDomainFailure();
376
+ try {
377
+ const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
378
+ parseTypedTaskFrontmatter(content);
379
+ const section = locateActivityLog(content);
380
+ if (!section)
381
+ return recoveryDomainFailure();
382
+ const run = readRun(resolved.taskDir);
383
+ const store = readLifecycleStore(options, repoRoot);
384
+ const facts = readRecoveryTerminalFacts(section, resolved.taskId, request, run, store);
385
+ if (!facts.ok)
386
+ return recoveryDomainFailure();
387
+ if (!facts.facts.stored)
388
+ return { consistent: true, recovery: true, targetState: 'active', recoveryState: 'released' };
389
+ if (!isReleaseRetryWarning(terminalResult.warning))
390
+ return recoveryDomainFailure();
391
+ return {
392
+ consistent: true,
393
+ recovery: true,
394
+ targetState: 'active',
395
+ recoveryState: 'retry-required',
396
+ warning: terminalResult.warning
397
+ };
398
+ }
399
+ catch {
400
+ return recoveryDomainFailure();
401
+ }
402
+ }
403
+ function recoverStartedLifecycleUnderLock(requestInput, options = {}) {
404
+ const normalized = normalizeRequest(requestInput);
405
+ if ('code' in normalized)
406
+ return failure(requestInput, 'conflict', normalized.code, normalized.message);
407
+ const request = normalized;
408
+ const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
409
+ if (!resolved.ok)
410
+ return failure(request, 'conflict', resolved.code, resolved.message);
411
+ const taskId = resolved.taskId;
412
+ let content;
413
+ try {
414
+ content = fs.readFileSync(resolved.taskMdPath, 'utf8');
415
+ }
416
+ catch (error) {
417
+ return failure(request, 'owner-unknown', 'RECOVERY_TASK_READ_FAILED', String(error), { taskId });
418
+ }
419
+ try {
420
+ parseTypedTaskFrontmatter(content);
421
+ }
422
+ catch (error) {
423
+ return failure(request, 'conflict', 'RECOVERY_TASK_INVALID', error instanceof Error ? error.message : String(error), { taskId });
424
+ }
425
+ const section = locateActivityLog(content);
426
+ if (!section)
427
+ return failure(request, 'conflict', 'RECOVERY_LOG_INVALID', 'task has no unique Activity Log section', { taskId });
428
+ const targets = targetRows(section.entries, request.stage, request.round);
429
+ const open = targets.rows.filter((row) => row.started !== '' && row.done === '');
430
+ if (open.length > 1 || targets.recoveryEntries.length > 1) {
431
+ return failure(request, 'conflict', 'RECOVERY_SELECTOR_AMBIGUOUS', 'recovery selector does not identify one lifecycle attempt', { taskId });
432
+ }
433
+ let run;
434
+ try {
435
+ run = readRun(resolved.taskDir, options.orchestration);
436
+ }
437
+ catch (error) {
438
+ return failure(request, 'owner-unknown', 'RECOVERY_ORCHESTRATION_UNKNOWN', String(error), { taskId });
439
+ }
440
+ if (!run)
441
+ return failure(request, 'owner-unknown', 'RECOVERY_ORCHESTRATION_MISSING', 'no orchestration run exists for the open lifecycle execution', { taskId });
442
+ if (open.length === 0) {
443
+ if (targets.recoveryEntries.length !== 1) {
444
+ return failure(request, 'conflict', 'RECOVERY_TERMINAL_MISSING', 'there is no matching open or structured recovery lifecycle row', { taskId });
445
+ }
446
+ const store = readLifecycleStore(options, resolved.repoRoot);
447
+ const facts = readRecoveryTerminalFacts(section, taskId, request, run, store);
448
+ if (!facts.ok) {
449
+ return failure(request, recoveryTerminalFailureStatus(facts.code), facts.code, facts.message, { taskId });
450
+ }
451
+ const { note, receipt, consumer, stored } = facts.facts;
452
+ if (!stored)
453
+ return result(request, 'no-op', { taskId, receiptId: receipt.id, childId: receipt.childId });
454
+ let released = false;
455
+ try {
456
+ released = (options.releaseRecovery ?? store.releaseRecovery)(note.childId, consumer);
457
+ }
458
+ catch (error) {
459
+ return releaseResult(request, taskId, receipt, false, false);
460
+ }
461
+ return releaseResult(request, taskId, receipt, released, false);
462
+ }
463
+ const started = open[0];
464
+ if (normalizeAgentToken(started.agent) !== request.agent || started.note !== 'started') {
465
+ return failure(request, 'conflict', 'RECOVERY_SELECTOR_MISMATCH', 'open started lifecycle row does not match the recovery selector', { taskId });
466
+ }
467
+ if (targets.recoveryEntries.length > 0) {
468
+ return failure(request, 'conflict', 'RECOVERY_LOG_CONFLICT', 'open lifecycle execution already has a recovery terminal row', { taskId });
469
+ }
470
+ const pending = run.pendingDelegation;
471
+ const recovered = run.receipts.filter((candidate) => isPostActivationAbortedReceipt(candidate, taskId, request));
472
+ if (pending && recovered.length > 0) {
473
+ return failure(request, 'conflict', 'RECOVERY_RECEIPT_INVALID', 'open lifecycle execution has both a pending and an aborted matching delegation', { taskId });
474
+ }
475
+ const receipt = pending ?? (recovered.length === 1 ? recovered[0] : null);
476
+ if (!receipt || (pending && (receipt.status !== 'activated' || receipt.client !== 'codex')) || (!pending && receipt.status !== 'aborted')) {
477
+ return failure(request, 'owner-unknown', 'RECOVERY_DELEGATION_UNAVAILABLE', 'open lifecycle execution is not backed by one matching activated Codex delegation', { taskId });
478
+ }
479
+ if (normalizeAgentToken(started.agent) !== normalizeAgentToken(receipt.client)) {
480
+ return failure(request, 'conflict', 'RECOVERY_SELECTOR_MISMATCH', 'started agent does not match the delegation client', { taskId, receiptId: receipt.id, childId: receipt.childId });
481
+ }
482
+ const consumer = recoveryConsumer(taskId, receipt.id);
483
+ const store = readLifecycleStore(options, resolved.repoRoot);
484
+ const stored = readStoredEvidence(store, receipt.childId ?? '');
485
+ if ('error' in stored)
486
+ return failure(request, 'owner-unknown', 'RECOVERY_STORE_UNKNOWN', stored.error.message, { taskId, receiptId: receipt.id, childId: receipt.childId });
487
+ if ('missing' in stored)
488
+ return failure(request, 'owner-unknown', 'RECOVERY_STOP_EVIDENCE_MISSING', 'matching Codex lifecycle stop evidence is missing', { taskId, receiptId: receipt.id, childId: receipt.childId });
489
+ const evidence = validateStopRecord(stored, receipt, consumer);
490
+ if (!evidence.ok)
491
+ return failure(request, evidence.code === 'RECOVERY_CONSUMER_CONFLICT' ? 'conflict' : 'owner-unknown', evidence.code, evidence.message, { taskId, receiptId: receipt.id, childId: receipt.childId });
492
+ let claimed = stored;
493
+ if (stored.consumer === null) {
494
+ try {
495
+ claimed = store.claimRecovery(receipt.childId, taskId, receipt.id, receipt.hostEvidence?.hookDefinitionHash);
496
+ }
497
+ catch (error) {
498
+ return failure(request, 'owner-unknown', 'RECOVERY_CLAIM_FAILED', error instanceof Error ? error.message : String(error), { taskId, receiptId: receipt.id, childId: receipt.childId });
499
+ }
500
+ }
501
+ const stopRevision = claimed.revision;
502
+ const consumedAt = claimed.consumedAt;
503
+ if (!consumedAt)
504
+ return failure(request, 'owner-unknown', 'RECOVERY_CLAIM_INVALID', 'protected lifecycle claim has no consumed timestamp', { taskId, receiptId: receipt.id, childId: receipt.childId });
505
+ if (receipt.status === 'activated') {
506
+ const recoveredRun = recoverActivatedOrchestrationDelegationUnderLock(taskId, {
507
+ receiptId: receipt.id,
508
+ stage: request.stage,
509
+ round: request.round,
510
+ artifact: request.artifact,
511
+ startedAgent: request.agent,
512
+ childId: receipt.childId,
513
+ stopRevision,
514
+ consumer,
515
+ consumedAt
516
+ }, { ...options.orchestration, repoRoot: resolved.repoRoot, now: options.now });
517
+ if (recoveredRun.status === 'failed' || !recoveredRun.run) {
518
+ return failure(request, 'owner-unknown', recoveredRun.error?.code ?? 'RECOVERY_ORCHESTRATION_FAILED', recoveredRun.error?.message ?? 'orchestration recovery failed', { taskId, receiptId: receipt.id, childId: receipt.childId });
519
+ }
520
+ }
521
+ const note = taskNote(taskId, request, receipt, stopRevision, consumer, consumedAt);
522
+ const metadata = captureTaskWriteMetadata();
523
+ const updatedBody = `${section.body ? `${section.body}\n` : ''}- ${metadata.timestamp} — **${baseStep(started.step)} [aborted]** by ${request.agent} — ${renderRecoveryNote(note)}`;
524
+ const written = (options.writeTask ?? writeTask)({
525
+ taskRef: taskId,
526
+ expectedState: 'active',
527
+ mutations: [{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body: updatedBody }]
528
+ }, { repoRoot: resolved.repoRoot, metadataProvider: () => metadata });
529
+ if (written.status === 'failed') {
530
+ return failure(request, 'owner-unknown', 'RECOVERY_LOG_WRITE_FAILED', written.error.message, { taskId, receiptId: receipt.id, childId: receipt.childId });
531
+ }
532
+ const verified = (options.verifyRecoveryCommit ?? verifyRecoveryCommit)(resolved.taskMdPath, resolved.taskDir, taskId, request, note, options);
533
+ if (!verified.ok) {
534
+ return failure(request, 'owner-unknown', 'RECOVERY_COMMIT_VERIFY_FAILED', verified.message, { taskId, receiptId: receipt.id, childId: receipt.childId });
535
+ }
536
+ let released = false;
537
+ try {
538
+ released = (options.releaseRecovery ?? store.releaseRecovery)(receipt.childId, consumer);
539
+ }
540
+ catch {
541
+ released = false;
542
+ }
543
+ if (!released) {
544
+ return releaseResult(request, taskId, receipt, false, true);
545
+ }
546
+ return result(request, 'applied', { taskId, receiptId: receipt.id, childId: receipt.childId });
547
+ }
548
+ export { RECOVERY_NOTE_PREFIX, parseRecoveryNote, recoveryFailure, recoveryConsumer, recoverStartedLifecycleUnderLock, renderRecoveryNote };
549
+ //# sourceMappingURL=lifecycle-recovery.js.map
@@ -2,15 +2,18 @@ import { createHash } from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { appendActivityEntry, locateActivityLog } from './activity-log.js';
5
- import { artifactFamilyCatalog, inspectArtifactDirectory, parseArtifactName } from './artifact-lifecycle.js';
5
+ import { artifactFamilyCatalog, inspectArtifactDirectory } from './artifact-lifecycle.js';
6
+ import { parseArtifactName } from './artifact-name.js';
6
7
  import { parseTypedTaskFrontmatter } from './frontmatter.js';
7
8
  import { locateHotTaskDirs, resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
8
9
  import { configuredShortIdLength, executeShortIdCommand, loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.js';
9
10
  import { captureTaskWriteMetadata, writeTask } from './write.js';
10
11
  import { validateCurrentTaskContract } from './current-contract.js';
11
12
  import { isValidAgentInfraVersion } from '../version.js';
13
+ import { taskIssueIdentity } from '../platform/task-identities.js';
14
+ import { resourceIdentityEquals } from '../platform/resource-identity.js';
12
15
  const lifecycleIntentCatalog = [
13
- 'block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore'
16
+ 'block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'
14
17
  ];
15
18
  const lifecycleFailureCatalog = [
16
19
  'LIFECYCLE_DIRECTORY_RENAME_FAILED',
@@ -147,6 +150,8 @@ function actionAndNote(request, restoredFiles = 0) {
147
150
  return { action: 'Close Codescan', note: `Code Scanning alert #${request.alertNumber} dismissed: ${request.reason}` };
148
151
  if (request.intent === 'close-dependabot')
149
152
  return { action: 'Close Dependabot', note: `Dependabot alert #${request.alertNumber} dismissed: ${request.reason}` };
153
+ if (request.intent === 'recover-started')
154
+ return { action: 'Recover Started', note: request.reason };
150
155
  return { action: 'Restore Task', note: `Restored ${restoredFiles} files from Issue #${request.issueNumber}` };
151
156
  }
152
157
  function matchingCompletion(content, request, restoredFiles = 0) {
@@ -196,8 +201,10 @@ function validateRestoreStaging(request, repoRoot, taskId) {
196
201
  return { code: 'LIFECYCLE_STAGING_INVALID', message: 'restore staging has no task.md' };
197
202
  try {
198
203
  const frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(taskFile, 'utf8'));
199
- if (frontmatter.id !== taskId || Number(frontmatter.issue_number) !== request.issueNumber || frontmatter.current_step === 'completed') {
200
- return { code: 'LIFECYCLE_STAGING_IDENTITY_INVALID', message: 'restore task identity, issue, or current_step is invalid' };
204
+ const issueIdentity = taskIssueIdentity(frontmatter);
205
+ const requestedIssueIdentity = { kind: 'number', value: request.issueNumber };
206
+ if (frontmatter.id !== taskId || !issueIdentity || !resourceIdentityEquals(issueIdentity, requestedIssueIdentity) || frontmatter.current_step === 'completed') {
207
+ return { code: 'LIFECYCLE_STAGING_IDENTITY_INVALID', message: 'restore task identity, platform issue identity, or current_step is invalid' };
201
208
  }
202
209
  }
203
210
  catch (error) {
@@ -296,6 +303,23 @@ function parseJournal(content) {
296
303
  }
297
304
  return value;
298
305
  }
306
+ /** Recovery reads the same journal contract as the lifecycle writer. */
307
+ export function readLifecycleJournalEvidence(repoRoot, taskId) {
308
+ const journalPath = locateHotTaskDirs(repoRoot, taskId)
309
+ .map((entry) => path.join(entry.taskDir, '.task-lifecycle.json'))
310
+ .find((candidate) => fs.existsSync(candidate));
311
+ if (!journalPath)
312
+ return { exists: false, completedSteps: [], failure: null };
313
+ try {
314
+ const journal = parseJournal(fs.readFileSync(journalPath, 'utf8'));
315
+ if (journal.taskId !== taskId)
316
+ throw new Error('journal task identity mismatch');
317
+ return { exists: true, completedSteps: journal.completedSteps, failure: journal.failure?.code ?? null };
318
+ }
319
+ catch {
320
+ return { exists: true, completedSteps: [], failure: 'SANDBOX_CONTROL_LIFECYCLE_JOURNAL_INVALID' };
321
+ }
322
+ }
299
323
  function directoryManifest(root) {
300
324
  const entries = [];
301
325
  const visit = (current, relative = '') => {
@@ -356,6 +380,9 @@ function removeCopiedSource(source, target) {
356
380
  fs.rmSync(source, { recursive: true, force: false });
357
381
  }
358
382
  function applyTaskLifecycle(requestInput, options = {}) {
383
+ if (requestInput.intent === 'recover-started') {
384
+ return failed(requestInput, { code: 'LIFECYCLE_RECOVERY_REQUIRES_AUTHORITY', message: 'recover-started must be dispatched through task control authority' });
385
+ }
359
386
  const normalized = normalizedRequest(requestInput);
360
387
  if ('code' in normalized)
361
388
  return failed(requestInput, normalized);