@fitlab-ai/agent-infra 0.11.8 → 0.11.10

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 (163) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +0 -6
  4. package/dist/lib/agent-clients/custom-tool-invocations.js +92 -0
  5. package/dist/lib/agent-clients/next-steps.js +1 -1
  6. package/dist/lib/agent-clients/reconcile.js +3 -3
  7. package/dist/lib/agent-clients/schema.js +18 -10
  8. package/dist/lib/git/workflow.js +1 -1
  9. package/dist/lib/init.js +2 -2
  10. package/dist/lib/internal/agent-client.js +3 -3
  11. package/dist/lib/internal/cli-route-inventory.js +0 -3
  12. package/dist/lib/internal/sandbox-control.js +30 -5
  13. package/dist/lib/internal/task-finalization.js +14 -23
  14. package/dist/lib/internal/task-operation-registry.js +2 -3
  15. package/dist/lib/internal/task-platform-recovery.js +9 -4
  16. package/dist/lib/platform/completion-backfill.js +2 -1
  17. package/dist/lib/platform/context.js +4 -2
  18. package/dist/lib/platform/github-client.js +20 -17
  19. package/dist/lib/platform/github-provider.js +42 -22
  20. package/dist/lib/platform/github-release-notes.js +4 -2
  21. package/dist/lib/platform/in-label-sync.js +2 -1
  22. package/dist/lib/platform/issue-comments.js +13 -9
  23. package/dist/lib/platform/operation-coordinator.js +1 -1
  24. package/dist/lib/platform/pr-review.js +2 -1
  25. package/dist/lib/platform/provider-bridge.js +4 -5
  26. package/dist/lib/platform/provider-contract.d.ts +3 -6
  27. package/dist/lib/platform/provider-contract.js +3 -15
  28. package/dist/lib/platform/provider-validation.js +9 -4
  29. package/dist/lib/platform/pull-requests.js +4 -2
  30. package/dist/lib/sandbox/control/client.js +54 -6
  31. package/dist/lib/sandbox/control/executor.js +8 -3
  32. package/dist/lib/sandbox/recovery.js +1 -22
  33. package/dist/lib/sandbox/removal.js +0 -1
  34. package/dist/lib/sandbox/tools.js +2 -0
  35. package/dist/lib/task/artifact-command.js +11 -1
  36. package/dist/lib/task/artifact-lifecycle.js +119 -62
  37. package/dist/lib/task/artifact-selection.js +1 -1
  38. package/dist/lib/task/capabilities.js +55 -21
  39. package/dist/lib/task/commands/status.js +0 -27
  40. package/dist/lib/task/commit-identity.js +22 -7
  41. package/dist/lib/task/commit-intent.js +67 -26
  42. package/dist/lib/task/commit-operation.js +19 -8
  43. package/dist/lib/task/completion-facts.js +32 -10
  44. package/dist/lib/task/control-authority.js +2 -4
  45. package/dist/lib/task/current-contract.js +1 -5
  46. package/dist/lib/task/events.js +8 -274
  47. package/dist/lib/task/execution-inputs.js +14 -0
  48. package/dist/lib/task/finalization-envelope.js +4 -0
  49. package/dist/lib/task/finalization.js +100 -129
  50. package/dist/lib/task/frontmatter.js +1 -1
  51. package/dist/lib/task/ledger-intents.js +4 -7
  52. package/dist/lib/task/orchestration.js +11 -10
  53. package/dist/lib/task/platform-operation-journal.js +17 -10
  54. package/dist/lib/task/platform-operation-recovery.js +95 -28
  55. package/dist/lib/task/review-fingerprint.js +4 -26
  56. package/dist/lib/task/verification-engine.js +95 -6
  57. package/dist/lib/task/verification.js +0 -1
  58. package/dist/lib/task/write.js +0 -11
  59. package/dist/lib/update.js +2 -2
  60. package/dist/lifecycle-build-manifest.json +3 -3
  61. package/lib/agent-clients/custom-tool-invocations.ts +129 -0
  62. package/lib/agent-clients/next-steps.ts +3 -3
  63. package/lib/agent-clients/reconcile.ts +3 -3
  64. package/lib/agent-clients/schema.ts +18 -10
  65. package/lib/git/workflow.ts +1 -1
  66. package/lib/init.ts +2 -2
  67. package/lib/internal/agent-client.ts +3 -3
  68. package/lib/internal/cli-route-inventory.ts +0 -2
  69. package/lib/internal/sandbox-control.ts +39 -6
  70. package/lib/internal/task-finalization.ts +16 -24
  71. package/lib/internal/task-operation-registry.ts +2 -3
  72. package/lib/internal/task-platform-recovery.ts +9 -4
  73. package/lib/platform/completion-backfill.ts +2 -1
  74. package/lib/platform/context.ts +8 -4
  75. package/lib/platform/github-client.ts +24 -20
  76. package/lib/platform/github-provider.ts +47 -26
  77. package/lib/platform/github-release-notes.ts +5 -3
  78. package/lib/platform/in-label-sync.ts +2 -1
  79. package/lib/platform/issue-comments.ts +14 -9
  80. package/lib/platform/operation-coordinator.ts +1 -1
  81. package/lib/platform/pr-review.ts +2 -1
  82. package/lib/platform/provider-bridge.ts +10 -9
  83. package/lib/platform/provider-contract.ts +6 -21
  84. package/lib/platform/provider-loader.ts +7 -6
  85. package/lib/platform/provider-validation.ts +30 -8
  86. package/lib/platform/pull-requests.ts +4 -2
  87. package/lib/platform/repository-metadata.ts +3 -2
  88. package/lib/platform/security-alerts.ts +1 -1
  89. package/lib/platform/types.ts +2 -0
  90. package/lib/sandbox/control/client.ts +57 -6
  91. package/lib/sandbox/control/executor.ts +8 -3
  92. package/lib/sandbox/recovery.ts +0 -25
  93. package/lib/sandbox/removal.ts +2 -10
  94. package/lib/sandbox/tool-types.ts +1 -0
  95. package/lib/sandbox/tools.ts +2 -0
  96. package/lib/task/artifact-command.ts +10 -1
  97. package/lib/task/artifact-lifecycle.ts +100 -52
  98. package/lib/task/artifact-selection.ts +1 -1
  99. package/lib/task/capabilities.ts +47 -24
  100. package/lib/task/commands/status.ts +0 -31
  101. package/lib/task/commit-identity.ts +24 -7
  102. package/lib/task/commit-intent.ts +58 -24
  103. package/lib/task/commit-operation.ts +17 -8
  104. package/lib/task/completion-facts.ts +31 -9
  105. package/lib/task/control-authority.ts +1 -4
  106. package/lib/task/current-contract.ts +1 -6
  107. package/lib/task/events.ts +8 -247
  108. package/lib/task/execution-inputs.ts +25 -0
  109. package/lib/task/finalization-envelope.ts +21 -0
  110. package/lib/task/finalization.ts +101 -123
  111. package/lib/task/frontmatter.ts +1 -1
  112. package/lib/task/ledger-intents.ts +4 -7
  113. package/lib/task/orchestration.ts +11 -17
  114. package/lib/task/platform-operation-journal.ts +18 -11
  115. package/lib/task/platform-operation-recovery.ts +90 -31
  116. package/lib/task/review-fingerprint.ts +4 -26
  117. package/lib/task/verification-engine.ts +103 -9
  118. package/lib/task/verification.ts +1 -2
  119. package/lib/task/write.ts +0 -19
  120. package/lib/update.ts +2 -3
  121. package/package.json +1 -1
  122. package/templates/.agents/README.en.md +2 -50
  123. package/templates/.agents/README.zh-CN.md +2 -50
  124. package/templates/.agents/rules/commit-and-pr.en.md +5 -5
  125. package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
  126. package/templates/.agents/rules/decision-qualification.en.md +1 -1
  127. package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
  128. package/templates/.agents/rules/next-step-output.en.md +1 -1
  129. package/templates/.agents/rules/next-step-output.zh-CN.md +1 -1
  130. package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
  131. package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
  132. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +26 -9
  133. package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
  134. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
  135. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
  136. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
  137. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  138. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  139. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  140. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  141. package/templates/.agents/skills/complete-task/SKILL.en.md +15 -12
  142. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
  143. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  144. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  145. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  146. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  147. package/templates/.agents/skills/review-code/reference/output-templates.en.md +15 -6
  148. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +15 -6
  149. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
  150. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
  151. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +2 -268
  152. package/templates/.agents/templates/task.zh-CN.md +0 -12
  153. package/templates/.github/workflows/pr-label.yml +1 -2
  154. package/dist/lib/agent-clients/custom-tuis.js +0 -99
  155. package/dist/lib/internal/task-invalidation.js +0 -63
  156. package/dist/lib/task/intermediate-cleanup.js +0 -327
  157. package/dist/lib/task/invalidation-command.js +0 -88
  158. package/dist/lib/task/invalidation.js +0 -242
  159. package/lib/agent-clients/custom-tuis.ts +0 -149
  160. package/lib/internal/task-invalidation.ts +0 -41
  161. package/lib/task/intermediate-cleanup.ts +0 -393
  162. package/lib/task/invalidation-command.ts +0 -88
  163. package/lib/task/invalidation.ts +0 -313
@@ -619,9 +619,12 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
619
619
  const blocked = [steps.backfill, steps.lifecycle, steps.taskComment, steps.verification, steps.summary].some((step) => step?.status === 'blocked');
620
620
  const warnings = openWarnings(receipt);
621
621
  const postLifecyclePending = receipt.lifecycle === 'done' && (pending.some((step) => step !== 'lifecycle') || receipt.warningProjection === 'pending');
622
- const hardError = error?.code.startsWith('FINALIZATION_') || error?.code === 'TASK_FINALIZATION_RECEIPT_INVALID';
622
+ const terminalVerificationPending = receipt.lifecycle === 'done' && receipt.verification === 'pending';
623
+ const resultError = error ?? (terminalVerificationPending ? steps.verification?.error : null) ?? null;
624
+ const hardError = error?.code.startsWith('FINALIZATION_') || error?.code === 'TASK_FINALIZATION_RECEIPT_INVALID'
625
+ || terminalVerificationPending;
623
626
  return {
624
- status: hardError ? (error?.retryable ? 'blocked' : 'failed') : pending.length === 0 ? 'completed' : postLifecyclePending ? 'completed' : blocked ? 'blocked' : 'failed',
627
+ status: hardError ? (resultError?.retryable ? 'blocked' : 'failed') : pending.length === 0 ? 'completed' : postLifecyclePending ? 'completed' : blocked ? 'blocked' : 'failed',
625
628
  changed,
626
629
  taskId,
627
630
  backfill: steps.backfill ?? null,
@@ -632,7 +635,7 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
632
635
  completedSteps: completedSteps(receipt),
633
636
  pendingSteps: pending,
634
637
  result: hardError
635
- ? (error?.retryable ? 'blocked' : 'failed')
638
+ ? (resultError?.retryable ? 'blocked' : 'failed')
636
639
  : postLifecyclePending
637
640
  ? 'completed_with_warnings'
638
641
  : pending.length === 0
@@ -641,7 +644,7 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
641
644
  ? 'blocked'
642
645
  : 'failed',
643
646
  warnings,
644
- error: hardError ? error : postLifecyclePending ? null : error
647
+ error: hardError ? resultError : postLifecyclePending ? null : resultError
645
648
  };
646
649
  }
647
650
  function preparedResult(taskId, receipt, steps, changed) {
@@ -663,7 +666,6 @@ async function prepareUnderLock(request, taskId, options) {
663
666
  const commentSync = options.commentSync ?? syncPlatformComment;
664
667
  const issueSync = options.issueSync ?? syncPlatformIssue;
665
668
  const issueInspect = options.issueInspect ?? inspectPlatformIssue;
666
- const verify = options.verify ?? verifyTaskEvent;
667
669
  const consumedCapabilities = new Set();
668
670
  let receipt;
669
671
  try {
@@ -676,16 +678,6 @@ async function prepareUnderLock(request, taskId, options) {
676
678
  catch (error) {
677
679
  return failed(taskId, errorOf(error, 'TASK_FINALIZATION_RECEIPT_INVALID'));
678
680
  }
679
- if (options.controlBinding && (receipt.controlBinding?.generation !== options.controlBinding.generation
680
- || receipt.controlBinding.requestId !== options.controlBinding.requestId)) {
681
- const state = resolveTaskRef(taskId, { repoRoot });
682
- if (!(receipt.lifecycle === 'done' && state.ok && state.state === 'completed')) {
683
- return failed(taskId, {
684
- code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
685
- message: 'canonical receipt is not bound to the current sandbox request', retryable: false
686
- });
687
- }
688
- }
689
681
  const preflightState = resolveTaskRef(taskId, { repoRoot });
690
682
  if (options.preflight && preflightState.ok && preflightState.state === 'active') {
691
683
  try {
@@ -708,7 +700,7 @@ async function prepareUnderLock(request, taskId, options) {
708
700
  if (preflightState.ok && preflightState.state === 'active') {
709
701
  const recovery = await recoverPlatformOperations(taskId, 'all', { agent: request.agent, cwd: repoRoot });
710
702
  if (recovery.status !== 'applied' && recovery.status !== 'no-op') {
711
- return failed(taskId, recovery.error ?? {
703
+ return failed(taskId, recovery.error ? { ...recovery.error, retryable: true } : {
712
704
  code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
713
705
  message: 'Pending platform operations remain unresolved',
714
706
  retryable: true
@@ -718,12 +710,10 @@ async function prepareUnderLock(request, taskId, options) {
718
710
  const taskFrontmatter = parseTaskFrontmatter(taskContent);
719
711
  const issueIdentity = taskIssueIdentity(taskFrontmatter);
720
712
  if (issueIdentity) {
721
- const base = typeof taskFrontmatter.delivery_base_ref === 'string' ? taskFrontmatter.delivery_base_ref.trim() : '';
722
713
  let synced;
723
714
  try {
724
715
  synced = await issueSync(taskId, {
725
- agent: request.agent, cwd: repoRoot, requirements: true, issueType: true, fields: true,
726
- ...(base ? { inLabels: 'from-diff', base } : {}), dependency: 'required'
716
+ agent: request.agent, cwd: repoRoot, requirements: true, issueType: true, fields: true, dependency: 'required'
727
717
  });
728
718
  }
729
719
  catch (error) {
@@ -731,8 +721,9 @@ async function prepareUnderLock(request, taskId, options) {
731
721
  return failed(taskId, detail);
732
722
  }
733
723
  const issueOperations = synced.operations;
734
- const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields', ...(base ? ['labels:in'] : [])].includes(operation.name)
735
- && operation.status !== 'applied' && operation.status !== 'no-op');
724
+ const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields'].includes(operation.name)
725
+ && operation.status !== 'applied' && operation.status !== 'no-op'
726
+ && !(operation.name === 'requirements' && operation.status === 'skipped' && operation.reasonCode === 'NO_REQUIREMENTS_ANCHOR'));
736
727
  if ((synced.status !== 'applied' && synced.status !== 'no-op') || synced.error || badOperation) {
737
728
  const error = synced.error ?? {
738
729
  code: badOperation?.reasonCode || 'FINALIZATION_ISSUE_METADATA_UNCONFIRMED',
@@ -756,7 +747,7 @@ async function prepareUnderLock(request, taskId, options) {
756
747
  retryable: true
757
748
  });
758
749
  }
759
- for (const name of ['requirements', 'issue-type', 'fields', ...(base ? ['labels:in'] : [])]) {
750
+ for (const name of ['requirements', 'issue-type', 'fields']) {
760
751
  const operation = issueOperations?.find((candidate) => candidate.name === name);
761
752
  if (!operation)
762
753
  return failed(taskId, {
@@ -779,14 +770,6 @@ async function prepareUnderLock(request, taskId, options) {
779
770
  code: 'FINALIZATION_ISSUE_TYPE_UNCONFIRMED', message: 'Issue Type did not match the verified target', retryable: true
780
771
  });
781
772
  }
782
- else if (name === 'labels:in') {
783
- const expected = Array.isArray(operation.value) ? operation.value : before?.labels;
784
- if (!expected || expected.filter((label) => label.startsWith('in:')).sort().join('\0')
785
- !== remote.labels.filter((label) => label.startsWith('in:')).sort().join('\0'))
786
- return failed(taskId, {
787
- code: 'FINALIZATION_IN_LABELS_UNCONFIRMED', message: 'Issue in-labels did not match the verified target', retryable: true
788
- });
789
- }
790
773
  else {
791
774
  const expected = operation.value && typeof operation.value === 'object'
792
775
  ? operation.value
@@ -907,88 +890,9 @@ async function prepareUnderLock(request, taskId, options) {
907
890
  metadataProvider: () => ({ timestamp: projection.timestamp, agentInfraVersion: projection.agentInfraVersion })
908
891
  })
909
892
  : undefined;
910
- let verification = null;
911
- if (receipt.verification !== 'pending') {
912
- verification = { status: receipt.verification === 'skipped' ? 'skipped' : 'no-op', changed: false, error: null };
913
- }
914
- else {
915
- try {
916
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
917
- const verificationState = resolveTaskRef(taskId, { repoRoot });
918
- const verificationEvent = verificationState.ok && verificationState.state === 'completed'
919
- ? 'complete-task.completed'
920
- : 'complete-task.prepared';
921
- const result = await verify({ taskRef: taskId, event: verificationEvent }, { repoRoot });
922
- verification = verificationStep(result);
923
- if (result.status === 'pass') {
924
- const resolvedTaskWarning = receipt.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
925
- const warnings = applyVerificationWarnings(receipt, result);
926
- receipt = updateReceipt(repoRoot, receipt, {
927
- verification: 'done', taskComment: resolvedTaskWarning ? 'pending' : receipt.taskComment,
928
- warningProjection: warnings.length > 0 ? 'pending' : 'done', warnings, lastError: null
929
- });
930
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
931
- const finalComment = await syncPendingTaskComment({
932
- repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities
933
- });
934
- receipt = finalComment.receipt;
935
- if (finalComment.step.status !== 'no-op')
936
- taskComment = finalComment.step;
937
- changed = changed || finalComment.changed;
938
- if (finalComment.error) {
939
- return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
940
- }
941
- }
942
- else {
943
- const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
944
- const warnings = applyVerificationWarnings(receipt, result);
945
- receipt = updateReceipt(repoRoot, receipt, {
946
- verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
947
- });
948
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
949
- const warningComment = await syncPendingTaskComment({
950
- repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities
951
- });
952
- receipt = warningComment.receipt;
953
- taskComment = warningComment.step;
954
- changed = changed || warningComment.changed;
955
- if (warningComment.error) {
956
- return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, warningComment.error);
957
- }
958
- return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, detail);
959
- }
960
- }
961
- catch (error) {
962
- const detail = errorOf(error, 'VERIFY_FAILED', true);
963
- try {
964
- const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
965
- receipt = updateReceipt(repoRoot, receipt, {
966
- verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
967
- });
968
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
969
- }
970
- catch (writeError) {
971
- const persistence = errorOf(writeError, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
972
- const hardError = {
973
- code: 'FINALIZATION_RECEIPT_WRITE_FAILED',
974
- message: `Unable to persist verification failure (${detail.code}: ${detail.message}): ${persistence.message}`,
975
- retryable: true
976
- };
977
- return terminalResult(taskId, receipt, {
978
- backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail }
979
- }, changed, hardError);
980
- }
981
- try {
982
- const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities });
983
- receipt = warningComment.receipt;
984
- if (warningComment.step.status !== 'no-op')
985
- taskComment = warningComment.step;
986
- changed = changed || warningComment.changed;
987
- }
988
- catch { /* preserve the primary error */ }
989
- return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
990
- }
991
- }
893
+ const verification = receipt.verification === 'done'
894
+ ? { status: 'no-op', changed: false, error: null }
895
+ : { status: 'skipped', changed: false, error: null };
992
896
  let summary = receipt.summary === 'pending'
993
897
  ? await syncPendingSummary({ repoRoot, taskId, agent: request.agent, receipt, commentSync })
994
898
  : { receipt, step: { status: 'no-op', changed: false, error: null }, changed: false, error: null };
@@ -1039,7 +943,7 @@ async function commitPreparedTaskFinalization(request, options) {
1039
943
  if (!resolved.ok)
1040
944
  return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
1041
945
  try {
1042
- return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.commit', () => {
946
+ return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.commit', async () => {
1043
947
  if (options.controlBinding && options.manifestPath) {
1044
948
  try {
1045
949
  const manifest = readSandboxControlManifest(options.manifestPath);
@@ -1065,6 +969,7 @@ async function commitPreparedTaskFinalization(request, options) {
1065
969
  }
1066
970
  }
1067
971
  let receipt = readReceipt(repoRoot, resolved.taskId);
972
+ let importedHandoff = false;
1068
973
  if (!receipt && options.controlBinding && options.handoffDirectory && request.handoffSha256) {
1069
974
  try {
1070
975
  const handoff = readTaskFinalizationHandoff(options.handoffDirectory, resolved.taskId, options.controlBinding, request.handoffSha256);
@@ -1075,6 +980,7 @@ async function commitPreparedTaskFinalization(request, options) {
1075
980
  throw new Error('TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE');
1076
981
  }
1077
982
  writeReceipt(repoRoot, receipt);
983
+ importedHandoff = true;
1078
984
  try {
1079
985
  handoff.cleanup();
1080
986
  }
@@ -1091,32 +997,24 @@ async function commitPreparedTaskFinalization(request, options) {
1091
997
  return failed(resolved.taskId, {
1092
998
  code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation receipt is unavailable', retryable: true
1093
999
  });
1094
- if (receipt.lifecycle === 'done' && resolved.state === 'completed') {
1000
+ if (receipt.lifecycle === 'done' && receipt.verification !== 'pending' && resolved.state === 'completed') {
1095
1001
  return terminalResult(resolved.taskId, receipt, { lifecycle: { status: 'no-op', changed: false, error: null } }, false);
1096
1002
  }
1097
1003
  if (options.controlBinding) {
1098
1004
  const proof = inspectTaskLifecycleProgress(repoRoot, resolved.taskId, request.agent);
1099
- const oldBinding = receipt.controlBinding;
1100
- if (!oldBinding || oldBinding.generation !== options.controlBinding.generation || proof === 'unknown') {
1005
+ if (proof === 'unknown') {
1101
1006
  return failed(resolved.taskId, {
1102
1007
  code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
1103
- message: 'canonical receipt has no valid host recovery proof', retryable: false
1008
+ message: 'task lifecycle progress cannot be verified', retryable: false
1104
1009
  });
1105
1010
  }
1106
- if (oldBinding.requestId !== options.controlBinding.requestId && proof === 'not-started') {
1107
- if (receipt.lifecycle !== 'pending' || !options.handoffDirectory || !request.handoffSha256) {
1108
- return failed(resolved.taskId, {
1109
- code: 'TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE',
1110
- message: 'pending receipt cannot be rebound without a current handoff', retryable: false
1111
- });
1112
- }
1011
+ if (!importedHandoff && options.handoffDirectory && request.handoffSha256) {
1113
1012
  try {
1114
1013
  const handoff = readTaskFinalizationHandoff(options.handoffDirectory, resolved.taskId, options.controlBinding, request.handoffSha256);
1115
1014
  const nextReceipt = validateReceipt(handoff.receipt, resolved.taskId);
1116
1015
  if (nextReceipt.receiptId !== receipt.receiptId || nextReceipt.intent !== receipt.intent
1117
- || nextReceipt.lifecycle !== 'pending')
1016
+ || nextReceipt.lifecycle !== receipt.lifecycle)
1118
1017
  throw new Error('handoff is not continuous with canonical receipt');
1119
- receipt = updateReceipt(repoRoot, receipt, { controlBinding: options.controlBinding });
1120
1018
  try {
1121
1019
  handoff.cleanup();
1122
1020
  }
@@ -1130,7 +1028,7 @@ async function commitPreparedTaskFinalization(request, options) {
1130
1028
  }
1131
1029
  }
1132
1030
  }
1133
- if (receipt.taskComment === 'pending' || receipt.verification === 'pending' || receipt.summary === 'pending' || receipt.warningProjection === 'pending') {
1031
+ if (receipt.taskComment === 'pending' || receipt.summary === 'pending' || receipt.warningProjection === 'pending') {
1134
1032
  return failed(resolved.taskId, {
1135
1033
  code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation has pending external steps', retryable: true
1136
1034
  }, { completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt) });
@@ -1142,6 +1040,7 @@ async function commitPreparedTaskFinalization(request, options) {
1142
1040
  message: `cannot verify canonical short-id registry: ${registry.error.code}: ${registry.error.message}`,
1143
1041
  retryable: false
1144
1042
  });
1043
+ receipt = updateReceipt(repoRoot, receipt, { verification: 'pending' });
1145
1044
  const lifecycle = options.lifecycle ?? applyTaskLifecycle;
1146
1045
  const result = lifecycle({ taskRef: resolved.taskId, intent: 'complete', agent: request.agent }, { repoRoot, ...(options.metadataProvider ? { metadataProvider: options.metadataProvider } : {}) });
1147
1046
  const step = lifecycleStep(result);
@@ -1149,8 +1048,80 @@ async function commitPreparedTaskFinalization(request, options) {
1149
1048
  const updated = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: step.error });
1150
1049
  return terminalResult(resolved.taskId, updated, { lifecycle: step }, result.changed, step.error);
1151
1050
  }
1152
- const updated = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
1153
- return terminalResult(resolved.taskId, updated, { lifecycle: step }, result.changed);
1051
+ let verificationResult;
1052
+ try {
1053
+ verificationResult = await (options.verify ?? verifyTaskEvent)({ taskRef: resolved.taskId, event: 'complete-task.completed' }, { repoRoot });
1054
+ }
1055
+ catch (error) {
1056
+ const detail = errorOf(error, 'VERIFY_FAILED', true);
1057
+ const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
1058
+ let updated;
1059
+ try {
1060
+ updated = updateReceipt(repoRoot, receipt, {
1061
+ lifecycle: 'done', verification: 'pending', taskComment: 'pending', warningProjection: 'pending',
1062
+ warnings, lastError: detail
1063
+ });
1064
+ }
1065
+ catch (persistCause) {
1066
+ const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
1067
+ persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
1068
+ persistError.message = `${persistError.message}; verification also failed: ${detail.code}: ${detail.message}`;
1069
+ return failed(resolved.taskId, persistError, {
1070
+ changed: result.changed,
1071
+ lifecycle: step,
1072
+ verification: { status: 'blocked', changed: false, error: persistError },
1073
+ completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
1074
+ });
1075
+ }
1076
+ const verification = { status: detail.retryable ? 'blocked' : 'failed', changed: false, error: detail };
1077
+ return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed, detail);
1078
+ }
1079
+ const verification = verificationStep(verificationResult);
1080
+ if (verificationResult.status === 'pass') {
1081
+ const resolvedVerificationWarning = receipt.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
1082
+ const warnings = applyVerificationWarnings(receipt, verificationResult);
1083
+ const hasOpenWarnings = warnings.some((warning) => warning.status === 'open');
1084
+ let updated;
1085
+ try {
1086
+ updated = updateReceipt(repoRoot, receipt, {
1087
+ lifecycle: 'done', verification: 'done',
1088
+ taskComment: resolvedVerificationWarning ? 'pending' : receipt.taskComment,
1089
+ warningProjection: hasOpenWarnings ? 'pending' : 'done', warnings, lastError: null
1090
+ });
1091
+ }
1092
+ catch (persistCause) {
1093
+ const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
1094
+ persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
1095
+ return failed(resolved.taskId, persistError, {
1096
+ changed: result.changed,
1097
+ lifecycle: step,
1098
+ verification,
1099
+ completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
1100
+ });
1101
+ }
1102
+ return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed);
1103
+ }
1104
+ const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
1105
+ const warnings = applyVerificationWarnings(receipt, verificationResult);
1106
+ let updated;
1107
+ try {
1108
+ updated = updateReceipt(repoRoot, receipt, {
1109
+ lifecycle: 'done', verification: 'pending', taskComment: 'pending', warningProjection: 'pending',
1110
+ warnings, lastError: detail
1111
+ });
1112
+ }
1113
+ catch (persistCause) {
1114
+ const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
1115
+ persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
1116
+ persistError.message = `${persistError.message}; verification also failed: ${detail.code}: ${detail.message}`;
1117
+ return failed(resolved.taskId, persistError, {
1118
+ changed: result.changed,
1119
+ lifecycle: step,
1120
+ verification: { status: 'blocked', changed: false, error: persistError },
1121
+ completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
1122
+ });
1123
+ }
1124
+ return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed, detail);
1154
1125
  });
1155
1126
  }
1156
1127
  catch (error) {
@@ -47,7 +47,7 @@ function serializeScalar(value) {
47
47
  }
48
48
  if (value === '')
49
49
  return '';
50
- const serialized = stringify(value).trimEnd();
50
+ const serialized = stringify(value, { lineWidth: 0 }).trimEnd();
51
51
  if (serialized.includes('\n') || serialized.includes('\r')) {
52
52
  throw new FrontmatterError('MUTATION_INVALID', 'frontmatter values must be scalar');
53
53
  }
@@ -10,6 +10,7 @@ import { parseLegacyReworkIntentDocument, parseReworkIntentDocument, reworkInten
10
10
  import { extractSection, extractSubSection } from './sections.js';
11
11
  import { parseLifecyclePathDecision } from './lifecycle-path.js';
12
12
  import { scanVisibleMarkdown } from './markdown.js';
13
+ import { inspectArtifactDirectory } from './artifact-lifecycle.js';
13
14
  const PREFIX = { analysis: 'AN', plan: 'PL', code: 'CD' };
14
15
  const RESPONSE = new Set(['accepted', 'adjusted', 'refuted', 'cannot-judge']);
15
16
  const DISPOSITION = new Set(['confirmed', 'closed', 'open', 'needs-human-decision']);
@@ -61,9 +62,8 @@ function findingEvidence(reviewContent, evidence) {
61
62
  return semanticFindingEvidence(reviewContent, heading, next?.start ?? reviewContent.length);
62
63
  }
63
64
  function taskFactDigest(taskDir, content) {
64
- const analysisNames = fs.readdirSync(taskDir).filter((name) => /^analysis(?:-r\d+)?\.md$/.test(name));
65
- const latest = analysisNames.map((name) => ({ name, round: parseArtifactName(name)?.round ?? 0 })).sort((a, b) => b.round - a.round)[0];
66
- const flow = latest ? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, latest.name), 'utf8')) : null;
65
+ const latest = inspectArtifactDirectory(taskDir, 'analysis').latest;
66
+ const flow = latest ? parseLifecyclePathDecision(fs.readFileSync(latest.path, 'utf8'), latest.name) : null;
67
67
  return semanticDigest(JSON.stringify({
68
68
  taskInput: normalizedTaskFact(extractSection(content, ['任务输入', 'Task Input'])),
69
69
  requirements: normalizedTaskFact(extractSection(content, ['需求', 'Requirements'])),
@@ -220,10 +220,7 @@ function applyLedgerIntent(intent, options = {}) {
220
220
  }
221
221
  const sourceIdentity = parseArtifactName(intent.sourceArtifact);
222
222
  const expectedFamily = `review-${finding.stage}`;
223
- const latestSource = fs.readdirSync(resolved.taskDir)
224
- .map((name) => parseArtifactName(name))
225
- .filter((identity) => identity?.family === expectedFamily)
226
- .sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0];
223
+ const latestSource = inspectArtifactDirectory(resolved.taskDir, expectedFamily).latest;
227
224
  if (!sourceIdentity || sourceIdentity.family !== expectedFamily || latestSource?.name !== sourceIdentity.name) {
228
225
  return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild source artifact is not the latest review for the finding stage', resolved.taskId);
229
226
  }
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
- import { artifactName, maxArtifactRound } from './artifact-name.js';
4
+ import { inspectArtifactDirectory } from './artifact-lifecycle.js';
5
5
  import { parseTypedTaskFrontmatter } from './frontmatter.js';
6
6
  import { validateCurrentTaskContract } from './current-contract.js';
7
7
  import { parseReviewSummary } from './review-artifacts.js';
@@ -20,7 +20,6 @@ import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead,
20
20
  import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.js';
21
21
  import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js';
22
22
  import { hasOpenLifecycleExecution } from './activity-log.js';
23
- import { reconcileTaskInvalidation } from './invalidation-command.js';
24
23
  import { normalizeAgentToken } from '../agent-clients/tokens.js';
25
24
  import { resolveArtifactContext } from './artifact-lifecycle.js';
26
25
  function supportsLifecycleDelegation(client) {
@@ -441,8 +440,11 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
441
440
  return { status: 'running', changed: true, taskId: resolved.taskId, run, next: null, error: null };
442
441
  }
443
442
  function validateSavedCurrentDiffBase(repoRoot, metadata, taskDir, reviewedHead) {
444
- const reviewRound = maxArtifactRound(fs.readdirSync(taskDir), 'review-code');
445
- const reviewContent = fs.readFileSync(path.join(taskDir, artifactName('review-code', reviewRound)), 'utf8');
443
+ const reviewInventory = inspectArtifactDirectory(taskDir, 'review-code');
444
+ if (reviewInventory.status !== 'ready' || !reviewInventory.latest) {
445
+ return { code: 'ORCHESTRATION_REVIEW_INVALID', message: 'latest completed review-code artifact is unavailable' };
446
+ }
447
+ const reviewContent = fs.readFileSync(reviewInventory.latest.path, 'utf8');
446
448
  const savedTargetHead = extractReviewTargetHead(reviewContent);
447
449
  const savedDiffBase = extractReviewDiffBase(reviewContent);
448
450
  const savedReviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
@@ -540,8 +542,11 @@ function routeOrchestration(taskRef, options = {}) {
540
542
  if (facts.facts.executionBusy && !run?.pendingDelegation) {
541
543
  return failed('ORCHESTRATION_EXECUTION_BUSY', 'clean completion requires no open lifecycle execution', resolved.taskId);
542
544
  }
543
- const reviewRound = maxArtifactRound(fs.readdirSync(resolved.taskDir), 'review-code');
544
- const review = parseReviewSummary(fs.readFileSync(path.join(resolved.taskDir, artifactName('review-code', reviewRound)), 'utf8'));
545
+ const reviewInventory = inspectArtifactDirectory(resolved.taskDir, 'review-code');
546
+ if (reviewInventory.status !== 'ready' || !reviewInventory.latest) {
547
+ return failed('ORCHESTRATION_REVIEW_INVALID', 'latest completed review-code artifact is unavailable', resolved.taskId);
548
+ }
549
+ const review = parseReviewSummary(fs.readFileSync(reviewInventory.latest.path, 'utf8'));
545
550
  if (!review.ok || review.summary.manualValidation === null) {
546
551
  return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
547
552
  }
@@ -678,10 +683,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
678
683
  }
679
684
  if (run.stepCount >= run.maxSteps)
680
685
  return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
681
- const invalidation = reconcileTaskInvalidation(taskRef, { repoRoot: resolved.repoRoot });
682
- if (invalidation.status === 'failed') {
683
- return failed('ORCHESTRATION_INVALIDATION_RECONCILE_FAILED', `${invalidation.error?.code ?? 'UNKNOWN'}: ${invalidation.error?.message ?? ''}`, resolved.taskId);
684
- }
685
686
  const routed = routeOrchestration(taskRef, options);
686
687
  if (!routed.next)
687
688
  return routed;
@@ -4,7 +4,6 @@ import { createHash, randomUUID } from 'node:crypto';
4
4
  import { isResourceIdentity } from '../platform/resource-identity.js';
5
5
  import { resolveTaskRef } from './resolve-ref.js';
6
6
  const JOURNAL_FILE = '.platform-operations.json';
7
- const MAX_ATTEMPTS = 3;
8
7
  function operationId(input) {
9
8
  return createHash('sha256')
10
9
  .update(`${input.kind}\0${input.target}\0${input.expectedDigest}`)
@@ -36,8 +35,7 @@ function parseJournal(file, taskId) {
36
35
  || !/^[a-f0-9]{64}$/u.test(item.expectedDigest)
37
36
  || !['deferred', 'required'].includes(item.dependency)
38
37
  || !['queued', 'pending', 'unknown', 'succeeded', 'failed'].includes(item.state)
39
- || !Number.isSafeInteger(item.attempts) || item.attempts < 0 || item.attempts > MAX_ATTEMPTS
40
- || item.maxAttempts !== MAX_ATTEMPTS
38
+ || !Number.isSafeInteger(item.attempts) || item.attempts < 0
41
39
  || !(item.lastCode === null || typeof item.lastCode === 'string')
42
40
  || (item.kind === 'issue-metadata'
43
41
  ? !item.issueMetadata || typeof item.issueMetadata.requirements !== 'boolean'
@@ -120,7 +118,13 @@ function recordPlatformOperation(input) {
120
118
  const { taskId, file } = resolveJournal(input.taskRef, input.cwd);
121
119
  const journal = parseJournal(file, taskId);
122
120
  const id = operationId(input);
123
- const previous = journal.operations.find((item) => item.id === id);
121
+ const targetIndex = journal.operations.findIndex((item) => item.id === id);
122
+ const replacementIndex = input.replaceOperationId
123
+ ? journal.operations.findIndex((item) => item.id === input.replaceOperationId)
124
+ : -1;
125
+ const target = targetIndex >= 0 ? journal.operations[targetIndex] : undefined;
126
+ const replacement = replacementIndex >= 0 ? journal.operations[replacementIndex] : undefined;
127
+ const previousAttempts = Math.max(target?.attempts ?? 0, replacement?.attempts ?? 0);
124
128
  const next = {
125
129
  id,
126
130
  kind: input.kind,
@@ -128,8 +132,7 @@ function recordPlatformOperation(input) {
128
132
  expectedDigest: input.expectedDigest,
129
133
  dependency: input.dependency,
130
134
  state: input.state,
131
- attempts: Math.min(MAX_ATTEMPTS, (previous?.attempts ?? 0) + (input.state === 'pending' ? 1 : 0)),
132
- maxAttempts: MAX_ATTEMPTS,
135
+ attempts: previousAttempts + (input.state === 'pending' ? 1 : 0),
133
136
  lastCode: input.lastCode ?? null,
134
137
  ...(input.issueMetadata ? { issueMetadata: input.issueMetadata } : {}),
135
138
  ...(input.issueCreate ? { issueCreate: input.issueCreate } : {}),
@@ -138,9 +141,13 @@ function recordPlatformOperation(input) {
138
141
  ...(input.pullRequestReview ? { pullRequestReview: input.pullRequestReview } : {}),
139
142
  updatedAt: new Date().toISOString()
140
143
  };
141
- const operations = previous
142
- ? journal.operations.map((item) => item.id === id ? next : item)
143
- : [...journal.operations, next];
144
+ const operations = replacementIndex >= 0
145
+ ? journal.operations.flatMap((item, index) => index === replacementIndex
146
+ ? [next]
147
+ : index === targetIndex ? [] : [item])
148
+ : targetIndex >= 0
149
+ ? journal.operations.map((item, index) => index === targetIndex ? next : item)
150
+ : [...journal.operations, next];
144
151
  writeJournal(file, { version: 1, taskId, operations });
145
152
  return next;
146
153
  }
@@ -148,5 +155,5 @@ function readPlatformOperationJournal(taskRef, cwd) {
148
155
  const { taskId, file } = resolveJournal(taskRef, cwd);
149
156
  return parseJournal(file, taskId);
150
157
  }
151
- export { JOURNAL_FILE as PLATFORM_OPERATION_JOURNAL_FILE, MAX_ATTEMPTS as PLATFORM_OPERATION_MAX_ATTEMPTS, operationId, recordPlatformOperation, readPlatformOperationJournal };
158
+ export { JOURNAL_FILE as PLATFORM_OPERATION_JOURNAL_FILE, operationId, recordPlatformOperation, readPlatformOperationJournal };
152
159
  //# sourceMappingURL=platform-operation-journal.js.map