@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
@@ -7,19 +7,19 @@ import { validateCurrentTaskContract } from './current-contract.js';
7
7
  import { parseReviewSummary } from './review-artifacts.js';
8
8
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
9
9
  import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
10
+ import { resolveProjectTaskWorkspaceRoot } from './workspace-root.js';
10
11
  import { activateDelegation, abortActivatedDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
11
12
  import { normalizeAgentClients } from '../agent-clients/config.js';
12
13
  import { getAgentClientAdapter, getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
13
14
  import { isAgentClientId } from '../agent-clients/types.js';
14
15
  import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
15
16
  import { assertGitRepositoryBinding } from '../git/worktree-identity.js';
16
- import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
17
17
  import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.js';
18
18
  import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.js';
19
19
  import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js';
20
20
  import { hasOpenLifecycleExecution } from './activity-log.js';
21
21
  import { normalizeAgentToken } from '../agent-clients/tokens.js';
22
- import { resolveArtifactContext } from './artifact-lifecycle.js';
22
+ import { resolveArtifactContextForResolvedTask } from './artifact-lifecycle.js';
23
23
  function supportsLifecycleDelegation(client) {
24
24
  return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
25
25
  && getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
@@ -274,15 +274,9 @@ function mutateDelegationAdapterEvidence(taskRef, receiptId, adapterId, mutate,
274
274
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
275
275
  if (!resolved.ok)
276
276
  throw new Error(`${resolved.code}: ${resolved.message}`);
277
- return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, false);
277
+ return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options);
278
278
  }
279
- function mutateDelegationAdapterEvidenceWithinTaskLock(taskRef, receiptId, adapterId, mutate, options = {}) {
280
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
281
- if (!resolved.ok)
282
- throw new Error(`${resolved.code}: ${resolved.message}`);
283
- return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, true);
284
- }
285
- function mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, taskLockHeld) {
279
+ function mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options) {
286
280
  const update = () => {
287
281
  try {
288
282
  const run = readRun(resolved.taskDir, options);
@@ -316,9 +310,7 @@ function mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterI
316
310
  }
317
311
  };
318
312
  let mutationError;
319
- const result = taskLockHeld
320
- ? update()
321
- : withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `agent-adapter.${adapterId}.evidence`, update);
313
+ const result = update();
322
314
  if (mutationError)
323
315
  throw mutationError;
324
316
  return result;
@@ -542,10 +534,7 @@ function routeFromFacts(facts) {
542
534
  const role = action.startsWith('review-') ? 'reviewer' : 'executor';
543
535
  return { action: command, role, stage: action };
544
536
  }
545
- function routeOrchestration(taskRef, options = {}) {
546
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
547
- if (!resolved.ok)
548
- return failed(resolved.code, resolved.message, resolved.taskId);
537
+ function routeResolvedTask(taskRef, resolved, options) {
549
538
  let gitRoot;
550
539
  try {
551
540
  gitRoot = gitRootFor(resolved.repoRoot, options);
@@ -584,7 +573,7 @@ function routeOrchestration(taskRef, options = {}) {
584
573
  catch (error) {
585
574
  return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
586
575
  }
587
- const facts = buildLifecycleFacts(resolved.taskDir, content, resolved.state, hasOpenLifecycleExecution(content) || Boolean(run?.pendingDelegation));
576
+ const facts = buildLifecycleFacts(resolved.taskDir, content, resolved.state, hasOpenLifecycleExecution(content) || Boolean(run?.pendingDelegation), resolved.repoRoot);
588
577
  if (!facts.ok)
589
578
  return failed('ORCHESTRATION_CAPABILITY_FACTS_INVALID', facts.message, resolved.taskId);
590
579
  const routed = routeFromFacts(facts.facts);
@@ -674,7 +663,7 @@ function routeOrchestration(taskRef, options = {}) {
674
663
  }
675
664
  const action = routed.stage;
676
665
  const family = action === 'analysis' ? 'analysis' : action;
677
- const artifactContext = resolveArtifactContext(taskRef, family, {
666
+ const artifactContext = resolveArtifactContextForResolvedTask(resolved, family, {
678
667
  repoRoot: resolved.repoRoot
679
668
  });
680
669
  if (artifactContext.status !== 'ready' || !artifactContext.selection) {
@@ -700,6 +689,12 @@ function routeOrchestration(taskRef, options = {}) {
700
689
  };
701
690
  return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
702
691
  }
692
+ function routeOrchestration(taskRef, options = {}) {
693
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
694
+ if (!resolved.ok)
695
+ return failed(resolved.code, resolved.message, resolved.taskId);
696
+ return routeResolvedTask(taskRef, resolved, options);
697
+ }
703
698
  function statusOrchestration(taskRef, options = {}) {
704
699
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
705
700
  if (!resolved.ok)
@@ -743,7 +738,7 @@ function validateDelegationRun(taskRef, taskId, run, options) {
743
738
  }
744
739
  return null;
745
740
  }
746
- function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
741
+ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
747
742
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
748
743
  if (!resolved.ok)
749
744
  return failed(resolved.code, resolved.message, resolved.taskId);
@@ -763,7 +758,7 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
763
758
  if (runFailure)
764
759
  return runFailure;
765
760
  const activeRun = run;
766
- const routed = routeOrchestration(taskRef, options);
761
+ const routed = routeResolvedTask(taskRef, resolved, options);
767
762
  if (!routed.next)
768
763
  return routed;
769
764
  const next = routed.next;
@@ -797,20 +792,7 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
797
792
  saveRun(resolved.taskDir, updated);
798
793
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
799
794
  }
800
- function prepareOrchestrationDelegation(taskRef, input, options = {}) {
801
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
802
- if (!resolved.ok)
803
- return failed(resolved.code, resolved.message, resolved.taskId);
804
- try {
805
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.prepare.task', () => prepareOrchestrationDelegationUnlocked(taskRef, input, options));
806
- }
807
- catch (error) {
808
- if (error instanceof TaskExecutionLockError)
809
- return failed(error.code, error.message, resolved.taskId);
810
- throw error;
811
- }
812
- }
813
- function dispatchOrchestrationDelegationUnlocked(taskRef, options = {}) {
795
+ function dispatchOrchestrationDelegation(taskRef, options = {}) {
814
796
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
815
797
  if (!resolved.ok)
816
798
  return failed(resolved.code, resolved.message, resolved.taskId);
@@ -830,19 +812,6 @@ function dispatchOrchestrationDelegationUnlocked(taskRef, options = {}) {
830
812
  saveRun(resolved.taskDir, updated);
831
813
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
832
814
  }
833
- function dispatchOrchestrationDelegation(taskRef, options = {}) {
834
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
835
- if (!resolved.ok)
836
- return failed(resolved.code, resolved.message, resolved.taskId);
837
- try {
838
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.dispatch.task', () => dispatchOrchestrationDelegationUnlocked(taskRef, options));
839
- }
840
- catch (error) {
841
- if (error instanceof TaskExecutionLockError)
842
- return failed(error.code, error.message, resolved.taskId);
843
- throw error;
844
- }
845
- }
846
815
  function matchingDelegations(predicate, options) {
847
816
  const taskId = options.taskId;
848
817
  if (taskId === undefined || taskId === '') {
@@ -852,7 +821,7 @@ function matchingDelegations(predicate, options) {
852
821
  return { ok: false, error: failed('ORCHESTRATION_TASK_ID_INVALID', 'taskId must be a canonical TASK-id') };
853
822
  }
854
823
  const repoRoot = options.repoRoot ?? process.cwd();
855
- const taskDir = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
824
+ const taskDir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId);
856
825
  const run = readRun(taskDir, options);
857
826
  const matches = run && run.status === 'running' && run.pendingDelegation && predicate(run.pendingDelegation)
858
827
  ? [{ taskId, run }]
@@ -1033,7 +1002,7 @@ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
1033
1002
  if (!resolved.ok)
1034
1003
  return failed(resolved.code, resolved.message, resolved.taskId);
1035
1004
  try {
1036
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.recover-prepared', () => {
1005
+ return (() => {
1037
1006
  const run = readRun(resolved.taskDir, options);
1038
1007
  const receipt = run?.pendingDelegation;
1039
1008
  if (!run || !receipt) {
@@ -1073,11 +1042,9 @@ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
1073
1042
  }, options.now);
1074
1043
  saveRun(resolved.taskDir, updated);
1075
1044
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1076
- });
1045
+ })();
1077
1046
  }
1078
1047
  catch (error) {
1079
- if (error instanceof TaskExecutionLockError)
1080
- return failed(error.code, error.message, resolved.taskId);
1081
1048
  return failed('ORCHESTRATION_PREPARED_RECOVERY_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
1082
1049
  }
1083
1050
  }
@@ -1088,7 +1055,7 @@ function recoveryReceiptMatches(receipt, event) {
1088
1055
  && receipt.childId === event.childId
1089
1056
  && normalizeAgentToken(event.startedAgent) === normalizeAgentToken(receipt.client);
1090
1057
  }
1091
- function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, options = {}) {
1058
+ function recoverActivatedOrchestrationDelegation(taskRef, event, options = {}) {
1092
1059
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1093
1060
  if (!resolved.ok)
1094
1061
  return failed(resolved.code, resolved.message, resolved.taskId);
@@ -1204,7 +1171,7 @@ function sealOrchestrationDelegation(taskRef, event, expected, options = {}, bef
1204
1171
  const resolved = resolveTaskRef(taskRef, { repoRoot: orchestrationOptions.repoRoot });
1205
1172
  if (!resolved.ok)
1206
1173
  return failed(resolved.code, resolved.message, resolved.taskId);
1207
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.seal', () => {
1174
+ return (() => {
1208
1175
  const run = readRun(resolved.taskDir, orchestrationOptions);
1209
1176
  if (!run?.pendingDelegation)
1210
1177
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
@@ -1226,7 +1193,7 @@ function sealOrchestrationDelegation(taskRef, event, expected, options = {}, bef
1226
1193
  const updated = withUpdatedRun(current, { pendingDelegation: result.receipt });
1227
1194
  saveRun(resolved.taskDir, updated);
1228
1195
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1229
- });
1196
+ })();
1230
1197
  }
1231
1198
  function advanceOrchestration(taskRef, options = {}) {
1232
1199
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
@@ -1262,5 +1229,5 @@ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
1262
1229
  saveRun(resolved.taskDir, updated);
1263
1230
  return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1264
1231
  }
1265
- export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, orchestrationPath, readDelegationAdapterEvidence, mutateDelegationAdapterEvidence, mutateDelegationAdapterEvidenceWithinTaskLock, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverPreparedOrchestrationDelegation, recoverActivatedOrchestrationDelegationUnderLock, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration, };
1232
+ export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, orchestrationPath, readDelegationAdapterEvidence, mutateDelegationAdapterEvidence, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverPreparedOrchestrationDelegation, recoverActivatedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration, };
1266
1233
  //# sourceMappingURL=orchestration.js.map
@@ -2,13 +2,15 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { execFileSync } from 'node:child_process';
5
- import { inspectPlatformCommentOperation, normalizeCommentContent, syncPlatformComment } from '../platform/issue-comments.js';
5
+ import { inspectPlatformCommentOperation, syncPlatformComment } from '../platform/issue-comments.js';
6
+ import { parseDeliverySummaryContent, renderDeliverySummaryContent, parseCancelSummaryContent, renderCancelSummaryContent } from '../platform/issue-summary-content.js';
7
+ import { enumerateArtifacts } from './artifacts.js';
6
8
  import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.js';
7
9
  import { resolvePlatformProviderContext } from '../platform/context.js';
8
10
  import { providerOperationContext, providerError, unsupportedProviderOperation } from '../platform/provider-bridge.js';
9
11
  import { inLabelMappingDigest } from '../platform/in-label-sync.js';
10
- import { bindPlatformPullRequest, inspectPlatformPullRequest, recoverCreatedPullRequest, syncPlatformPullRequest } from '../platform/pull-requests.js';
11
- import { syncPullRequestSummary } from '../platform/pr-summary.js';
12
+ import { bindPlatformPullRequest, recoverCreatedPullRequest, syncPlatformPullRequest } from '../platform/pull-requests.js';
13
+ import { replayPullRequestSummary } from '../platform/pr-summary.js';
12
14
  import { publishPrReview, readReviewBodyFile } from '../platform/pr-review.js';
13
15
  import { parseTaskFrontmatter } from './frontmatter.js';
14
16
  import { taskIssueIdentity } from '../platform/task-identities.js';
@@ -35,32 +37,70 @@ function summaryPayload(taskDir, taskId) {
35
37
  if (!fs.existsSync(file))
36
38
  return null;
37
39
  const data = JSON.parse(fs.readFileSync(file, 'utf8'));
38
- const canonical = typeof data.body === 'string' ? canonicalizeSummaryBody(data.body) : null;
39
- if (data.taskId !== taskId || typeof data.body !== 'string' || typeof data.sha256 !== 'string'
40
- || !canonical?.ok || canonical.value !== data.body
41
- || createHash('sha256').update(data.body).digest('hex') !== data.sha256) {
40
+ let content;
41
+ try {
42
+ content = parseDeliverySummaryContent(data.content);
43
+ }
44
+ catch {
45
+ content = null;
46
+ }
47
+ if (data.taskId !== taskId || !content || content.taskId !== taskId || typeof data.contentSha256 !== 'string'
48
+ || createHash('sha256').update(JSON.stringify(content)).digest('hex') !== data.contentSha256) {
42
49
  throw Object.assign(new Error('delivery summary staging record is invalid'), { code: 'SUMMARY_STAGING_INVALID' });
43
50
  }
44
- return { body: data.body, sha256: data.sha256 };
51
+ const canonical = canonicalizeSummaryBody(renderDeliverySummaryContent(content));
52
+ if (!canonical?.ok)
53
+ throw Object.assign(new Error('delivery summary staging body is not canonical'), { code: 'SUMMARY_STAGING_INVALID' });
54
+ return { body: canonical.value, sha256: createHash('sha256').update(canonical.value).digest('hex') };
55
+ }
56
+ function cancellationPayload(taskDir, taskId) {
57
+ const taskPath = path.join(taskDir, 'task.md');
58
+ if (!fs.existsSync(taskPath))
59
+ return null;
60
+ const taskContent = fs.readFileSync(taskPath, 'utf8');
61
+ const frontmatter = parseTaskFrontmatter(taskContent);
62
+ const title = /^#\s+(.+)$/mu.exec(taskContent)?.[1]?.trim() || taskId;
63
+ const artifacts = enumerateArtifacts(taskDir).filter((artifact) => artifact.name !== 'task.md').map((artifact) => artifact.name);
64
+ const content = parseCancelSummaryContent({
65
+ taskId,
66
+ title,
67
+ outcome: 'cancelled',
68
+ reason: frontmatter.cancel_reason,
69
+ cancelledAt: frontmatter.cancelled_at,
70
+ lastCompletedArtifact: artifacts.at(-1) ?? null
71
+ });
72
+ return renderCancelSummaryContent(content);
45
73
  }
46
74
  async function replayComment(taskId, taskDir, operation, agent, cwd, client) {
47
75
  if (operation.kind === 'task-comment') {
48
- return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency, skipQueue: true });
76
+ return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
49
77
  }
50
78
  if (operation.kind === 'artifact-comment') {
51
- return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency, skipQueue: true });
79
+ return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
52
80
  }
53
81
  if (operation.kind === 'summary-comment') {
54
- const summary = summaryPayload(taskDir, taskId);
82
+ let summary;
83
+ try {
84
+ summary = summaryPayload(taskDir, taskId);
85
+ }
86
+ catch (error) {
87
+ return platformResult('failed', { error: { code: 'SUMMARY_STAGING_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } });
88
+ }
55
89
  if (!summary)
56
- return platformResult('failed', { error: { code: 'SUMMARY_STAGING_MISSING', message: 'delivery summary staging record is unavailable', retryable: false } });
57
- return syncPlatformComment(taskId, {
58
- kind: 'summary', body: summary.body, agent, cwd,
59
- summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency, client, skipQueue: true
60
- });
90
+ return platformResult('blocked', { error: { code: 'SUMMARY_STAGING_MISSING', message: 'delivery summary staging record is unavailable', retryable: true } });
91
+ return syncPlatformComment(taskId, { kind: 'summary', body: summary.body, agent, cwd, client, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency, queuedOperation: operation });
61
92
  }
62
93
  if (operation.kind === 'cancel-comment') {
63
- return syncPlatformComment(taskId, { kind: 'cancel', agent, cwd, client, dependency: operation.dependency, skipQueue: true });
94
+ let body;
95
+ try {
96
+ body = cancellationPayload(taskDir, taskId);
97
+ }
98
+ catch (error) {
99
+ return platformResult('failed', { error: { code: 'CANCEL_SUMMARY_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } });
100
+ }
101
+ if (!body)
102
+ return platformResult('blocked', { error: { code: 'CANCEL_SUMMARY_INVALID', message: 'Cancellation facts are unavailable for recovery', retryable: true } });
103
+ return syncPlatformComment(taskId, { kind: 'cancel', body, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
64
104
  }
65
105
  return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_UNSUPPORTED', message: `operation kind '${operation.kind}' has no registered recovery handler`, retryable: false } });
66
106
  }
@@ -185,65 +225,6 @@ async function verifyIssueCreate(taskId, operation, cwd, client) {
185
225
  } });
186
226
  return platformResult('no-op');
187
227
  }
188
- async function replayPullRequestSummary(taskId, operation, agent, cwd, client) {
189
- let identity;
190
- try {
191
- identity = parseResourceIdentity(JSON.parse(operation.target), 'queued pull-request summary identity');
192
- }
193
- catch {
194
- return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary identity is invalid', retryable: false } });
195
- }
196
- const summary = operation.pullRequestSummary;
197
- if (!summary)
198
- return platformResult('blocked', { error: {
199
- code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request summary recovery content is missing', retryable: false
200
- } });
201
- const manualValidationHeading = /^###\s+(?:⏳\s+(?:Manual Validation Pending|人工验证待收尾)|✅\s+(?:Manual Validation Passed|人工验证已通过))\s*$/mu.test(summary.body);
202
- if (manualValidationHeading && !summary.manualValidation)
203
- return platformResult('blocked', { error: {
204
- code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'Queued manual-validation summary has no persisted transaction context', retryable: false
205
- } });
206
- const resolved = await resolvePlatformProviderContext({ cwd, client });
207
- if (!resolved.ok)
208
- return resolved.context;
209
- const listed = resolved.value.provider.comments?.list
210
- ? await resolved.value.provider.comments.list({ context: providerOperationContext(resolved.value), parent: identity })
211
- : unsupportedProviderOperation(resolved.value.provider, 'comments.list');
212
- if (!listed.ok)
213
- return platformResult(providerErrorRetryable(listed.error.code) ? 'blocked' : 'failed', { error: providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED') });
214
- const marker = `<!-- sync-pr:${taskId}:summary -->`;
215
- const summaries = listed.value.filter((comment) => normalizeCommentContent(comment.body).split('\n', 1)[0] === marker);
216
- if (summaries.length > 1)
217
- return platformResult('blocked', { error: {
218
- code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple pull-request comments contain the queued summary marker', retryable: false
219
- } });
220
- if (!summary.manualValidation && summaries.length === 1 && createHash('sha256').update(normalizeCommentContent(summaries[0].body)).digest('hex') === operation.expectedDigest) {
221
- return platformResult('no-op');
222
- }
223
- const bound = await inspectPlatformPullRequest(taskId, { cwd, client });
224
- if (!bound.pullRequest || !resourceIdentityEquals(bound.pullRequest.identity, identity))
225
- return platformResult('blocked', { error: bound.error ?? {
226
- code: 'PLATFORM_OPERATION_IDENTITY_MISMATCH', message: 'Bound pull request differs from the queued summary target', retryable: false
227
- } });
228
- const replayed = await syncPullRequestSummary(taskId, {
229
- agent,
230
- cwd,
231
- client,
232
- body: summary.body,
233
- changeReportFile: summary.changeReportFile,
234
- ...(summary.manualValidation ? { manualValidation: summary.manualValidation } : {}),
235
- primaryResult: 'no_op',
236
- strict: true,
237
- skipQueue: true,
238
- lockAlreadyHeld: true
239
- });
240
- if ((replayed.status === 'applied' || replayed.status === 'no-op') && !replayed.error && replayed.warnings.length === 0) {
241
- return platformResult(replayed.status, { changed: replayed.changed });
242
- }
243
- return platformResult('blocked', { error: replayed.error ?? {
244
- code: 'PR_SUMMARY_RECOVERY_UNCONFIRMED', message: 'Pull-request summary replay did not confirm the requested remote state', retryable: true
245
- } });
246
- }
247
228
  async function replayPullRequestReview(operation, agent, cwd, client) {
248
229
  const intent = operation.pullRequestReview;
249
230
  if (!intent)
@@ -302,6 +283,14 @@ async function replayPullRequest(taskId, operation, agent, cwd) {
302
283
  }
303
284
  return recoverCreatedPullRequest(taskId, { agent, cwd, base: intent.baseRef || '', head: intent.headRef || '', skipQueue: true });
304
285
  }
286
+ function isRecoveryEligible(operation, excludeId) {
287
+ if (operation.id === excludeId)
288
+ return false;
289
+ if (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown')
290
+ return true;
291
+ return operation.state === 'failed'
292
+ && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED';
293
+ }
305
294
  async function recoverPlatformOperationsOnce(taskRef, selection, options) {
306
295
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
307
296
  if (!resolved.ok)
@@ -314,9 +303,7 @@ async function recoverPlatformOperationsOnce(taskRef, selection, options) {
314
303
  const value = error;
315
304
  return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
316
305
  }
317
- const candidates = journal.operations.filter((operation) => operation.id !== options.excludeId
318
- && (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
319
- || (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED')));
306
+ const candidates = journal.operations.filter((operation) => isRecoveryEligible(operation, options.excludeId));
320
307
  if (!candidates.length)
321
308
  return result('no-op', [], []);
322
309
  const head = candidates[0];
@@ -383,9 +370,22 @@ async function recoverPlatformOperationsOnce(taskRef, selection, options) {
383
370
  : operation.kind === 'artifact-comment'
384
371
  ? { kind: 'artifact', artifact: operation.target, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
385
372
  : operation.kind === 'summary-comment' ? (() => {
386
- const summary = summaryPayload(resolved.taskDir, resolved.taskId);
387
- return summary ? { kind: 'summary', body: summary.body, agent: options.agent, cwd: resolved.repoRoot, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency } : null;
388
- })() : { kind: 'cancel', agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency };
373
+ try {
374
+ const summary = summaryPayload(resolved.taskDir, resolved.taskId);
375
+ return summary ? { kind: 'summary', body: summary.body, agent: options.agent, cwd: resolved.repoRoot, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency } : null;
376
+ }
377
+ catch {
378
+ return null;
379
+ }
380
+ })() : (() => {
381
+ try {
382
+ const body = cancellationPayload(resolved.taskDir, resolved.taskId);
383
+ return body ? { kind: 'cancel', body, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency } : null;
384
+ }
385
+ catch {
386
+ return null;
387
+ }
388
+ })();
389
389
  const current = commentOptions ? inspectPlatformCommentOperation(resolved.taskId, commentOptions) : null;
390
390
  if (!current) {
391
391
  pending.push(operation.id);
@@ -408,20 +408,21 @@ async function recoverPlatformOperationsOnce(taskRef, selection, options) {
408
408
  remote = operation.kind === 'issue-create'
409
409
  ? await verifyIssueCreate(resolved.taskId, operation, resolved.repoRoot)
410
410
  : operation.kind === 'pull-request-summary'
411
- ? await replayPullRequestSummary(resolved.taskId, operation, options.agent, resolved.repoRoot, options.client)
411
+ ? await replayPullRequestSummary(resolved.taskId, targetOperation, { agent: options.agent, cwd: resolved.repoRoot, client: options.client })
412
412
  : operation.kind === 'pull-request-review'
413
413
  ? await replayPullRequestReview(operation, options.agent, resolved.repoRoot, options.client)
414
414
  : operation.kind === 'issue-metadata'
415
415
  ? await replayIssueMetadata(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
416
416
  : operation.kind === 'pull-request'
417
417
  ? await replayPullRequest(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
418
- : await replayComment(resolved.taskId, resolved.taskDir, operation, options.agent, resolved.repoRoot);
418
+ : await replayComment(resolved.taskId, resolved.taskDir, targetOperation, options.agent, resolved.repoRoot, options.client);
419
419
  }
420
420
  catch (error) {
421
421
  const value = error;
422
422
  remote = platformResult('blocked', { error: { code: value.code || 'PLATFORM_OPERATION_RECOVERY_FAILED', message: value.message || String(error), retryable: true } });
423
423
  }
424
424
  const succeeded = (remote.status === 'applied' || remote.status === 'no-op') && !remote.error;
425
+ const latestOperation = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.find((item) => item.id === operation.id);
425
426
  try {
426
427
  recordPlatformOperation({
427
428
  taskRef: resolved.taskId,
@@ -436,7 +437,7 @@ async function recoverPlatformOperationsOnce(taskRef, selection, options) {
436
437
  ...(operation.pullRequestReview ? { pullRequestReview: operation.pullRequestReview } : {}),
437
438
  dependency: operation.dependency,
438
439
  state: succeeded ? 'succeeded' : 'unknown',
439
- lastCode: remote.error?.code ?? null
440
+ lastCode: remote.error?.code ?? latestOperation?.lastCode ?? null
440
441
  });
441
442
  }
442
443
  catch (error) {
@@ -465,9 +466,7 @@ async function recoverPlatformOperations(taskRef, selection, options) {
465
466
  const value = error;
466
467
  return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
467
468
  }
468
- const eligible = (operation) => operation.id !== options.excludeId
469
- && (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
470
- || (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'));
469
+ const eligible = (operation) => isRecoveryEligible(operation, options.excludeId);
471
470
  const first = initial.operations.filter(eligible);
472
471
  if (!first.length)
473
472
  return result('no-op', [], []);
@@ -3,10 +3,12 @@ import path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
4
  import { normalizeShortIdInput, resolveShortIdReadOnly } from './short-id.js';
5
5
  import { parseTaskFrontmatter } from './frontmatter.js';
6
+ import { resolveProjectTaskWorkspaceRoot } from './workspace-root.js';
6
7
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
7
8
  // Flat-structured workspace dirs that hold tasks under `{dir}/{taskId}/task.md`.
8
9
  // Note: `archive` uses a three-level YYYY/MM/DD layout and is handled separately.
9
10
  const FLAT_WORKSPACE_DIRS = ['active', 'blocked', 'completed'];
11
+ function taskRoot(repoRoot) { return resolveProjectTaskWorkspaceRoot(repoRoot); }
10
12
  function detectRepoRoot() {
11
13
  try {
12
14
  return execFileSync('git', ['rev-parse', '--show-toplevel'], {
@@ -40,12 +42,12 @@ function listSortedNumeric(dir, width) {
40
42
  .sort()
41
43
  .reverse();
42
44
  }
43
- function findInArchive(repoRoot, taskId) {
44
- // archive-tasks stores local material at .agents/workspace/archive/YYYY/MM/DD/{taskId}/local/task.md
45
+ function findInArchive(workspaceRoot, taskId) {
46
+ // archive-tasks stores local material at the project task workspace archive/YYYY/MM/DD/{taskId}/local/task.md
45
47
  // where YYYY/MM/DD comes from completed_at (or updated_at fallback) — NOT from
46
48
  // the task id's creation date. So we cannot derive the path from taskId alone;
47
49
  // walk the bounded YYYY/MM/DD tree instead. Newest-first to favor recent archives.
48
- const archiveDir = path.join(repoRoot, '.agents', 'workspace', 'archive');
50
+ const archiveDir = path.join(workspaceRoot, 'archive');
49
51
  for (const year of listSortedNumeric(archiveDir, 4)) {
50
52
  const yearDir = path.join(archiveDir, year);
51
53
  for (const month of listSortedNumeric(yearDir, 2)) {
@@ -59,13 +61,13 @@ function findInArchive(repoRoot, taskId) {
59
61
  }
60
62
  return null;
61
63
  }
62
- function findTaskMd(repoRoot, taskId) {
64
+ function findTaskMd(workspaceRoot, taskId) {
63
65
  for (const sub of FLAT_WORKSPACE_DIRS) {
64
- const candidate = path.join(repoRoot, '.agents', 'workspace', sub, taskId, 'task.md');
66
+ const candidate = path.join(workspaceRoot, sub, taskId, 'task.md');
65
67
  if (fs.existsSync(candidate))
66
68
  return { taskMdPath: candidate, state: sub };
67
69
  }
68
- const archived = findInArchive(repoRoot, taskId);
70
+ const archived = findInArchive(workspaceRoot, taskId);
69
71
  return archived ? { taskMdPath: archived, state: 'archive' } : null;
70
72
  }
71
73
  /**
@@ -76,8 +78,9 @@ function findTaskMd(repoRoot, taskId) {
76
78
  */
77
79
  function enumerateTaskDirs(repoRoot) {
78
80
  const out = [];
81
+ const workspaceRoot = taskRoot(repoRoot);
79
82
  for (const sub of FLAT_WORKSPACE_DIRS) {
80
- const base = path.join(repoRoot, '.agents', 'workspace', sub);
83
+ const base = path.join(workspaceRoot, sub);
81
84
  if (!fs.existsSync(base))
82
85
  continue;
83
86
  for (const entry of fs.readdirSync(base).sort()) {
@@ -98,6 +101,7 @@ function enumerateTaskDirs(repoRoot) {
98
101
  */
99
102
  function enumerateAllTaskDirs(repoRoot) {
100
103
  const out = [];
104
+ const workspaceRoot = taskRoot(repoRoot);
101
105
  const seen = new Map();
102
106
  const add = (taskId, taskDir, state) => {
103
107
  if (!TASK_ID_RE.test(taskId))
@@ -116,13 +120,13 @@ function enumerateAllTaskDirs(repoRoot) {
116
120
  out.push({ taskId, taskDir, state });
117
121
  };
118
122
  for (const state of FLAT_WORKSPACE_DIRS) {
119
- const base = path.join(repoRoot, '.agents', 'workspace', state);
123
+ const base = path.join(workspaceRoot, state);
120
124
  if (!fs.existsSync(base))
121
125
  continue;
122
126
  for (const taskId of fs.readdirSync(base).sort())
123
127
  add(taskId, path.join(base, taskId), state);
124
128
  }
125
- const archive = path.join(repoRoot, '.agents', 'workspace', 'archive');
129
+ const archive = path.join(workspaceRoot, 'archive');
126
130
  for (const year of listSortedNumeric(archive, 4).reverse()) {
127
131
  const yearDir = path.join(archive, year);
128
132
  for (const month of listSortedNumeric(yearDir, 2).reverse()) {
@@ -137,8 +141,9 @@ function enumerateAllTaskDirs(repoRoot) {
137
141
  return out;
138
142
  }
139
143
  function locateHotTaskDirs(repoRoot, taskId) {
144
+ const workspaceRoot = taskRoot(repoRoot);
140
145
  return FLAT_WORKSPACE_DIRS.flatMap((state) => {
141
- const taskDir = path.join(repoRoot, '.agents', 'workspace', state, taskId);
146
+ const taskDir = path.join(workspaceRoot, state, taskId);
142
147
  const taskMdPath = path.join(taskDir, 'task.md');
143
148
  return fs.existsSync(taskMdPath) ? [{ taskId, taskDir, taskMdPath, state }] : [];
144
149
  });
@@ -203,7 +208,7 @@ function resolveTaskRef(arg, options = {}) {
203
208
  }
204
209
  taskId = shortResult.taskId;
205
210
  }
206
- const located = findTaskMd(repoRoot, taskId);
211
+ const located = findTaskMd(taskRoot(repoRoot), taskId);
207
212
  if (!located) {
208
213
  return {
209
214
  ok: false,
@@ -255,7 +260,7 @@ function resolveTaskContext(taskRef, options = {}) {
255
260
  taskId: null
256
261
  };
257
262
  }
258
- const activeDir = path.join(repoRoot, '.agents', 'workspace', 'active');
263
+ const activeDir = path.join(taskRoot(repoRoot), 'active');
259
264
  const matches = [];
260
265
  if (fs.existsSync(activeDir)) {
261
266
  for (const entry of fs.readdirSync(activeDir).sort()) {