@fitlab-ai/agent-infra 0.11.15 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/bin/cli.ts +1 -1
  2. package/dist/bin/cli.js +1 -1
  3. package/dist/bin/internal-cli.js +6 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +6 -6
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +8 -18
  6. package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -1
  7. package/dist/lib/decide.js +2 -7
  8. package/dist/lib/internal/cli-route-inventory.js +3 -0
  9. package/dist/lib/internal/manual-validation.js +19 -33
  10. package/dist/lib/internal/platform-comment.js +59 -9
  11. package/dist/lib/internal/task-activity.js +1 -12
  12. package/dist/lib/internal/task-event.js +1 -12
  13. package/dist/lib/internal/task-ledger.js +1 -12
  14. package/dist/lib/internal/task-operation-registry.js +2 -1
  15. package/dist/lib/internal/task-qualification.js +6 -15
  16. package/dist/lib/internal/task-review.js +3 -16
  17. package/dist/lib/internal/task-short-id.js +2 -1
  18. package/dist/lib/internal/task-verify.js +1 -11
  19. package/dist/lib/internal/task-warning.js +1 -12
  20. package/dist/lib/internal/task-workspace.js +43 -0
  21. package/dist/lib/merge.js +4 -3
  22. package/dist/lib/platform/issue-comments.js +373 -267
  23. package/dist/lib/platform/issue-summary-content.js +112 -0
  24. package/dist/lib/platform/pr-change-report.js +175 -26
  25. package/dist/lib/platform/pr-summary-content.js +54 -0
  26. package/dist/lib/platform/pr-summary.js +321 -296
  27. package/dist/lib/platform/pull-requests.js +3 -15
  28. package/dist/lib/platform/summary-content-pipeline.js +18 -0
  29. package/dist/lib/pr-review/evidence-grading.js +3 -2
  30. package/dist/lib/process-data/index.js +5 -4
  31. package/dist/lib/process-data/sources.js +6 -0
  32. package/dist/lib/run/index.js +3 -2
  33. package/dist/lib/sandbox/commands/create.js +5 -1
  34. package/dist/lib/sandbox/commands/enter.js +1 -1
  35. package/dist/lib/sandbox/commands/list-running.js +2 -1
  36. package/dist/lib/sandbox/control/client.js +8 -13
  37. package/dist/lib/sandbox/control/completed-reentry.js +2 -1
  38. package/dist/lib/sandbox/control/task-view.js +4 -2
  39. package/dist/lib/sandbox/mounts.js +4 -4
  40. package/dist/lib/sandbox/recovery.js +26 -16
  41. package/dist/lib/sandbox/removal.js +62 -12
  42. package/dist/lib/sandbox/task-resolver.js +9 -6
  43. package/dist/lib/sandbox/workspace-identity.js +6 -5
  44. package/dist/lib/sandbox/workspace-view.js +29 -22
  45. package/dist/lib/task/activity-intent.js +2 -12
  46. package/dist/lib/task/artifact-lifecycle.js +17 -11
  47. package/dist/lib/task/artifact-operations.js +41 -49
  48. package/dist/lib/task/capabilities.js +2 -2
  49. package/dist/lib/task/commands/ls.js +4 -3
  50. package/dist/lib/task/commands/status.js +1 -1
  51. package/dist/lib/task/commit-operation.js +6 -6
  52. package/dist/lib/task/control-authority.js +32 -32
  53. package/dist/lib/task/create-service.js +2 -4
  54. package/dist/lib/task/create.js +12 -65
  55. package/dist/lib/task/delegation-receipts.js +1 -1
  56. package/dist/lib/task/delivery.js +2 -8
  57. package/dist/lib/task/events.js +5 -31
  58. package/dist/lib/task/finalization.js +33 -29
  59. package/dist/lib/task/lifecycle.js +9 -48
  60. package/dist/lib/task/local-artifact-finalization.js +1 -2
  61. package/dist/lib/task/orchestration.js +24 -57
  62. package/dist/lib/task/platform-operation-recovery.js +87 -88
  63. package/dist/lib/task/resolve-ref.js +17 -12
  64. package/dist/lib/task/review-finalization.js +2 -35
  65. package/dist/lib/task/review-identity.js +3 -1
  66. package/dist/lib/task/short-id.js +10 -8
  67. package/dist/lib/task/verification-engine.js +19 -58
  68. package/dist/lib/task/workspace-root.js +59 -0
  69. package/dist/lib/task/workspace-snapshot.js +4 -3
  70. package/dist/lifecycle-build-manifest.json +3 -3
  71. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +6 -6
  72. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +8 -34
  73. package/lib/agent-clients/adapters/codex-orchestration.ts +1 -1
  74. package/lib/decide.ts +2 -7
  75. package/lib/internal/cli-route-inventory.ts +2 -0
  76. package/lib/internal/manual-validation.ts +20 -37
  77. package/lib/internal/platform-comment.ts +49 -6
  78. package/lib/internal/task-activity.ts +1 -10
  79. package/lib/internal/task-event.ts +1 -10
  80. package/lib/internal/task-ledger.ts +1 -10
  81. package/lib/internal/task-operation-registry.ts +2 -1
  82. package/lib/internal/task-qualification.ts +5 -11
  83. package/lib/internal/task-review.ts +3 -14
  84. package/lib/internal/task-short-id.ts +2 -1
  85. package/lib/internal/task-verify.ts +1 -9
  86. package/lib/internal/task-warning.ts +1 -10
  87. package/lib/internal/task-workspace.ts +39 -0
  88. package/lib/merge.ts +4 -3
  89. package/lib/platform/issue-comments.ts +478 -280
  90. package/lib/platform/issue-summary-content.ts +132 -0
  91. package/lib/platform/pr-change-report.ts +165 -20
  92. package/lib/platform/pr-summary-content.ts +60 -0
  93. package/lib/platform/pr-summary.ts +367 -269
  94. package/lib/platform/pull-requests.ts +3 -24
  95. package/lib/platform/summary-content-pipeline.ts +23 -0
  96. package/lib/pr-review/evidence-grading.ts +3 -2
  97. package/lib/process-data/index.ts +4 -3
  98. package/lib/process-data/sources.ts +6 -0
  99. package/lib/run/index.ts +3 -2
  100. package/lib/sandbox/commands/create.ts +5 -1
  101. package/lib/sandbox/commands/enter.ts +1 -1
  102. package/lib/sandbox/commands/list-running.ts +2 -1
  103. package/lib/sandbox/control/client.ts +7 -12
  104. package/lib/sandbox/control/completed-reentry.ts +2 -1
  105. package/lib/sandbox/control/task-view.ts +4 -2
  106. package/lib/sandbox/mounts.ts +4 -4
  107. package/lib/sandbox/recovery.ts +26 -18
  108. package/lib/sandbox/removal.ts +60 -27
  109. package/lib/sandbox/task-resolver.ts +9 -6
  110. package/lib/sandbox/workspace-identity.ts +6 -5
  111. package/lib/sandbox/workspace-view.ts +28 -20
  112. package/lib/task/activity-intent.ts +3 -17
  113. package/lib/task/artifact-lifecycle.ts +39 -11
  114. package/lib/task/artifact-operations.ts +28 -35
  115. package/lib/task/capabilities.ts +2 -2
  116. package/lib/task/commands/ls.ts +4 -3
  117. package/lib/task/commands/status.ts +1 -1
  118. package/lib/task/commit-operation.ts +5 -6
  119. package/lib/task/control-authority.ts +29 -35
  120. package/lib/task/create-service.ts +2 -4
  121. package/lib/task/create.ts +10 -51
  122. package/lib/task/delegation-receipts.ts +1 -1
  123. package/lib/task/delivery.ts +2 -10
  124. package/lib/task/events.ts +5 -32
  125. package/lib/task/finalization.ts +27 -30
  126. package/lib/task/lifecycle.ts +9 -50
  127. package/lib/task/local-artifact-finalization.ts +1 -4
  128. package/lib/task/orchestration.ts +31 -80
  129. package/lib/task/platform-operation-recovery.ts +70 -82
  130. package/lib/task/resolve-ref.ts +18 -12
  131. package/lib/task/review-finalization.ts +2 -52
  132. package/lib/task/review-identity.ts +3 -1
  133. package/lib/task/short-id.ts +10 -8
  134. package/lib/task/verification-engine.ts +20 -62
  135. package/lib/task/workspace-root.ts +78 -0
  136. package/lib/task/workspace-snapshot.ts +4 -3
  137. package/package.json +1 -1
  138. package/templates/.agents/QUICKSTART.en.md +19 -8
  139. package/templates/.agents/QUICKSTART.zh-CN.md +19 -8
  140. package/templates/.agents/README.en.md +10 -15
  141. package/templates/.agents/README.zh-CN.md +9 -13
  142. package/templates/.agents/rules/issue-sync.en.md +1 -1
  143. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  144. package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
  145. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
  146. package/templates/.agents/rules/next-step-output.en.md +2 -2
  147. package/templates/.agents/rules/next-step-output.zh-CN.md +2 -2
  148. package/templates/.agents/rules/pr-sync.en.md +5 -3
  149. package/templates/.agents/rules/pr-sync.zh-CN.md +7 -10
  150. package/templates/.agents/rules/release-commands.en.md +3 -0
  151. package/templates/.agents/rules/release-commands.zh-CN.md +3 -0
  152. package/templates/.agents/rules/task-management.en.md +0 -2
  153. package/templates/.agents/rules/task-management.zh-CN.md +0 -2
  154. package/templates/.agents/rules/task-short-id.en.md +2 -2
  155. package/templates/.agents/rules/task-short-id.zh-CN.md +2 -2
  156. package/templates/.agents/scripts/task-short-id.js +2 -8
  157. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  158. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  159. package/templates/.agents/skills/analyze-task/config/verify.en.json +0 -1
  160. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +0 -1
  161. package/templates/.agents/skills/archive-tasks/SKILL.en.md +5 -5
  162. package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +5 -5
  163. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +10 -2
  164. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  165. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +5 -5
  166. package/templates/.agents/skills/block-task/config/verify.json +0 -1
  167. package/templates/.agents/skills/cancel-task/SKILL.en.md +7 -7
  168. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +7 -7
  169. package/templates/.agents/skills/cancel-task/config/verify.json +0 -1
  170. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  171. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  172. package/templates/.agents/skills/code-task/config/verify.en.json +0 -1
  173. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +0 -1
  174. package/templates/.agents/skills/code-task/reference/output-template.en.md +1 -1
  175. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
  176. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  177. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  178. package/templates/.agents/skills/commit/config/verify.json +0 -1
  179. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +2 -2
  180. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +2 -2
  181. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +2 -2
  182. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +2 -2
  183. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  184. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  185. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +0 -1
  186. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +0 -1
  187. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +3 -3
  188. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +3 -3
  189. package/templates/.agents/skills/complete-task/SKILL.en.md +15 -30
  190. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +17 -32
  191. package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
  192. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
  193. package/templates/.agents/skills/create-pr/SKILL.en.md +3 -3
  194. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -3
  195. package/templates/.agents/skills/create-pr/config/verify.json +0 -1
  196. package/templates/.agents/skills/create-pr/reference/change-report.en.md +10 -3
  197. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +10 -3
  198. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +3 -3
  199. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +3 -3
  200. package/templates/.agents/skills/create-release-note/SKILL.en.md +2 -2
  201. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +2 -2
  202. package/templates/.agents/skills/create-task/SKILL.en.md +4 -22
  203. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -22
  204. package/templates/.agents/skills/create-task/config/verify.json +0 -1
  205. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  206. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  207. package/templates/.agents/skills/import-codescan/config/verify.json +0 -1
  208. package/templates/.agents/skills/import-dependabot/SKILL.en.md +3 -3
  209. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
  210. package/templates/.agents/skills/import-dependabot/config/verify.json +0 -1
  211. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  212. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  213. package/templates/.agents/skills/import-issue/config/verify.json +0 -1
  214. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  215. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  216. package/templates/.agents/skills/plan-task/config/verify.en.json +0 -1
  217. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +0 -1
  218. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  219. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -3
  220. package/templates/.agents/skills/restore-task/config/verify.json +0 -1
  221. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  222. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  223. package/templates/.agents/skills/review-analysis/config/verify.en.json +0 -1
  224. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +0 -1
  225. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
  226. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
  227. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  228. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  229. package/templates/.agents/skills/review-code/reference/output-templates.en.md +4 -4
  230. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +4 -4
  231. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  232. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  233. package/templates/.agents/skills/review-plan/config/verify.en.json +0 -1
  234. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +0 -1
  235. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
  236. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
  237. package/templates/.agents/skills/review-pr/SKILL.en.md +2 -2
  238. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -2
  239. package/templates/.agents/skills/review-pr/config/verify.en.json +0 -1
  240. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +0 -1
  241. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +1 -1
  242. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +1 -1
  243. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +0 -1
  244. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +0 -1
  245. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  246. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  247. package/templates/.agents/skills/test/SKILL.en.md +0 -3
  248. package/templates/.agents/skills/test/SKILL.zh-CN.md +0 -3
  249. package/templates/.agents/skills/test-integration/SKILL.en.md +0 -3
  250. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +0 -3
  251. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +13 -6
  252. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +0 -3
  253. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +0 -3
  254. package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
  255. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
  256. package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
  257. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +3 -3
  258. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +3 -3
  259. package/templates/.agents/templates/handoff.en.md +1 -1
  260. package/templates/.agents/templates/handoff.zh-CN.md +1 -1
  261. package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
  262. package/templates/.agents/workflows/feature-development.en.yaml +1 -1
  263. package/templates/.agents/workflows/refactoring.en.yaml +1 -1
  264. package/dist/lib/task/task-execution-lock.js +0 -250
  265. package/lib/task/task-execution-lock.ts +0 -320
@@ -18,7 +18,7 @@ import { validateCurrentTaskContract } from './current-contract.ts';
18
18
  import { isValidAgentInfraVersion } from '../version.ts';
19
19
  import { taskIssueIdentity } from '../platform/task-identities.ts';
20
20
  import { resourceIdentityEquals } from '../platform/resource-identity.ts';
21
- import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
21
+ import { resolveProjectTaskWorkspaceRoot } from './workspace-root.ts';
22
22
 
23
23
  const lifecycleIntentCatalog = [
24
24
  'block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'
@@ -138,7 +138,7 @@ const DEFAULT_IO: LifecycleFileSystem = {
138
138
 
139
139
  function isVirtiofsMountPoint(directory: string, mountInfo: string): boolean {
140
140
  const decodeMountInfoPath = (value: string): string => value.replace(/\\([0-7]{3})/gu, (_match, octal: string) => String.fromCharCode(Number.parseInt(octal, 8)));
141
- const expected = path.resolve(directory);
141
+ const expected = fs.realpathSync.native(directory);
142
142
  return mountInfo.split('\n').some((line) => {
143
143
  if (!line) return false;
144
144
  const fields = line.split(' ');
@@ -437,14 +437,14 @@ function validateRestoreStaging(request: Extract<TaskLifecycleRequest, { intent:
437
437
  let workspace: string;
438
438
  let stagingDir: string;
439
439
  try {
440
- workspace = fs.realpathSync.native(path.resolve(repoRoot, '.agents', 'workspace'));
440
+ workspace = fs.realpathSync.native(resolveProjectTaskWorkspaceRoot(repoRoot));
441
441
  stagingDir = fs.realpathSync.native(path.resolve(request.stagingDir));
442
442
  } catch (error) {
443
443
  return { code: 'LIFECYCLE_STAGING_INVALID', message: String(error) };
444
444
  }
445
445
  const relative = path.relative(workspace, stagingDir);
446
446
  if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) || path.basename(stagingDir) === taskId) {
447
- return { code: 'LIFECYCLE_STAGING_INVALID', message: 'restore staging must be a non-final directory inside .agents/workspace' };
447
+ return { code: 'LIFECYCLE_STAGING_INVALID', message: 'restore staging must be a non-final directory inside the project task workspace' };
448
448
  }
449
449
  let entries: fs.Dirent[];
450
450
  try { entries = fs.readdirSync(stagingDir, { withFileTypes: true }); }
@@ -629,7 +629,7 @@ export function inspectTaskLifecycleProgress(
629
629
  }
630
630
  }
631
631
 
632
- function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options: TaskLifecycleOptions = {}): TaskLifecycleResult {
632
+ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLifecycleOptions = {}): TaskLifecycleResult {
633
633
  if (requestInput.intent === 'recover-started') {
634
634
  return failed(requestInput, { code: 'LIFECYCLE_RECOVERY_REQUIRES_AUTHORITY', message: 'recover-started must be dispatched through task control authority' });
635
635
  }
@@ -671,7 +671,7 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
671
671
  } else {
672
672
  return failed(request, { code: 'LIFECYCLE_TASK_NOT_FOUND', message: `task ${taskId} has no lifecycle source` }, { taskId });
673
673
  }
674
- const targetPath = path.join(repoRoot, '.agents', 'workspace', spec.target, taskId);
674
+ const targetPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), spec.target, taskId);
675
675
  const requiredSteps = stepsForIntent(request.intent);
676
676
  const movingTaskDirectory = sourcePath !== targetPath;
677
677
  const currentTaskFile = path.join(sourcePath, 'task.md');
@@ -709,7 +709,7 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
709
709
  if (!contract.ok) return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: contract.message }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
710
710
  }
711
711
  const registryRequest = executeShortIdCommand({
712
- operation: 'list', activeDir: path.join(repoRoot, '.agents', 'workspace', 'active'),
712
+ operation: 'list', activeDir: path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active'),
713
713
  shortIdLength: configuredShortIdLength(repoRoot)
714
714
  });
715
715
  if (registryRequest.status === 'failed') {
@@ -880,7 +880,7 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
880
880
  }
881
881
  }
882
882
  }
883
- const activeTaskPath = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
883
+ const activeTaskPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId);
884
884
  if (request.intent === 'complete' && fs.existsSync(activeTaskPath)) {
885
885
  try {
886
886
  let sourceIsVirtiofsMount = false;
@@ -925,50 +925,9 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
925
925
  };
926
926
  }
927
927
 
928
- function applyTaskLifecycle(request: TaskLifecycleRequest, options: TaskLifecycleOptions = {}): TaskLifecycleResult {
929
- const normalized = normalizedRequest(request);
930
- if ('code' in normalized) return applyTaskLifecycleUnlocked(request, options);
931
- const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
932
- const taskId = normalized.intent === 'restore'
933
- ? normalized.taskRef
934
- : (() => {
935
- const resolved = resolveTaskRef(normalized.taskRef, { repoRoot });
936
- return resolved.ok ? resolved.taskId : null;
937
- })();
938
- if (!taskId) return applyTaskLifecycleUnlocked(request, options);
939
- try {
940
- return withTaskExecutionLock(repoRoot, taskId, 'task-lifecycle.move', () => applyTaskLifecycleUnlocked(request, options));
941
- } catch (error) {
942
- if (error instanceof TaskExecutionLockError) {
943
- return failed(request, { code: error.code, message: error.message }, { taskId });
944
- }
945
- throw error;
946
- }
947
- }
948
-
949
- function applyTaskLifecycleWithinTaskLock(
950
- request: TaskLifecycleRequest,
951
- options: TaskLifecycleOptions,
952
- lockedTaskId: string
953
- ): TaskLifecycleResult {
954
- const normalized = normalizedRequest(request);
955
- if ('code' in normalized) return failed(request, normalized);
956
- const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
957
- const resolved = normalized.intent === 'restore'
958
- ? { ok: true as const, taskId: normalized.taskRef }
959
- : resolveTaskRef(normalized.taskRef, { repoRoot });
960
- if (!resolved.ok || resolved.taskId !== lockedTaskId) {
961
- return failed(request, {
962
- code: 'LIFECYCLE_LOCK_SCOPE_MISMATCH',
963
- message: 'task lifecycle request does not match the held task lock'
964
- }, { taskId: resolved.ok ? resolved.taskId : resolved.taskId });
965
- }
966
- return applyTaskLifecycleUnlocked(request, options);
967
- }
968
-
969
928
  export {
970
929
  lifecycleIntentCatalog, lifecycleFailureCatalog, lifecycleProducerCatalog,
971
- applyTaskLifecycle, applyTaskLifecycleWithinTaskLock, previewTaskCompletion
930
+ applyTaskLifecycle, previewTaskCompletion
972
931
  };
973
932
  export type {
974
933
  TaskLifecycleIntent, TaskLifecycleRequest, TaskLifecycleResult, TaskLifecycleOptions,
@@ -52,7 +52,6 @@ type LocalArtifactFinalizationRequest = {
52
52
  artifact: string;
53
53
  repoRoot?: string;
54
54
  requiredSections?: readonly string[];
55
- lockAlreadyHeld?: boolean;
56
55
  };
57
56
 
58
57
  type LocalArtifactFinalizationResult = {
@@ -129,7 +128,6 @@ type LocalArtifactPreparation = Readonly<{
129
128
  result: LocalArtifactFinalizationResult;
130
129
  content: string;
131
130
  repoRoot?: string;
132
- lockAlreadyHeld?: boolean;
133
131
  }>;
134
132
 
135
133
  function prepareLocalArtifact(
@@ -180,8 +178,7 @@ function prepareLocalArtifact(
180
178
  return {
181
179
  result: validation.ok && !qualificationError ? { ...result, status: 'passed', error: null } : result,
182
180
  content,
183
- repoRoot: resolved.repoRoot,
184
- lockAlreadyHeld: request.lockAlreadyHeld
181
+ repoRoot: resolved.repoRoot
185
182
  };
186
183
 
187
184
  }
@@ -9,6 +9,7 @@ import { parseReviewSummary } from './review-artifacts.ts';
9
9
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
10
10
  import type { LedgerDocument } from './ledger.ts';
11
11
  import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
12
+ import { resolveProjectTaskWorkspaceRoot } from './workspace-root.ts';
12
13
  import {
13
14
  activateDelegation,
14
15
  abortActivatedDelegation,
@@ -48,14 +49,13 @@ import {
48
49
  import type { RepositorySnapshot } from './workspace-snapshot.ts';
49
50
  import type { WorkspaceSnapshotContext } from './workspace-snapshot.ts';
50
51
  import { assertGitRepositoryBinding } from '../git/worktree-identity.ts';
51
- import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
52
52
  import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.ts';
53
53
  import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.ts';
54
54
  import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.ts';
55
55
  import { hasOpenLifecycleExecution } from './activity-log.ts';
56
56
  import type { LifecycleAction, LifecycleFacts } from './capabilities.ts';
57
57
  import { normalizeAgentToken } from '../agent-clients/tokens.ts';
58
- import { resolveArtifactContext } from './artifact-lifecycle.ts';
58
+ import { resolveArtifactContextForResolvedTask } from './artifact-lifecycle.ts';
59
59
 
60
60
  type OrchestrationStatus = 'running' | 'paused' | 'completed';
61
61
  type ModelPolicySource = Readonly<{
@@ -441,19 +441,7 @@ function mutateDelegationAdapterEvidence(
441
441
  ): unknown {
442
442
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
443
443
  if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
444
- return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, false);
445
- }
446
-
447
- function mutateDelegationAdapterEvidenceWithinTaskLock(
448
- taskRef: string,
449
- receiptId: string,
450
- adapterId: AgentClientId,
451
- mutate: (current: unknown) => unknown,
452
- options: OrchestrationOptions = {}
453
- ): unknown {
454
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
455
- if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
456
- return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, true);
444
+ return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options);
457
445
  }
458
446
 
459
447
  function mutateDelegationAdapterEvidenceAtResolved(
@@ -461,8 +449,7 @@ function mutateDelegationAdapterEvidenceAtResolved(
461
449
  receiptId: string,
462
450
  adapterId: AgentClientId,
463
451
  mutate: (current: unknown) => unknown,
464
- options: OrchestrationOptions,
465
- taskLockHeld: boolean
452
+ options: OrchestrationOptions
466
453
  ): unknown {
467
454
  const update = () => {
468
455
  try {
@@ -494,9 +481,7 @@ function mutateDelegationAdapterEvidenceAtResolved(
494
481
  }
495
482
  };
496
483
  let mutationError: unknown;
497
- const result = taskLockHeld
498
- ? update()
499
- : withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `agent-adapter.${adapterId}.evidence`, update);
484
+ const result = update();
500
485
  if (mutationError) throw mutationError;
501
486
  return result;
502
487
  }
@@ -733,9 +718,13 @@ function routeFromFacts(facts: LifecycleFacts): Omit<OrchestrationNext, 'request
733
718
  return { action: command, role, stage: action };
734
719
  }
735
720
 
736
- function routeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
737
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
738
- if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
721
+ type ResolvedTaskRef = Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>;
722
+
723
+ function routeResolvedTask(
724
+ taskRef: string,
725
+ resolved: ResolvedTaskRef,
726
+ options: OrchestrationOptions
727
+ ): OrchestrationResult {
739
728
  let gitRoot: string;
740
729
  try {
741
730
  gitRoot = gitRootFor(resolved.repoRoot, options);
@@ -772,7 +761,8 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
772
761
  resolved.taskDir,
773
762
  content,
774
763
  resolved.state,
775
- hasOpenLifecycleExecution(content) || Boolean(run?.pendingDelegation)
764
+ hasOpenLifecycleExecution(content) || Boolean(run?.pendingDelegation),
765
+ resolved.repoRoot
776
766
  );
777
767
  if (!facts.ok) return failed('ORCHESTRATION_CAPABILITY_FACTS_INVALID', facts.message, resolved.taskId);
778
768
  const routed = routeFromFacts(facts.facts);
@@ -864,7 +854,7 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
864
854
  }
865
855
  const action = routed.stage as LifecycleAction;
866
856
  const family = action === 'analysis' ? 'analysis' : action;
867
- const artifactContext = resolveArtifactContext(taskRef, family, {
857
+ const artifactContext = resolveArtifactContextForResolvedTask(resolved, family, {
868
858
  repoRoot: resolved.repoRoot
869
859
  });
870
860
  if (artifactContext.status !== 'ready' || !artifactContext.selection) {
@@ -898,6 +888,12 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
898
888
  return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
899
889
  }
900
890
 
891
+ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
892
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
893
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
894
+ return routeResolvedTask(taskRef, resolved, options);
895
+ }
896
+
901
897
  function statusOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
902
898
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
903
899
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
@@ -952,7 +948,7 @@ function validateDelegationRun(
952
948
  return null;
953
949
  }
954
950
 
955
- function prepareOrchestrationDelegationUnlocked(
951
+ function prepareOrchestrationDelegation(
956
952
  taskRef: string,
957
953
  input: Readonly<{
958
954
  client: AgentClientId;
@@ -978,7 +974,7 @@ function prepareOrchestrationDelegationUnlocked(
978
974
  const runFailure = validateDelegationRun(taskRef, resolved.taskId, run, options);
979
975
  if (runFailure) return runFailure;
980
976
  const activeRun = run!;
981
- const routed = routeOrchestration(taskRef, options);
977
+ const routed = routeResolvedTask(taskRef, resolved, options);
982
978
  if (!routed.next) return routed;
983
979
  const next = routed.next;
984
980
  const policyFailure = validatePreparedModelPolicy(resolved.taskId, activeRun, next, input);
@@ -1010,31 +1006,7 @@ function prepareOrchestrationDelegationUnlocked(
1010
1006
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
1011
1007
  }
1012
1008
 
1013
- function prepareOrchestrationDelegation(
1014
- taskRef: string,
1015
- input: Readonly<{
1016
- client: AgentClientId;
1017
- requestedModel?: string;
1018
- requestedReasoningEffort?: string;
1019
- }>,
1020
- options: OrchestrationOptions = {}
1021
- ): OrchestrationResult {
1022
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1023
- if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1024
- try {
1025
- return withTaskExecutionLock(
1026
- resolved.repoRoot,
1027
- resolved.taskId,
1028
- 'task-orchestration.prepare.task',
1029
- () => prepareOrchestrationDelegationUnlocked(taskRef, input, options)
1030
- );
1031
- } catch (error) {
1032
- if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
1033
- throw error;
1034
- }
1035
- }
1036
-
1037
- function dispatchOrchestrationDelegationUnlocked(
1009
+ function dispatchOrchestrationDelegation(
1038
1010
  taskRef: string,
1039
1011
  options: OrchestrationOptions = {}
1040
1012
  ): OrchestrationResult {
@@ -1055,25 +1027,6 @@ function dispatchOrchestrationDelegationUnlocked(
1055
1027
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1056
1028
  }
1057
1029
 
1058
- function dispatchOrchestrationDelegation(
1059
- taskRef: string,
1060
- options: OrchestrationOptions = {}
1061
- ): OrchestrationResult {
1062
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1063
- if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1064
- try {
1065
- return withTaskExecutionLock(
1066
- resolved.repoRoot,
1067
- resolved.taskId,
1068
- 'task-orchestration.dispatch.task',
1069
- () => dispatchOrchestrationDelegationUnlocked(taskRef, options)
1070
- );
1071
- } catch (error) {
1072
- if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
1073
- throw error;
1074
- }
1075
- }
1076
-
1077
1030
  type MatchingDelegationsResult =
1078
1031
  | Readonly<{ ok: true; matches: readonly { taskId: string; run: OrchestrationRun }[] }>
1079
1032
  | Readonly<{ ok: false; error: OrchestrationResult }>;
@@ -1090,7 +1043,7 @@ function matchingDelegations(
1090
1043
  return { ok: false, error: failed('ORCHESTRATION_TASK_ID_INVALID', 'taskId must be a canonical TASK-id') };
1091
1044
  }
1092
1045
  const repoRoot = options.repoRoot ?? process.cwd();
1093
- const taskDir = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
1046
+ const taskDir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId);
1094
1047
  const run = readRun(taskDir, options);
1095
1048
  const matches = run && run.status === 'running' && run.pendingDelegation && predicate(run.pendingDelegation)
1096
1049
  ? [{ taskId, run }]
@@ -1385,7 +1338,7 @@ function recoverPreparedOrchestrationDelegation(
1385
1338
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1386
1339
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1387
1340
  try {
1388
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.recover-prepared', () => {
1341
+ return (() => {
1389
1342
  const run = readRun(resolved.taskDir, options);
1390
1343
  const receipt = run?.pendingDelegation;
1391
1344
  if (!run || !receipt) {
@@ -1430,9 +1383,8 @@ function recoverPreparedOrchestrationDelegation(
1430
1383
  }, options.now);
1431
1384
  saveRun(resolved.taskDir, updated);
1432
1385
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1433
- });
1386
+ })();
1434
1387
  } catch (error) {
1435
- if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
1436
1388
  return failed(
1437
1389
  'ORCHESTRATION_PREPARED_RECOVERY_FAILED',
1438
1390
  error instanceof Error ? error.message : String(error),
@@ -1462,7 +1414,7 @@ function recoveryReceiptMatches(
1462
1414
  && normalizeAgentToken(event.startedAgent) === normalizeAgentToken(receipt.client);
1463
1415
  }
1464
1416
 
1465
- function recoverActivatedOrchestrationDelegationUnderLock(
1417
+ function recoverActivatedOrchestrationDelegation(
1466
1418
  taskRef: string,
1467
1419
  event: ActivatedRecoveryEvent,
1468
1420
  options: OrchestrationOptions = {}
@@ -1598,7 +1550,7 @@ function sealOrchestrationDelegation(
1598
1550
  const orchestrationOptions = options;
1599
1551
  const resolved = resolveTaskRef(taskRef, { repoRoot: orchestrationOptions.repoRoot });
1600
1552
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1601
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.seal', () => {
1553
+ return (() => {
1602
1554
  const run = readRun(resolved.taskDir, orchestrationOptions);
1603
1555
  if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1604
1556
  if (expected && (run.runId !== expected.runId || run.pendingDelegation.id !== expected.receiptId)) {
@@ -1616,7 +1568,7 @@ function sealOrchestrationDelegation(
1616
1568
  const updated = withUpdatedRun(current, { pendingDelegation: result.receipt });
1617
1569
  saveRun(resolved.taskDir, updated);
1618
1570
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1619
- });
1571
+ })();
1620
1572
  }
1621
1573
 
1622
1574
  function advanceOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
@@ -1672,7 +1624,6 @@ export {
1672
1624
  orchestrationPath,
1673
1625
  readDelegationAdapterEvidence,
1674
1626
  mutateDelegationAdapterEvidence,
1675
- mutateDelegationAdapterEvidenceWithinTaskLock,
1676
1627
  pauseMatchingOrchestrationDelegation,
1677
1628
  pauseOrchestration,
1678
1629
  planOrchestrationStageCompletion,
@@ -1680,7 +1631,7 @@ export {
1680
1631
  readRun,
1681
1632
  reconcileMatchingOrchestrationDelegation,
1682
1633
  recoverPreparedOrchestrationDelegation,
1683
- recoverActivatedOrchestrationDelegationUnderLock,
1634
+ recoverActivatedOrchestrationDelegation,
1684
1635
  routeOrchestration,
1685
1636
  sealMatchingOrchestrationDelegation,
1686
1637
  sealOrchestrationDelegation,
@@ -3,13 +3,15 @@ import path from 'node:path';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { execFileSync } from 'node:child_process';
5
5
 
6
- import { inspectPlatformCommentOperation, normalizeCommentContent, syncPlatformComment } from '../platform/issue-comments.ts';
6
+ import { inspectPlatformCommentOperation, syncPlatformComment } from '../platform/issue-comments.ts';
7
+ import { parseDeliverySummaryContent, renderDeliverySummaryContent, parseCancelSummaryContent, renderCancelSummaryContent } from '../platform/issue-summary-content.ts';
8
+ import { enumerateArtifacts } from './artifacts.ts';
7
9
  import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.ts';
8
10
  import { resolvePlatformProviderContext } from '../platform/context.ts';
9
11
  import { providerOperationContext, providerError, unsupportedProviderOperation } from '../platform/provider-bridge.ts';
10
12
  import { inLabelMappingDigest } from '../platform/in-label-sync.ts';
11
- import { bindPlatformPullRequest, inspectPlatformPullRequest, recoverCreatedPullRequest, syncPlatformPullRequest } from '../platform/pull-requests.ts';
12
- import { syncPullRequestSummary } from '../platform/pr-summary.ts';
13
+ import { bindPlatformPullRequest, recoverCreatedPullRequest, syncPlatformPullRequest } from '../platform/pull-requests.ts';
14
+ import { replayPullRequestSummary } from '../platform/pr-summary.ts';
13
15
  import { publishPrReview, readReviewBodyFile } from '../platform/pr-review.ts';
14
16
  import type { PlatformClient } from '../platform/context.ts';
15
17
  import { parseTaskFrontmatter } from './frontmatter.ts';
@@ -53,33 +55,57 @@ function result(status: RecoveryResult['status'], recovered: string[], pending:
53
55
  function summaryPayload(taskDir: string, taskId: string): { body: string; sha256: string } | null {
54
56
  const file = path.join(taskDir, '.delivery-summary.json');
55
57
  if (!fs.existsSync(file)) return null;
56
- const data = JSON.parse(fs.readFileSync(file, 'utf8')) as { taskId?: unknown; body?: unknown; sha256?: unknown };
57
- const canonical = typeof data.body === 'string' ? canonicalizeSummaryBody(data.body) : null;
58
- if (data.taskId !== taskId || typeof data.body !== 'string' || typeof data.sha256 !== 'string'
59
- || !canonical?.ok || canonical.value !== data.body
60
- || createHash('sha256').update(data.body).digest('hex') !== data.sha256) {
58
+ const data = JSON.parse(fs.readFileSync(file, 'utf8')) as { taskId?: unknown; content?: unknown; contentSha256?: unknown };
59
+ let content;
60
+ try { content = parseDeliverySummaryContent(data.content); }
61
+ catch { content = null; }
62
+ if (data.taskId !== taskId || !content || content.taskId !== taskId || typeof data.contentSha256 !== 'string'
63
+ || createHash('sha256').update(JSON.stringify(content)).digest('hex') !== data.contentSha256) {
61
64
  throw Object.assign(new Error('delivery summary staging record is invalid'), { code: 'SUMMARY_STAGING_INVALID' });
62
65
  }
63
- return { body: data.body, sha256: data.sha256 };
66
+ const canonical = canonicalizeSummaryBody(renderDeliverySummaryContent(content));
67
+ if (!canonical?.ok) throw Object.assign(new Error('delivery summary staging body is not canonical'), { code: 'SUMMARY_STAGING_INVALID' });
68
+ return { body: canonical.value, sha256: createHash('sha256').update(canonical.value).digest('hex') };
69
+ }
70
+
71
+ function cancellationPayload(taskDir: string, taskId: string): string | null {
72
+ const taskPath = path.join(taskDir, 'task.md');
73
+ if (!fs.existsSync(taskPath)) return null;
74
+ const taskContent = fs.readFileSync(taskPath, 'utf8');
75
+ const frontmatter = parseTaskFrontmatter(taskContent);
76
+ const title = /^#\s+(.+)$/mu.exec(taskContent)?.[1]?.trim() || taskId;
77
+ const artifacts = enumerateArtifacts(taskDir).filter((artifact) => artifact.name !== 'task.md').map((artifact) => artifact.name);
78
+ const content = parseCancelSummaryContent({
79
+ taskId,
80
+ title,
81
+ outcome: 'cancelled',
82
+ reason: frontmatter.cancel_reason,
83
+ cancelledAt: frontmatter.cancelled_at,
84
+ lastCompletedArtifact: artifacts.at(-1) ?? null
85
+ });
86
+ return renderCancelSummaryContent(content);
64
87
  }
65
88
 
66
89
  async function replayComment(taskId: string, taskDir: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
67
90
  if (operation.kind === 'task-comment') {
68
- return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency, skipQueue: true });
91
+ return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
69
92
  }
70
93
  if (operation.kind === 'artifact-comment') {
71
- return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency, skipQueue: true });
94
+ return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
72
95
  }
73
96
  if (operation.kind === 'summary-comment') {
74
- const summary = summaryPayload(taskDir, taskId);
75
- if (!summary) return platformResult('failed', { error: { code: 'SUMMARY_STAGING_MISSING', message: 'delivery summary staging record is unavailable', retryable: false } });
76
- return syncPlatformComment(taskId, {
77
- kind: 'summary', body: summary.body, agent, cwd,
78
- summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency, client, skipQueue: true
79
- });
97
+ let summary;
98
+ try { summary = summaryPayload(taskDir, taskId); }
99
+ catch (error) { return platformResult('failed', { error: { code: 'SUMMARY_STAGING_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } }); }
100
+ if (!summary) return platformResult('blocked', { error: { code: 'SUMMARY_STAGING_MISSING', message: 'delivery summary staging record is unavailable', retryable: true } });
101
+ return syncPlatformComment(taskId, { kind: 'summary', body: summary.body, agent, cwd, client, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency, queuedOperation: operation });
80
102
  }
81
103
  if (operation.kind === 'cancel-comment') {
82
- return syncPlatformComment(taskId, { kind: 'cancel', agent, cwd, client, dependency: operation.dependency, skipQueue: true });
104
+ let body: string | null;
105
+ try { body = cancellationPayload(taskDir, taskId); }
106
+ catch (error) { return platformResult('failed', { error: { code: 'CANCEL_SUMMARY_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } }); }
107
+ if (!body) return platformResult('blocked', { error: { code: 'CANCEL_SUMMARY_INVALID', message: 'Cancellation facts are unavailable for recovery', retryable: true } });
108
+ return syncPlatformComment(taskId, { kind: 'cancel', body, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
83
109
  }
84
110
  return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_UNSUPPORTED', message: `operation kind '${operation.kind}' has no registered recovery handler`, retryable: false } });
85
111
  }
@@ -183,56 +209,6 @@ async function verifyIssueCreate(taskId: string, operation: ReturnType<typeof re
183
209
  return platformResult('no-op');
184
210
  }
185
211
 
186
- async function replayPullRequestSummary(taskId: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
187
- let identity;
188
- try { identity = parseResourceIdentity(JSON.parse(operation.target), 'queued pull-request summary identity'); }
189
- catch { return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary identity is invalid', retryable: false } }); }
190
- const summary = operation.pullRequestSummary;
191
- if (!summary) return platformResult('blocked', { error: {
192
- code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request summary recovery content is missing', retryable: false
193
- } });
194
- const manualValidationHeading = /^###\s+(?:⏳\s+(?:Manual Validation Pending|人工验证待收尾)|✅\s+(?:Manual Validation Passed|人工验证已通过))\s*$/mu.test(summary.body);
195
- if (manualValidationHeading && !summary.manualValidation) return platformResult('blocked', { error: {
196
- code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'Queued manual-validation summary has no persisted transaction context', retryable: false
197
- } });
198
- const resolved = await resolvePlatformProviderContext({ cwd, client });
199
- if (!resolved.ok) return resolved.context;
200
- const listed = resolved.value.provider.comments?.list
201
- ? await resolved.value.provider.comments.list({ context: providerOperationContext(resolved.value), parent: identity })
202
- : unsupportedProviderOperation(resolved.value.provider, 'comments.list');
203
- if (!listed.ok) return platformResult(providerErrorRetryable(listed.error.code) ? 'blocked' : 'failed', { error: providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED') });
204
- const marker = `<!-- sync-pr:${taskId}:summary -->`;
205
- const summaries = listed.value.filter((comment) => normalizeCommentContent(comment.body).split('\n', 1)[0] === marker);
206
- if (summaries.length > 1) return platformResult('blocked', { error: {
207
- code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple pull-request comments contain the queued summary marker', retryable: false
208
- } });
209
- if (!summary.manualValidation && summaries.length === 1 && createHash('sha256').update(normalizeCommentContent(summaries[0]!.body)).digest('hex') === operation.expectedDigest) {
210
- return platformResult('no-op');
211
- }
212
- const bound = await inspectPlatformPullRequest(taskId, { cwd, client });
213
- if (!bound.pullRequest || !resourceIdentityEquals(bound.pullRequest.identity, identity)) return platformResult('blocked', { error: bound.error ?? {
214
- code: 'PLATFORM_OPERATION_IDENTITY_MISMATCH', message: 'Bound pull request differs from the queued summary target', retryable: false
215
- } });
216
- const replayed = await syncPullRequestSummary(taskId, {
217
- agent,
218
- cwd,
219
- client,
220
- body: summary.body,
221
- changeReportFile: summary.changeReportFile,
222
- ...(summary.manualValidation ? { manualValidation: summary.manualValidation } : {}),
223
- primaryResult: 'no_op',
224
- strict: true,
225
- skipQueue: true,
226
- lockAlreadyHeld: true
227
- });
228
- if ((replayed.status === 'applied' || replayed.status === 'no-op') && !replayed.error && replayed.warnings.length === 0) {
229
- return platformResult(replayed.status, { changed: replayed.changed });
230
- }
231
- return platformResult('blocked', { error: replayed.error ?? {
232
- code: 'PR_SUMMARY_RECOVERY_UNCONFIRMED', message: 'Pull-request summary replay did not confirm the requested remote state', retryable: true
233
- } });
234
- }
235
-
236
212
  async function replayPullRequestReview(operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
237
213
  const intent = operation.pullRequestReview;
238
214
  if (!intent) return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request review recovery parameters are missing', retryable: false } });
@@ -281,6 +257,14 @@ async function replayPullRequest(taskId: string, operation: ReturnType<typeof re
281
257
  return recoverCreatedPullRequest(taskId, { agent, cwd, base: intent.baseRef || '', head: intent.headRef || '', skipQueue: true });
282
258
  }
283
259
 
260
+ function isRecoveryEligible(operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], excludeId?: string): boolean {
261
+ if (operation.id === excludeId) return false;
262
+ if (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown') return true;
263
+ return operation.state === 'failed'
264
+ && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'
265
+ ;
266
+ }
267
+
284
268
  async function recoverPlatformOperationsOnce(
285
269
  taskRef: string,
286
270
  selection: 'required' | 'deferred' | 'all',
@@ -294,9 +278,7 @@ async function recoverPlatformOperationsOnce(
294
278
  const value = error as { code?: string; message?: string };
295
279
  return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
296
280
  }
297
- const candidates = journal.operations.filter((operation) => operation.id !== options.excludeId
298
- && (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
299
- || (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED')));
281
+ const candidates = journal.operations.filter((operation) => isRecoveryEligible(operation, options.excludeId));
300
282
  if (!candidates.length) return result('no-op', [], []);
301
283
  const head = candidates[0]!;
302
284
  if (selection !== 'all' && head.dependency !== selection) {
@@ -358,11 +340,18 @@ async function recoverPlatformOperationsOnce(
358
340
  const commentOptions = operation.kind === 'task-comment'
359
341
  ? { kind: 'task' as const, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
360
342
  : operation.kind === 'artifact-comment'
361
- ? { kind: 'artifact' as const, artifact: operation.target, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
362
- : operation.kind === 'summary-comment' ? (() => {
363
- const summary = summaryPayload(resolved.taskDir, resolved.taskId);
364
- return summary ? { kind: 'summary' as const, body: summary.body, agent: options.agent, cwd: resolved.repoRoot, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency } : null;
365
- })() : { kind: 'cancel' as const, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency };
343
+ ? { kind: 'artifact' as const, artifact: operation.target, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
344
+ : operation.kind === 'summary-comment' ? (() => {
345
+ try {
346
+ const summary = summaryPayload(resolved.taskDir, resolved.taskId);
347
+ return summary ? { kind: 'summary' as const, body: summary.body, agent: options.agent, cwd: resolved.repoRoot, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency } : null;
348
+ } catch { return null; }
349
+ })() : (() => {
350
+ try {
351
+ const body = cancellationPayload(resolved.taskDir, resolved.taskId);
352
+ return body ? { kind: 'cancel' as const, body, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency } : null;
353
+ } catch { return null; }
354
+ })();
366
355
  const current = commentOptions ? inspectPlatformCommentOperation(resolved.taskId, commentOptions) : null;
367
356
  if (!current) {
368
357
  pending.push(operation.id);
@@ -384,20 +373,21 @@ async function recoverPlatformOperationsOnce(
384
373
  remote = operation.kind === 'issue-create'
385
374
  ? await verifyIssueCreate(resolved.taskId, operation, resolved.repoRoot)
386
375
  : operation.kind === 'pull-request-summary'
387
- ? await replayPullRequestSummary(resolved.taskId, operation, options.agent, resolved.repoRoot, options.client)
376
+ ? await replayPullRequestSummary(resolved.taskId, targetOperation, { agent: options.agent, cwd: resolved.repoRoot, client: options.client })
388
377
  : operation.kind === 'pull-request-review'
389
378
  ? await replayPullRequestReview(operation, options.agent, resolved.repoRoot, options.client)
390
379
  : operation.kind === 'issue-metadata'
391
380
  ? await replayIssueMetadata(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
392
381
  : operation.kind === 'pull-request'
393
382
  ? await replayPullRequest(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
394
- : await replayComment(resolved.taskId, resolved.taskDir, operation, options.agent, resolved.repoRoot);
383
+ : await replayComment(resolved.taskId, resolved.taskDir, targetOperation, options.agent, resolved.repoRoot, options.client);
395
384
  }
396
385
  catch (error) {
397
386
  const value = error as { code?: string; message?: string };
398
387
  remote = platformResult('blocked', { error: { code: value.code || 'PLATFORM_OPERATION_RECOVERY_FAILED', message: value.message || String(error), retryable: true } });
399
388
  }
400
389
  const succeeded = (remote.status === 'applied' || remote.status === 'no-op') && !remote.error;
390
+ const latestOperation = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.find((item) => item.id === operation.id);
401
391
  try {
402
392
  recordPlatformOperation({
403
393
  taskRef: resolved.taskId,
@@ -412,7 +402,7 @@ async function recoverPlatformOperationsOnce(
412
402
  ...(operation.pullRequestReview ? { pullRequestReview: operation.pullRequestReview } : {}),
413
403
  dependency: operation.dependency,
414
404
  state: succeeded ? 'succeeded' : 'unknown',
415
- lastCode: remote.error?.code ?? null
405
+ lastCode: remote.error?.code ?? latestOperation?.lastCode ?? null
416
406
  });
417
407
  } catch (error) {
418
408
  const value = error as { code?: string; message?: string };
@@ -442,9 +432,7 @@ async function recoverPlatformOperations(
442
432
  const value = error as { code?: string; message?: string };
443
433
  return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
444
434
  }
445
- const eligible = (operation: typeof initial.operations[number]) => operation.id !== options.excludeId
446
- && (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
447
- || (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'));
435
+ const eligible = (operation: typeof initial.operations[number]) => isRecoveryEligible(operation, options.excludeId);
448
436
  const first = initial.operations.filter(eligible);
449
437
  if (!first.length) return result('no-op', [], []);
450
438
  const operationLimit = options.limit === undefined ? first.length : Math.max(1, options.limit);