@fitlab-ai/agent-infra 0.11.7 → 0.11.9

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 (128) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +8 -2
  4. package/dist/lib/agent-clients/adapters/codex.js +1 -1
  5. package/dist/lib/agent-clients/config.js +11 -3
  6. package/dist/lib/agent-clients/custom-tool-invocations.js +92 -0
  7. package/dist/lib/agent-clients/next-steps.js +1 -1
  8. package/dist/lib/agent-clients/reconcile.js +3 -3
  9. package/dist/lib/agent-clients/schema.js +18 -10
  10. package/dist/lib/defaults.json +6 -3
  11. package/dist/lib/init.js +2 -2
  12. package/dist/lib/internal/agent-client.js +3 -3
  13. package/dist/lib/internal/cli-route-inventory.js +3 -0
  14. package/dist/lib/internal/platform-pr-review.js +20 -3
  15. package/dist/lib/internal/task-operation-registry.js +3 -1
  16. package/dist/lib/internal/task-platform-recovery.js +56 -0
  17. package/dist/lib/merge.js +25 -5
  18. package/dist/lib/platform/completion-backfill.js +2 -1
  19. package/dist/lib/platform/in-label-sync.js +12 -1
  20. package/dist/lib/platform/issue-comments.js +58 -4
  21. package/dist/lib/platform/issues.js +105 -5
  22. package/dist/lib/platform/operation-coordinator.js +74 -0
  23. package/dist/lib/platform/pr-review.js +107 -1
  24. package/dist/lib/platform/pr-summary.js +79 -57
  25. package/dist/lib/platform/pull-requests.js +96 -4
  26. package/dist/lib/sandbox/commands/create.js +6 -3
  27. package/dist/lib/sandbox/commands/ls.js +4 -3
  28. package/dist/lib/sandbox/commands/rm.js +1 -1
  29. package/dist/lib/sandbox/config.js +22 -4
  30. package/dist/lib/sandbox/git-metadata.js +50 -0
  31. package/dist/lib/sandbox/index.js +1 -1
  32. package/dist/lib/sandbox/mounts.js +12 -0
  33. package/dist/lib/sandbox/recovery.js +12 -22
  34. package/dist/lib/sandbox/removal.js +58 -20
  35. package/dist/lib/sandbox/tools.js +31 -7
  36. package/dist/lib/task/activity-intent.js +4 -3
  37. package/dist/lib/task/create-service.js +5 -3
  38. package/dist/lib/task/create.js +22 -5
  39. package/dist/lib/task/finalization-artifacts.js +4 -1
  40. package/dist/lib/task/finalization.js +243 -113
  41. package/dist/lib/task/lifecycle.js +38 -2
  42. package/dist/lib/task/platform-operation-journal.js +152 -0
  43. package/dist/lib/task/platform-operation-recovery.js +459 -0
  44. package/dist/lib/task/verification.js +0 -1
  45. package/dist/lib/task/workflow-warning-intents.js +7 -1
  46. package/dist/lib/update.js +2 -2
  47. package/dist/lifecycle-build-manifest.json +3 -3
  48. package/lib/agent-clients/adapters/codex.ts +1 -1
  49. package/lib/agent-clients/config.ts +9 -3
  50. package/lib/agent-clients/custom-tool-invocations.ts +129 -0
  51. package/lib/agent-clients/next-steps.ts +3 -3
  52. package/lib/agent-clients/reconcile.ts +3 -3
  53. package/lib/agent-clients/schema.ts +18 -10
  54. package/lib/defaults.json +6 -3
  55. package/lib/init.ts +2 -2
  56. package/lib/internal/agent-client.ts +3 -3
  57. package/lib/internal/cli-route-inventory.ts +2 -0
  58. package/lib/internal/platform-pr-review.ts +14 -3
  59. package/lib/internal/task-operation-registry.ts +3 -1
  60. package/lib/internal/task-platform-recovery.ts +53 -0
  61. package/lib/merge.ts +26 -6
  62. package/lib/platform/completion-backfill.ts +2 -1
  63. package/lib/platform/in-label-sync.ts +12 -0
  64. package/lib/platform/issue-comments.ts +59 -3
  65. package/lib/platform/issues.ts +103 -5
  66. package/lib/platform/operation-coordinator.ts +96 -0
  67. package/lib/platform/pr-review.ts +113 -1
  68. package/lib/platform/pr-summary.ts +22 -1
  69. package/lib/platform/pull-requests.ts +105 -5
  70. package/lib/sandbox/commands/create.ts +7 -8
  71. package/lib/sandbox/commands/ls.ts +4 -3
  72. package/lib/sandbox/commands/rm.ts +1 -1
  73. package/lib/sandbox/config.ts +23 -5
  74. package/lib/sandbox/git-metadata.ts +66 -0
  75. package/lib/sandbox/index.ts +1 -1
  76. package/lib/sandbox/mounts.ts +14 -0
  77. package/lib/sandbox/recovery.ts +13 -25
  78. package/lib/sandbox/removal.ts +57 -19
  79. package/lib/sandbox/tool-types.ts +2 -1
  80. package/lib/sandbox/tools.ts +38 -7
  81. package/lib/task/activity-intent.ts +4 -3
  82. package/lib/task/create-service.ts +5 -3
  83. package/lib/task/create.ts +17 -5
  84. package/lib/task/finalization-artifacts.ts +4 -1
  85. package/lib/task/finalization.ts +242 -106
  86. package/lib/task/lifecycle.ts +35 -3
  87. package/lib/task/platform-operation-journal.ts +230 -0
  88. package/lib/task/platform-operation-recovery.ts +435 -0
  89. package/lib/task/verification.ts +1 -2
  90. package/lib/task/workflow-warning-intents.ts +9 -1
  91. package/lib/update.ts +2 -3
  92. package/package.json +1 -1
  93. package/templates/.agents/README.en.md +20 -66
  94. package/templates/.agents/README.zh-CN.md +19 -65
  95. package/templates/.agents/rules/README.en.md +1 -1
  96. package/templates/.agents/rules/README.zh-CN.md +1 -1
  97. package/templates/.agents/rules/next-step-output.en.md +1 -1
  98. package/templates/.agents/rules/next-step-output.zh-CN.md +1 -1
  99. package/templates/.agents/rules/task-management.en.md +12 -0
  100. package/templates/.agents/rules/task-management.zh-CN.md +12 -0
  101. package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
  102. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -2
  103. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +4 -2
  104. package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
  105. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  106. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  107. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  108. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -0
  109. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -0
  110. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  111. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  112. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  113. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  114. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  115. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  116. package/templates/.agents/skills/review-code/reference/output-templates.en.md +15 -6
  117. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +15 -6
  118. package/templates/.agents/skills/review-pr/SKILL.en.md +11 -11
  119. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +11 -11
  120. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +3 -3
  121. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +3 -3
  122. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -0
  123. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -0
  124. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -1
  125. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
  126. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +21 -274
  127. package/dist/lib/agent-clients/custom-tuis.js +0 -99
  128. package/lib/agent-clients/custom-tuis.ts +0 -149
@@ -7,13 +7,19 @@ import { canonicalizeSummaryBody } from '../platform/comment-safety.ts';
7
7
  import { backfillCompletionComments, inspectCompletionBackfillEligibility } from '../platform/completion-backfill.ts';
8
8
  import type { PlatformResult } from '../platform/types.ts';
9
9
  import { taskIssueIdentity } from '../platform/task-identities.ts';
10
+ import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.ts';
11
+ import type { IssueResult, SyncOptions as IssueSyncOptions } from '../platform/issues.ts';
12
+ import { resourceIdentityEquals } from '../platform/resource-identity.ts';
10
13
  import { parseTaskFrontmatter } from './frontmatter.ts';
14
+ import { recoverPlatformOperations } from './platform-operation-recovery.ts';
11
15
  import {
12
16
  applyTaskLifecycle,
13
17
  inspectTaskLifecycleProgress,
14
18
  type TaskLifecycleOptions,
15
19
  type TaskLifecycleRequest,
16
- type TaskLifecycleResult
20
+ type TaskLifecycleResult,
21
+ type TaskCompletionProjection,
22
+ previewTaskCompletion
17
23
  } from './lifecycle.ts';
18
24
  import { resolveTaskRef } from './resolve-ref.ts';
19
25
  import { inspectShortIdRegistry } from './short-id.ts';
@@ -71,6 +77,8 @@ type TaskFinalizationOptions = Readonly<{
71
77
  lifecycle?: typeof applyTaskLifecycle;
72
78
  backfill?: typeof backfillCompletionComments;
73
79
  commentSync?: typeof syncPlatformComment;
80
+ issueSync?: typeof syncPlatformIssue;
81
+ issueInspect?: typeof inspectPlatformIssue;
74
82
  verify?: typeof verifyTaskEvent;
75
83
  preflight?: typeof verifyTaskEvent;
76
84
  }>;
@@ -471,13 +479,13 @@ function capabilityError(code: 'FINALIZATION_CAPABILITY_STALE' | 'FINALIZATION_S
471
479
  return error;
472
480
  }
473
481
 
474
- function hasPreparedTaskDocument(repoRoot: string, taskId: string): boolean {
482
+ function hasFinalizationTaskDocument(repoRoot: string, taskId: string): boolean {
475
483
  const resolved = resolveTaskRef(taskId, { repoRoot });
476
- if (!resolved.ok) return false;
477
- if (resolved.state === 'completed') return true;
478
- if (resolved.state !== 'active') return false;
479
- try { return parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8')).status === 'completed'; }
480
- catch { return false; }
484
+ if (!resolved.ok || (resolved.state !== 'active' && resolved.state !== 'completed')) return false;
485
+ try {
486
+ const journal = JSON.parse(fs.readFileSync(path.join(resolved.taskDir, '.task-lifecycle.json'), 'utf8')) as Record<string, unknown>;
487
+ return journal.taskId === taskId && journal.intent === 'complete';
488
+ } catch { return resolved.state === 'completed'; }
481
489
  }
482
490
 
483
491
  function mutationKeys(value: FinalizationMutation): string[] {
@@ -525,7 +533,14 @@ function mutationPatch(current: TaskFinalizationReceipt, mutation: FinalizationM
525
533
  if (mutation.scope === 'task-comment') {
526
534
  if (mutation.operation === 'succeeded') {
527
535
  const warnings = resolveStepWarnings(current, 'task-comment');
528
- return { taskComment: mutation.state, warningProjection: warnings.length > 0 ? 'pending' : 'done', warnings, lastError: null };
536
+ const retryWithResolvedWarning = mutation.state === 'done'
537
+ && current.warnings.some((warning) => warning.step === 'task-comment' && warning.status === 'open');
538
+ return {
539
+ taskComment: retryWithResolvedWarning ? 'pending' : mutation.state,
540
+ warningProjection: warnings.length > 0 || retryWithResolvedWarning ? 'pending' : 'done',
541
+ warnings,
542
+ lastError: null
543
+ };
529
544
  }
530
545
  const warnings = replaceWarning(current, warningFromError('task-comment', mutation.error), 'open');
531
546
  return { taskComment: 'pending', warningProjection: 'pending', warnings, lastError: mutation.error };
@@ -557,8 +572,8 @@ function applyFinalizationReceiptMutationUnderLock(
557
572
  mutation: FinalizationMutation,
558
573
  consumed: Set<string>
559
574
  ): TaskFinalizationReceipt {
560
- if (!hasPreparedTaskDocument(repoRoot, receipt.taskId)) {
561
- throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active prepared task or a completed task');
575
+ if (!hasFinalizationTaskDocument(repoRoot, receipt.taskId)) {
576
+ throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active task or a completed task');
562
577
  }
563
578
  const current = readReceipt(repoRoot, receipt.taskId);
564
579
  if (!current || current.receiptId !== receipt.receiptId || current.revision !== receipt.revision) {
@@ -576,8 +591,8 @@ function applyFinalizationReceiptMutation(
576
591
  capability: FinalizationCapability,
577
592
  mutation: FinalizationMutation
578
593
  ): TaskFinalizationReceipt {
579
- if (!hasPreparedTaskDocument(repoRoot, receipt.taskId)) {
580
- throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active prepared task or a completed task');
594
+ if (!hasFinalizationTaskDocument(repoRoot, receipt.taskId)) {
595
+ throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active task or a completed task');
581
596
  }
582
597
  return withTaskExecutionLock(repoRoot, receipt.taskId, 'task-finalization.receipt-mutation', () =>
583
598
  applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, new Set<string>())
@@ -627,6 +642,8 @@ async function syncPendingTaskComment(input: {
627
642
  taskId: string;
628
643
  agent: string;
629
644
  receipt: TaskFinalizationReceipt;
645
+ projection?: TaskCompletionProjection;
646
+ refreshProjection?: () => TaskCompletionProjection | undefined;
630
647
  commentSync: typeof syncPlatformComment;
631
648
  consumedCapabilities: Set<string>;
632
649
  }): Promise<TaskCommentSyncOutcome> {
@@ -641,7 +658,15 @@ async function syncPendingTaskComment(input: {
641
658
  };
642
659
  }
643
660
  try {
644
- const result = await commentSync(taskId, { kind: 'task', agent, cwd: repoRoot });
661
+ const result = await commentSync(taskId, {
662
+ kind: 'task', agent, cwd: repoRoot, dependency: 'required',
663
+ ...(input.projection ? {
664
+ taskProjection: {
665
+ content: input.projection.content,
666
+ sha256: createHash('sha256').update(input.projection.content).digest('hex')
667
+ }
668
+ } : {})
669
+ });
645
670
  const step = commentStep(result);
646
671
  if (result.status === 'applied' || result.status === 'no-op') {
647
672
  const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
@@ -650,6 +675,14 @@ async function syncPendingTaskComment(input: {
650
675
  scope: 'task-comment', operation: 'succeeded', state: skipped ? 'skipped' : 'done'
651
676
  }, consumedCapabilities);
652
677
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
678
+ if (!skipped && receipt.taskComment === 'pending') {
679
+ const retry = await syncPendingTaskComment({
680
+ ...input,
681
+ receipt,
682
+ projection: input.refreshProjection?.() ?? input.projection
683
+ });
684
+ return { ...retry, changed: result.changed || retry.changed };
685
+ }
653
686
  return { receipt, step: skipped ? { ...step, status: 'skipped' } : step, changed: result.changed, error: null };
654
687
  }
655
688
  const detail = step.error ?? { code: 'COMMENT_SYNC_FAILED', message: 'task comment synchronization failed', retryable: true };
@@ -702,7 +735,7 @@ async function syncPendingSummary(input: {
702
735
  let { receipt } = input;
703
736
  try {
704
737
  const resolved = resolveTaskRef(input.taskId, { repoRoot: input.repoRoot });
705
- if (!resolved.ok || !hasPreparedTaskDocument(input.repoRoot, input.taskId)) throw Object.assign(new Error('prepared task is unavailable for summary sync'), { code: 'SUMMARY_STAGING_INVALID' });
738
+ if (!resolved.ok || (resolved.state !== 'active' && resolved.state !== 'completed')) throw Object.assign(new Error('task is unavailable for summary sync'), { code: 'SUMMARY_STAGING_INVALID' });
706
739
  if (!fs.existsSync(path.join(resolved.taskDir, '.delivery-summary.json'))) {
707
740
  receipt = updateReceipt(input.repoRoot, receipt, { summary: 'skipped', lastError: null });
708
741
  return { receipt, step: { status: 'skipped', changed: false, error: null }, changed: false, error: null };
@@ -716,7 +749,8 @@ async function syncPendingSummary(input: {
716
749
  const summary = readDeliverySummary(resolved.taskDir, input.taskId);
717
750
  const result = await input.commentSync(input.taskId, {
718
751
  kind: 'summary', agent: input.agent, body: summary.body, cwd: input.repoRoot,
719
- summaryAuthorization: { sha256: summary.sha256 }
752
+ summaryAuthorization: { sha256: summary.sha256 },
753
+ dependency: 'required'
720
754
  });
721
755
  const step = commentStep(result);
722
756
  if (result.status === 'applied' || result.status === 'no-op') {
@@ -726,7 +760,7 @@ async function syncPendingSummary(input: {
726
760
  receipt = updateReceipt(input.repoRoot, receipt, {
727
761
  summary: skipped ? 'skipped' : 'done',
728
762
  taskComment: resolvedWarning ? 'pending' : receipt.taskComment,
729
- warningProjection: warnings.some((warning) => warning.status === 'open') ? 'pending' : 'done',
763
+ warningProjection: warnings.some((warning) => warning.status === 'open') || resolvedWarning ? 'pending' : 'done',
730
764
  warnings,
731
765
  lastError: null
732
766
  });
@@ -761,9 +795,12 @@ function terminalResult(
761
795
  const blocked = [steps.backfill, steps.lifecycle, steps.taskComment, steps.verification, steps.summary].some((step) => step?.status === 'blocked');
762
796
  const warnings = openWarnings(receipt);
763
797
  const postLifecyclePending = receipt.lifecycle === 'done' && (pending.some((step) => step !== 'lifecycle') || receipt.warningProjection === 'pending');
764
- const hardError = error?.code.startsWith('FINALIZATION_') || error?.code === 'TASK_FINALIZATION_RECEIPT_INVALID';
798
+ const terminalVerificationPending = receipt.lifecycle === 'done' && receipt.verification === 'pending';
799
+ const resultError = error ?? (terminalVerificationPending ? steps.verification?.error : null) ?? null;
800
+ const hardError = error?.code.startsWith('FINALIZATION_') || error?.code === 'TASK_FINALIZATION_RECEIPT_INVALID'
801
+ || terminalVerificationPending;
765
802
  return {
766
- status: hardError ? (error?.retryable ? 'blocked' : 'failed') : pending.length === 0 ? 'completed' : postLifecyclePending ? 'completed' : blocked ? 'blocked' : 'failed',
803
+ status: hardError ? (resultError?.retryable ? 'blocked' : 'failed') : pending.length === 0 ? 'completed' : postLifecyclePending ? 'completed' : blocked ? 'blocked' : 'failed',
767
804
  changed,
768
805
  taskId,
769
806
  backfill: steps.backfill ?? null,
@@ -774,7 +811,7 @@ function terminalResult(
774
811
  completedSteps: completedSteps(receipt),
775
812
  pendingSteps: pending,
776
813
  result: hardError
777
- ? (error?.retryable ? 'blocked' : 'failed')
814
+ ? (resultError?.retryable ? 'blocked' : 'failed')
778
815
  : postLifecyclePending
779
816
  ? 'completed_with_warnings'
780
817
  : pending.length === 0
@@ -783,7 +820,7 @@ function terminalResult(
783
820
  ? 'blocked'
784
821
  : 'failed',
785
822
  warnings,
786
- error: hardError ? error : postLifecyclePending ? null : error
823
+ error: hardError ? resultError : postLifecyclePending ? null : resultError
787
824
  };
788
825
  }
789
826
 
@@ -814,7 +851,8 @@ async function prepareUnderLock(
814
851
  const lifecycle = options.lifecycle ?? applyTaskLifecycle;
815
852
  const backfill = options.backfill ?? backfillCompletionComments;
816
853
  const commentSync = options.commentSync ?? syncPlatformComment;
817
- const verify = options.verify ?? verifyTaskEvent;
854
+ const issueSync = options.issueSync ?? syncPlatformIssue;
855
+ const issueInspect = options.issueInspect ?? inspectPlatformIssue;
818
856
  const consumedCapabilities = new Set<string>();
819
857
  let receipt: TaskFinalizationReceipt;
820
858
  try {
@@ -858,6 +896,91 @@ async function prepareUnderLock(
858
896
  }
859
897
  }
860
898
 
899
+ if (preflightState.ok && preflightState.state === 'active') {
900
+ const recovery = await recoverPlatformOperations(taskId, 'all', { agent: request.agent, cwd: repoRoot });
901
+ if (recovery.status !== 'applied' && recovery.status !== 'no-op') {
902
+ return failed(taskId, recovery.error ?? {
903
+ code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
904
+ message: 'Pending platform operations remain unresolved',
905
+ retryable: true
906
+ });
907
+ }
908
+ const taskContent = fs.readFileSync(preflightState.taskMdPath, 'utf8');
909
+ const taskFrontmatter = parseTaskFrontmatter(taskContent);
910
+ const issueIdentity = taskIssueIdentity(taskFrontmatter);
911
+ if (issueIdentity) {
912
+ const base = typeof taskFrontmatter.delivery_base_ref === 'string' ? taskFrontmatter.delivery_base_ref.trim() : '';
913
+ let synced: IssueResult;
914
+ try {
915
+ synced = await issueSync(taskId, {
916
+ agent: request.agent, cwd: repoRoot, requirements: true, issueType: true, fields: true,
917
+ ...(base ? { inLabels: 'from-diff', base } : {}), dependency: 'required'
918
+ } satisfies IssueSyncOptions);
919
+ } catch (error) {
920
+ const detail = errorOf(error, 'FINALIZATION_ISSUE_METADATA_FAILED', true);
921
+ return failed(taskId, detail);
922
+ }
923
+ const issueOperations = synced.operations as Array<{ name: string; status: string; reasonCode: string | null; value?: unknown }>;
924
+ const badOperation = issueOperations?.find((operation) =>
925
+ ['requirements', 'issue-type', 'fields', ...(base ? ['labels:in'] : [])].includes(operation.name)
926
+ && operation.status !== 'applied' && operation.status !== 'no-op'
927
+ && !(operation.name === 'requirements' && operation.status === 'skipped' && operation.reasonCode === 'NO_REQUIREMENTS_ANCHOR')
928
+ );
929
+ if ((synced.status !== 'applied' && synced.status !== 'no-op') || synced.error || badOperation) {
930
+ const error = synced.error ?? {
931
+ code: badOperation?.reasonCode || 'FINALIZATION_ISSUE_METADATA_UNCONFIRMED',
932
+ message: `Required Issue metadata operation '${badOperation?.name ?? 'sync'}' was not confirmed`,
933
+ retryable: true
934
+ };
935
+ return failed(taskId, errorOf(error, 'FINALIZATION_ISSUE_METADATA_FAILED', true));
936
+ }
937
+ let inspected: IssueResult;
938
+ try { inspected = await issueInspect(taskId, { cwd: repoRoot }); }
939
+ catch (error) { return failed(taskId, errorOf(error, 'FINALIZATION_ISSUE_INSPECT_FAILED', true)); }
940
+ if (inspected.status !== 'no-op' || inspected.error || !inspected.issue
941
+ || !resourceIdentityEquals(inspected.issue.identity, issueIdentity)) {
942
+ return failed(taskId, inspected.error ?? {
943
+ code: 'FINALIZATION_ISSUE_IDENTITY_UNCONFIRMED',
944
+ message: 'Required Issue metadata could not be reread under the bound Issue identity',
945
+ retryable: true
946
+ });
947
+ }
948
+ for (const name of ['requirements', 'issue-type', 'fields', ...(base ? ['labels:in'] : [])]) {
949
+ const operation = issueOperations?.find((candidate) => candidate.name === name);
950
+ if (!operation) return failed(taskId, {
951
+ code: 'FINALIZATION_ISSUE_METADATA_UNCONFIRMED',
952
+ message: `Required Issue metadata operation '${name}' was not reported`, retryable: true
953
+ });
954
+ const before = synced.issue;
955
+ const remote = inspected.issue;
956
+ if (name === 'requirements') {
957
+ const expected = typeof operation.value === 'string' ? operation.value : before?.body;
958
+ if (expected === undefined || remote.body !== expected) return failed(taskId, {
959
+ code: 'FINALIZATION_ISSUE_REQUIREMENTS_UNCONFIRMED', message: 'Issue requirements did not match the verified target', retryable: true
960
+ });
961
+ } else if (name === 'issue-type') {
962
+ const expected = typeof operation.value === 'string' ? operation.value : before?.issueType;
963
+ if (!expected || remote.issueType !== expected) return failed(taskId, {
964
+ code: 'FINALIZATION_ISSUE_TYPE_UNCONFIRMED', message: 'Issue Type did not match the verified target', retryable: true
965
+ });
966
+ } else if (name === 'labels:in') {
967
+ const expected = Array.isArray(operation.value) ? operation.value as string[] : before?.labels;
968
+ if (!expected || expected.filter((label) => label.startsWith('in:')).sort().join('\0')
969
+ !== remote.labels.filter((label) => label.startsWith('in:')).sort().join('\0')) return failed(taskId, {
970
+ code: 'FINALIZATION_IN_LABELS_UNCONFIRMED', message: 'Issue in-labels did not match the verified target', retryable: true
971
+ });
972
+ } else {
973
+ const expected = operation.value && typeof operation.value === 'object'
974
+ ? operation.value as Record<string, string | number>
975
+ : before?.fields;
976
+ if (!expected || Object.entries(expected).some(([field, value]) => remote.fields[field] !== value)) return failed(taskId, {
977
+ code: 'FINALIZATION_ISSUE_FIELDS_UNCONFIRMED', message: 'Pinned Issue fields did not match the verified target', retryable: true
978
+ });
979
+ }
980
+ }
981
+ }
982
+ }
983
+
861
984
  let changed = false;
862
985
  let backfillResult: TaskFinalizationStep | null = null;
863
986
  let runBackfill = shouldRunBackfill(receipt);
@@ -921,9 +1044,10 @@ async function prepareUnderLock(
921
1044
  } else backfillResult = { status: 'no-op', changed: false, error: null };
922
1045
 
923
1046
  let lifecycleResult: TaskFinalizationStep | null = null;
1047
+ let projection: TaskCompletionProjection | undefined;
924
1048
  try {
925
1049
  const resolved = resolveTaskRef(taskId, { repoRoot });
926
- if (receipt.lifecycle === 'done' && resolved.ok && resolved.state === 'completed') {
1050
+ if (resolved.ok && resolved.state === 'completed') {
927
1051
  lifecycleResult = { status: 'no-op', changed: false, error: null };
928
1052
  receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
929
1053
  } else {
@@ -937,6 +1061,12 @@ async function prepareUnderLock(
937
1061
  return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment: null, verification: null }, result.changed, lifecycleResult.error);
938
1062
  }
939
1063
  changed = changed || result.changed;
1064
+ if (result.timestamp && result.agentInfraVersion) {
1065
+ projection = previewTaskCompletion(taskId, request.agent, {
1066
+ repoRoot,
1067
+ metadataProvider: () => ({ timestamp: result.timestamp!, agentInfraVersion: result.agentInfraVersion! })
1068
+ });
1069
+ }
940
1070
  }
941
1071
  } catch (error) {
942
1072
  const detail = errorOf(error, 'TASK_FINALIZATION_LIFECYCLE_FAILED');
@@ -946,85 +1076,16 @@ async function prepareUnderLock(
946
1076
 
947
1077
  let taskComment: TaskFinalizationStep | null = null;
948
1078
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
1079
+ const currentProjection = (): TaskCompletionProjection | undefined => projection
1080
+ ? previewTaskCompletion(taskId, request.agent, {
1081
+ repoRoot,
1082
+ metadataProvider: () => ({ timestamp: projection!.timestamp, agentInfraVersion: projection!.agentInfraVersion })
1083
+ })
1084
+ : undefined;
949
1085
 
950
- let verification: TaskFinalizationStep | null = null;
951
- if (receipt.verification !== 'pending') {
952
- verification = { status: receipt.verification === 'skipped' ? 'skipped' : 'no-op', changed: false, error: null };
953
- } else {
954
- try {
955
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
956
- const verificationState = resolveTaskRef(taskId, { repoRoot });
957
- const verificationEvent = verificationState.ok && verificationState.state === 'completed'
958
- ? 'complete-task.completed'
959
- : 'complete-task.prepared';
960
- const result = await verify(
961
- { taskRef: taskId, event: verificationEvent },
962
- { repoRoot }
963
- );
964
- verification = verificationStep(result);
965
- if (result.status === 'pass') {
966
- const resolvedTaskWarning = receipt.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
967
- const warnings = applyVerificationWarnings(receipt, result);
968
- receipt = updateReceipt(repoRoot, receipt, {
969
- verification: 'done', taskComment: resolvedTaskWarning ? 'pending' : receipt.taskComment,
970
- warningProjection: warnings.length > 0 ? 'pending' : 'done', warnings, lastError: null
971
- });
972
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
973
- const finalComment = await syncPendingTaskComment({
974
- repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
975
- });
976
- receipt = finalComment.receipt;
977
- if (finalComment.step.status !== 'no-op') taskComment = finalComment.step;
978
- changed = changed || finalComment.changed;
979
- if (finalComment.error) {
980
- return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
981
- }
982
- } else {
983
- const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
984
- const warnings = applyVerificationWarnings(receipt, result);
985
- receipt = updateReceipt(repoRoot, receipt, {
986
- verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
987
- });
988
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
989
- const warningComment = await syncPendingTaskComment({
990
- repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
991
- });
992
- receipt = warningComment.receipt;
993
- taskComment = warningComment.step;
994
- changed = changed || warningComment.changed;
995
- if (warningComment.error) {
996
- return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, warningComment.error);
997
- }
998
- return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, detail);
999
- }
1000
- } catch (error) {
1001
- const detail = errorOf(error, 'VERIFY_FAILED', true);
1002
- try {
1003
- const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
1004
- receipt = updateReceipt(repoRoot, receipt, {
1005
- verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
1006
- });
1007
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
1008
- } catch (writeError) {
1009
- const persistence = errorOf(writeError, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
1010
- const hardError: FinalizationError = {
1011
- code: 'FINALIZATION_RECEIPT_WRITE_FAILED',
1012
- message: `Unable to persist verification failure (${detail.code}: ${detail.message}): ${persistence.message}`,
1013
- retryable: true
1014
- };
1015
- return terminalResult(taskId, receipt, {
1016
- backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail }
1017
- }, changed, hardError);
1018
- }
1019
- try {
1020
- const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
1021
- receipt = warningComment.receipt;
1022
- if (warningComment.step.status !== 'no-op') taskComment = warningComment.step;
1023
- changed = changed || warningComment.changed;
1024
- } catch { /* preserve the primary error */ }
1025
- return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
1026
- }
1027
- }
1086
+ const verification: TaskFinalizationStep = receipt.verification === 'done'
1087
+ ? { status: 'no-op', changed: false, error: null }
1088
+ : { status: 'skipped', changed: false, error: null };
1028
1089
 
1029
1090
  let summary = receipt.summary === 'pending'
1030
1091
  ? await syncPendingSummary({ repoRoot, taskId, agent: request.agent, receipt, commentSync })
@@ -1038,7 +1099,7 @@ async function prepareUnderLock(
1038
1099
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
1039
1100
  if (receipt.taskComment === 'pending') {
1040
1101
  const finalComment = await syncPendingTaskComment({
1041
- repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
1102
+ repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities
1042
1103
  });
1043
1104
  receipt = finalComment.receipt;
1044
1105
  taskComment = finalComment.step;
@@ -1075,7 +1136,7 @@ async function commitPreparedTaskFinalization(request: TaskFinalizationRequest,
1075
1136
  const resolved = resolveTaskRef(request.taskRef, { repoRoot });
1076
1137
  if (!resolved.ok) return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
1077
1138
  try {
1078
- return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.commit', () => {
1139
+ return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.commit', async () => {
1079
1140
  if (options.controlBinding && options.manifestPath) {
1080
1141
  try {
1081
1142
  const manifest = readSandboxControlManifest(options.manifestPath);
@@ -1122,7 +1183,7 @@ async function commitPreparedTaskFinalization(request: TaskFinalizationRequest,
1122
1183
  if (!receipt) return failed(resolved.taskId, {
1123
1184
  code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation receipt is unavailable', retryable: true
1124
1185
  });
1125
- if (receipt.lifecycle === 'done' && resolved.state === 'completed') {
1186
+ if (receipt.lifecycle === 'done' && receipt.verification !== 'pending' && resolved.state === 'completed') {
1126
1187
  return terminalResult(resolved.taskId, receipt, { lifecycle: { status: 'no-op', changed: false, error: null } }, false);
1127
1188
  }
1128
1189
  if (options.controlBinding) {
@@ -1158,7 +1219,7 @@ async function commitPreparedTaskFinalization(request: TaskFinalizationRequest,
1158
1219
  }
1159
1220
  }
1160
1221
  }
1161
- if (receipt.taskComment === 'pending' || receipt.verification === 'pending' || receipt.summary === 'pending' || receipt.warningProjection === 'pending') {
1222
+ if (receipt.taskComment === 'pending' || receipt.summary === 'pending' || receipt.warningProjection === 'pending') {
1162
1223
  return failed(resolved.taskId, {
1163
1224
  code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation has pending external steps', retryable: true
1164
1225
  }, { completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt) });
@@ -1169,6 +1230,7 @@ async function commitPreparedTaskFinalization(request: TaskFinalizationRequest,
1169
1230
  message: `cannot verify canonical short-id registry: ${registry.error.code}: ${registry.error.message}`,
1170
1231
  retryable: false
1171
1232
  });
1233
+ receipt = updateReceipt(repoRoot, receipt, { verification: 'pending' });
1172
1234
  const lifecycle = options.lifecycle ?? applyTaskLifecycle;
1173
1235
  const result = lifecycle(
1174
1236
  { taskRef: resolved.taskId, intent: 'complete', agent: request.agent },
@@ -1179,8 +1241,82 @@ async function commitPreparedTaskFinalization(request: TaskFinalizationRequest,
1179
1241
  const updated = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: step.error });
1180
1242
  return terminalResult(resolved.taskId, updated, { lifecycle: step }, result.changed, step.error);
1181
1243
  }
1182
- const updated = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
1183
- return terminalResult(resolved.taskId, updated, { lifecycle: step }, result.changed);
1244
+
1245
+ let verificationResult: TaskVerificationResult;
1246
+ try {
1247
+ verificationResult = await (options.verify ?? verifyTaskEvent)(
1248
+ { taskRef: resolved.taskId, event: 'complete-task.completed' },
1249
+ { repoRoot }
1250
+ );
1251
+ } catch (error) {
1252
+ const detail = errorOf(error, 'VERIFY_FAILED', true);
1253
+ const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
1254
+ let updated: TaskFinalizationReceipt;
1255
+ try {
1256
+ updated = updateReceipt(repoRoot, receipt, {
1257
+ lifecycle: 'done', verification: 'pending', taskComment: 'pending', warningProjection: 'pending',
1258
+ warnings, lastError: detail
1259
+ });
1260
+ } catch (persistCause) {
1261
+ const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
1262
+ persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
1263
+ persistError.message = `${persistError.message}; verification also failed: ${detail.code}: ${detail.message}`;
1264
+ return failed(resolved.taskId, persistError, {
1265
+ changed: result.changed,
1266
+ lifecycle: step,
1267
+ verification: { status: 'blocked', changed: false, error: persistError },
1268
+ completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
1269
+ });
1270
+ }
1271
+ const verification: TaskFinalizationStep = { status: detail.retryable ? 'blocked' : 'failed', changed: false, error: detail };
1272
+ return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed, detail);
1273
+ }
1274
+
1275
+ const verification = verificationStep(verificationResult);
1276
+ if (verificationResult.status === 'pass') {
1277
+ const resolvedVerificationWarning = receipt.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
1278
+ const warnings = applyVerificationWarnings(receipt, verificationResult);
1279
+ const hasOpenWarnings = warnings.some((warning) => warning.status === 'open');
1280
+ let updated: TaskFinalizationReceipt;
1281
+ try {
1282
+ updated = updateReceipt(repoRoot, receipt, {
1283
+ lifecycle: 'done', verification: 'done',
1284
+ taskComment: resolvedVerificationWarning ? 'pending' : receipt.taskComment,
1285
+ warningProjection: hasOpenWarnings ? 'pending' : 'done', warnings, lastError: null
1286
+ });
1287
+ } catch (persistCause) {
1288
+ const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
1289
+ persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
1290
+ return failed(resolved.taskId, persistError, {
1291
+ changed: result.changed,
1292
+ lifecycle: step,
1293
+ verification,
1294
+ completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
1295
+ });
1296
+ }
1297
+ return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed);
1298
+ }
1299
+
1300
+ const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
1301
+ const warnings = applyVerificationWarnings(receipt, verificationResult);
1302
+ let updated: TaskFinalizationReceipt;
1303
+ try {
1304
+ updated = updateReceipt(repoRoot, receipt, {
1305
+ lifecycle: 'done', verification: 'pending', taskComment: 'pending', warningProjection: 'pending',
1306
+ warnings, lastError: detail
1307
+ });
1308
+ } catch (persistCause) {
1309
+ const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
1310
+ persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
1311
+ persistError.message = `${persistError.message}; verification also failed: ${detail.code}: ${detail.message}`;
1312
+ return failed(resolved.taskId, persistError, {
1313
+ changed: result.changed,
1314
+ lifecycle: step,
1315
+ verification: { status: 'blocked', changed: false, error: persistError },
1316
+ completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
1317
+ });
1318
+ }
1319
+ return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed, detail);
1184
1320
  });
1185
1321
  } catch (error) {
1186
1322
  const detail = error instanceof TaskExecutionLockError
@@ -5,7 +5,8 @@ import path from 'node:path';
5
5
  import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
6
6
  import { artifactFamilyCatalog, inspectArtifactDirectory } from './artifact-lifecycle.ts';
7
7
  import { parseArtifactName } from './artifact-name.ts';
8
- import { parseTypedTaskFrontmatter } from './frontmatter.ts';
8
+ import { parseTypedTaskFrontmatter, updateTaskFrontmatter } from './frontmatter.ts';
9
+ import { upsertSection } from './sections.ts';
9
10
  import { locateHotTaskDirs, resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
10
11
  import {
11
12
  configuredShortIdLength, executeShortIdCommand, loadShortIdByTaskId,
@@ -112,6 +113,8 @@ type TaskLifecycleOptions = {
112
113
  prepareOnly?: boolean;
113
114
  };
114
115
 
116
+ type TaskCompletionProjection = Readonly<{ taskId: string; content: string; timestamp: string; agentInfraVersion: string }>;
117
+
115
118
  const STEPS: readonly LifecycleStep[] = ['task-written', 'directory-moved', 'registry-committed'];
116
119
  const DEFAULT_IO: LifecycleFileSystem = {
117
120
  renameSync: (source, target) => fs.renameSync(source, target),
@@ -304,6 +307,25 @@ function mutationsFor(request: TaskLifecycleRequest, content: string, metadata:
304
307
  return mutations;
305
308
  }
306
309
 
310
+ function previewTaskCompletion(taskRef: string, agent: string, options: Pick<TaskLifecycleOptions, 'repoRoot' | 'metadataProvider'> = {}): TaskCompletionProjection {
311
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
312
+ if (!resolved.ok) throw Object.assign(new Error(resolved.message), { code: resolved.code });
313
+ if (resolved.state !== 'active') throw Object.assign(new Error(`task ${resolved.taskId} is ${resolved.state}, expected active`), { code: 'TASK_STATE_MISMATCH' });
314
+ const original = fs.readFileSync(resolved.taskMdPath, 'utf8');
315
+ const metadata = (options.metadataProvider ?? captureTaskWriteMetadata)();
316
+ if (!isValidAgentInfraVersion(metadata.agentInfraVersion)) throw Object.assign(new Error('metadata agentInfraVersion is invalid'), { code: 'LIFECYCLE_METADATA_FAILED' });
317
+ const mutations = mutationsFor({ taskRef: resolved.taskId, intent: 'complete', agent }, original, metadata, 0);
318
+ if (!Array.isArray(mutations)) throw Object.assign(new Error(mutations.message), { code: mutations.code });
319
+ let content = original;
320
+ for (const mutation of mutations) {
321
+ if (mutation.kind === 'frontmatter') content = updateTaskFrontmatter(content, mutation.set, mutation.remove);
322
+ else if (mutation.kind === 'section') content = upsertSection(content, mutation).content;
323
+ else throw Object.assign(new Error('completion projection contains an unsupported mutation'), { code: 'LIFECYCLE_PROJECTION_INVALID' });
324
+ }
325
+ content = updateTaskFrontmatter(content, { updated_at: metadata.timestamp, agent_infra_version: metadata.agentInfraVersion });
326
+ return { taskId: resolved.taskId, content, timestamp: metadata.timestamp, agentInfraVersion: metadata.agentInfraVersion };
327
+ }
328
+
307
329
  function writeJournal(file: string, journal: LifecycleJournal, io: LifecycleFileSystem, create = false): void {
308
330
  const temporary = `${file}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
309
331
  io.writeFileSync(temporary, `${JSON.stringify(journal, null, 2)}\n`, 'wx');
@@ -590,6 +612,16 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
590
612
  if (completed.has('directory-moved')) completed.add('registry-committed');
591
613
  }
592
614
  let taskOperations: readonly TaskOperationSummary[] = [];
615
+ if (options.prepareOnly && !matchingCompletion(io.readFileSync(path.join(sourcePath, 'task.md')), request, restoredFiles)) {
616
+ return {
617
+ ...failed(request, { code: '', message: '' }), status: 'applied', changed: true, error: null,
618
+ taskId, sourceState, targetState: spec.target, sourcePath, targetPath,
619
+ task: { operations: [] }, directory: { effect: 'unchanged', changed: false },
620
+ shortId: { effect: 'unchanged', shortId: null, changed: false },
621
+ timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion,
622
+ journalPath, completedSteps: [...completed], pendingSteps: STEPS.filter((step) => !completed.has(step))
623
+ };
624
+ }
593
625
  if (matchingCompletion(io.readFileSync(path.join(sourcePath, 'task.md')), request, restoredFiles)) completed.add('task-written');
594
626
  if (!completed.has('task-written')) {
595
627
  const current = io.readFileSync(path.join(sourcePath, 'task.md'));
@@ -687,8 +719,8 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
687
719
  };
688
720
  }
689
721
 
690
- export { lifecycleIntentCatalog, lifecycleFailureCatalog, lifecycleProducerCatalog, applyTaskLifecycle };
722
+ export { lifecycleIntentCatalog, lifecycleFailureCatalog, lifecycleProducerCatalog, applyTaskLifecycle, previewTaskCompletion };
691
723
  export type {
692
724
  TaskLifecycleIntent, TaskLifecycleRequest, TaskLifecycleResult, TaskLifecycleOptions,
693
- LifecycleError, LifecycleJournal, LifecycleStep
725
+ LifecycleError, LifecycleJournal, LifecycleStep, TaskCompletionProjection
694
726
  };