@fitlab-ai/agent-infra 0.11.1 → 0.11.3

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 (131) hide show
  1. package/dist/bin/internal-cli.js +7 -2
  2. package/dist/lib/agent-clients/adapter.js +3 -0
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
  6. package/dist/lib/agent-clients/adapters/codex.js +10 -0
  7. package/dist/lib/internal/cli-route-inventory.js +1 -1
  8. package/dist/lib/internal/manual-validation.js +22 -27
  9. package/dist/lib/internal/platform-comment.js +59 -0
  10. package/dist/lib/internal/sandbox-control.js +2 -171
  11. package/dist/lib/internal/task-artifact.js +1 -1
  12. package/dist/lib/internal/task-lifecycle.js +1 -1
  13. package/dist/lib/internal/task-operation-registry.js +7 -2
  14. package/dist/lib/internal/task-review.js +5 -5
  15. package/dist/lib/internal/task-validate.js +0 -6
  16. package/dist/lib/platform/comment-safety.js +7 -1
  17. package/dist/lib/platform/github-provider.js +13 -3
  18. package/dist/lib/platform/issue-comments.js +93 -15
  19. package/dist/lib/platform/pr-summary.js +7 -2
  20. package/dist/lib/platform/provider-contract.d.ts +2 -0
  21. package/dist/lib/platform/provider-validation.js +7 -2
  22. package/dist/lib/platform/verification-sync.js +19 -2
  23. package/dist/lib/sandbox/control/client.js +3 -44
  24. package/dist/lib/sandbox/control/executor.js +14 -117
  25. package/dist/lib/sandbox/control/finalization-response.js +8 -2
  26. package/dist/lib/sandbox/control/protocol.js +5 -64
  27. package/dist/lib/sandbox/control/server.js +29 -206
  28. package/dist/lib/sandbox/control/state.js +1 -7
  29. package/dist/lib/sandbox/control/task-view.js +2 -1
  30. package/dist/lib/task/artifact-command.js +6 -6
  31. package/dist/lib/task/codex-orchestration.js +10 -208
  32. package/dist/lib/task/commit-operation.js +3 -18
  33. package/dist/lib/task/control-authority.js +35 -44
  34. package/dist/lib/task/control-recovery.js +3 -42
  35. package/dist/lib/task/delegation-receipts.js +16 -78
  36. package/dist/lib/task/events.js +89 -237
  37. package/dist/lib/task/finalization-artifacts.js +1 -3
  38. package/dist/lib/task/finalization-state.js +2 -1
  39. package/dist/lib/task/finalization.js +208 -57
  40. package/dist/lib/task/lifecycle-execution.js +2 -11
  41. package/dist/lib/task/lifecycle.js +1 -1
  42. package/dist/lib/task/local-artifact-finalization.js +25 -242
  43. package/dist/lib/task/manual-validation-receipt.js +8 -3
  44. package/dist/lib/task/orchestration.js +12 -24
  45. package/dist/lib/task/review-command.js +0 -1
  46. package/dist/lib/task/review-finalization.js +46 -211
  47. package/dist/lifecycle-build-manifest.json +3 -3
  48. package/lib/agent-clients/adapter.ts +46 -0
  49. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
  50. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
  51. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
  52. package/lib/agent-clients/adapters/codex.ts +10 -0
  53. package/lib/internal/cli-route-inventory.ts +1 -1
  54. package/lib/internal/manual-validation.ts +22 -26
  55. package/lib/internal/platform-comment.ts +38 -0
  56. package/lib/internal/sandbox-control.ts +1 -185
  57. package/lib/internal/task-artifact.ts +1 -1
  58. package/lib/internal/task-lifecycle.ts +2 -2
  59. package/lib/internal/task-operation-registry.ts +8 -4
  60. package/lib/internal/task-review.ts +5 -5
  61. package/lib/internal/task-validate.ts +0 -5
  62. package/lib/platform/comment-safety.ts +8 -0
  63. package/lib/platform/github-provider.ts +11 -2
  64. package/lib/platform/issue-comments.ts +102 -17
  65. package/lib/platform/pr-summary.ts +10 -2
  66. package/lib/platform/provider-contract.ts +2 -0
  67. package/lib/platform/provider-validation.ts +6 -2
  68. package/lib/platform/verification-sync.ts +19 -2
  69. package/lib/sandbox/control/client.ts +5 -68
  70. package/lib/sandbox/control/executor.ts +14 -125
  71. package/lib/sandbox/control/finalization-response.ts +8 -2
  72. package/lib/sandbox/control/protocol.ts +7 -83
  73. package/lib/sandbox/control/server.ts +30 -220
  74. package/lib/sandbox/control/state.ts +1 -9
  75. package/lib/sandbox/control/task-view.ts +2 -1
  76. package/lib/task/artifact-command.ts +6 -7
  77. package/lib/task/codex-orchestration.ts +8 -241
  78. package/lib/task/commit-operation.ts +3 -23
  79. package/lib/task/control-authority.ts +39 -55
  80. package/lib/task/control-recovery.ts +3 -43
  81. package/lib/task/delegation-receipts.ts +14 -81
  82. package/lib/task/events.ts +106 -255
  83. package/lib/task/finalization-artifacts.ts +1 -3
  84. package/lib/task/finalization-state.ts +2 -1
  85. package/lib/task/finalization.ts +221 -65
  86. package/lib/task/lifecycle-execution.ts +10 -21
  87. package/lib/task/lifecycle.ts +2 -2
  88. package/lib/task/local-artifact-finalization.ts +23 -281
  89. package/lib/task/manual-validation-receipt.ts +8 -2
  90. package/lib/task/orchestration.ts +30 -53
  91. package/lib/task/review-command.ts +0 -1
  92. package/lib/task/review-finalization.ts +45 -193
  93. package/package.json +2 -2
  94. package/templates/.agents/rules/lifecycle-orchestration.en.md +23 -9
  95. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +23 -9
  96. package/templates/.agents/rules/local-artifact-repair.en.md +14 -60
  97. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -60
  98. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  99. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  100. package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
  101. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  102. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  103. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  104. package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
  105. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
  106. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
  107. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  108. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  109. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  110. package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
  111. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
  112. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  113. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  114. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
  115. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
  116. package/templates/.agents/skills/run-task/SKILL.en.md +10 -9
  117. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +10 -9
  118. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  119. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
  120. package/dist/lib/sandbox/control/task-workflow.js +0 -74
  121. package/dist/lib/sandbox/control/workflow-executor.js +0 -157
  122. package/dist/lib/task/lifecycle-recovery.js +0 -549
  123. package/dist/lib/task/recovery-warning.js +0 -19
  124. package/dist/lib/task/workflow-command.js +0 -18
  125. package/dist/lib/task/workflow-dispatch.js +0 -161
  126. package/lib/sandbox/control/task-workflow.ts +0 -95
  127. package/lib/sandbox/control/workflow-executor.ts +0 -183
  128. package/lib/task/lifecycle-recovery.ts +0 -691
  129. package/lib/task/recovery-warning.ts +0 -21
  130. package/lib/task/workflow-command.ts +0 -19
  131. package/lib/task/workflow-dispatch.ts +0 -171
@@ -1,7 +1,11 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { randomUUID } from 'node:crypto';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
4
  import { syncPlatformComment } from '../platform/issue-comments.js';
5
+ import { canonicalizeSummaryBody } from '../platform/comment-safety.js';
6
+ import { backfillCompletionComments } from '../platform/completion-backfill.js';
7
+ import { taskIssueIdentity } from '../platform/task-identities.js';
8
+ import { parseTaskFrontmatter } from './frontmatter.js';
5
9
  import { applyTaskLifecycle } from './lifecycle.js';
6
10
  import { resolveTaskRef } from './resolve-ref.js';
7
11
  import { inspectShortIdRegistry } from './short-id.js';
@@ -9,8 +13,8 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
9
13
  import { verifyTaskEvent } from './verification.js';
10
14
  import { projectFinalizationWarning } from './workflow-warning-intents.js';
11
15
  import { mergeOperationWarnings } from './operation-outcome.js';
12
- const RECEIPT_VERSION = 2;
13
- const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification'];
16
+ const RECEIPT_VERSION = 4;
17
+ const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification', 'summary'];
14
18
  function finalizationRoot(repoRoot) {
15
19
  return path.join(repoRoot, '.agents', 'workspace', '.task-finalization');
16
20
  }
@@ -31,9 +35,11 @@ function failed(taskId, error, overrides = {}) {
31
35
  status: error.retryable ? 'blocked' : 'failed',
32
36
  changed: false,
33
37
  taskId,
38
+ backfill: null,
34
39
  lifecycle: null,
35
40
  taskComment: null,
36
41
  verification: null,
42
+ summary: null,
37
43
  completedSteps: [],
38
44
  pendingSteps: [...FINALIZATION_STEPS],
39
45
  result: error.retryable ? 'blocked' : 'failed',
@@ -52,6 +58,7 @@ function emptyReceipt(taskId, controlBinding) {
52
58
  lifecycle: 'pending',
53
59
  taskComment: 'pending',
54
60
  verification: 'pending',
61
+ summary: 'pending',
55
62
  warningProjection: 'done',
56
63
  warnings: [],
57
64
  ...(controlBinding ? { controlBinding } : {}),
@@ -88,6 +95,7 @@ function validateReceipt(value, taskId) {
88
95
  || !states.includes(String(receipt.lifecycle))
89
96
  || !states.includes(String(receipt.taskComment))
90
97
  || !states.includes(String(receipt.verification))
98
+ || !states.includes(String(receipt.summary))
91
99
  || !['pending', 'done'].includes(String(receipt.warningProjection))
92
100
  || !Array.isArray(receipt.warnings) || receipt.warnings.some((warning) => !validWarning(warning))
93
101
  || typeof receipt.updatedAt !== 'string'
@@ -109,10 +117,6 @@ function readReceipt(repoRoot, taskId) {
109
117
  function readTaskFinalizationReceipt(repoRoot, taskId) {
110
118
  return readReceipt(path.resolve(repoRoot), taskId);
111
119
  }
112
- function controlBindingMatches(receipt, binding) {
113
- return receipt.controlBinding?.generation === binding.generation
114
- && receipt.controlBinding.requestId === binding.requestId;
115
- }
116
120
  function writeReceipt(repoRoot, receipt) {
117
121
  const directory = finalizationRoot(repoRoot);
118
122
  fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
@@ -132,20 +136,22 @@ function writeReceipt(repoRoot, receipt) {
132
136
  }
133
137
  function updateReceipt(repoRoot, receipt, patch) {
134
138
  const current = readReceipt(repoRoot, receipt.taskId);
135
- if (!current || current.receiptId !== receipt.receiptId || current.revision !== receipt.revision) {
136
- const error = new Error('finalization receipt revision is stale');
137
- Object.assign(error, { code: 'FINALIZATION_CAPABILITY_STALE' });
138
- throw error;
139
- }
139
+ if (!current || current.receiptId !== receipt.receiptId)
140
+ throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization receipt does not belong to the current task');
140
141
  const next = { ...current, ...patch, revision: current.revision + 1, updatedAt: now() };
141
142
  writeReceipt(repoRoot, next);
142
143
  return next;
143
144
  }
144
145
  function completedSteps(receipt) {
145
- return FINALIZATION_STEPS.filter((step) => receipt[step === 'task-comment' ? 'taskComment' : step] !== 'pending');
146
+ return FINALIZATION_STEPS.filter((step) => stepState(receipt, step) !== 'pending');
146
147
  }
147
148
  function pendingSteps(receipt) {
148
- return FINALIZATION_STEPS.filter((step) => receipt[step === 'task-comment' ? 'taskComment' : step] === 'pending');
149
+ return FINALIZATION_STEPS.filter((step) => stepState(receipt, step) === 'pending');
150
+ }
151
+ function stepState(receipt, step) {
152
+ if (step === 'task-comment')
153
+ return receipt.taskComment;
154
+ return receipt[step];
149
155
  }
150
156
  function lifecycleStep(result) {
151
157
  return {
@@ -234,9 +240,8 @@ function mutationKeys(value) {
234
240
  return Object.keys(value).sort();
235
241
  }
236
242
  function validateCapabilityMutation(current, capability, mutation, consumed) {
237
- if (capability.receiptId !== current.receiptId || capability.baseRevision !== current.revision || consumed.has(capability.nonce)) {
238
- throw capabilityError('FINALIZATION_CAPABILITY_STALE', 'finalization capability is stale or outside its scope');
239
- }
243
+ if (capability.receiptId !== current.receiptId)
244
+ throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization mutation belongs to a different task receipt');
240
245
  if (capability.scope !== mutation.scope) {
241
246
  throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability mutation scope does not match its capability');
242
247
  }
@@ -403,9 +408,80 @@ async function syncPendingTaskComment(input) {
403
408
  return { receipt, step: { status: 'blocked', changed: false, error: detail }, changed: false, error: detail };
404
409
  }
405
410
  }
411
+ function readDeliverySummary(taskDir, taskId) {
412
+ const file = path.join(taskDir, '.delivery-summary.json');
413
+ let value;
414
+ try {
415
+ value = JSON.parse(fs.readFileSync(file, 'utf8'));
416
+ }
417
+ catch {
418
+ throw Object.assign(new Error('delivery summary staging record is unavailable'), { code: 'SUMMARY_STAGING_INVALID' });
419
+ }
420
+ if (value.taskId !== taskId || typeof value.body !== 'string' || typeof value.sha256 !== 'string') {
421
+ throw Object.assign(new Error('delivery summary staging record is invalid'), { code: 'SUMMARY_STAGING_INVALID' });
422
+ }
423
+ const canonical = canonicalizeSummaryBody(value.body);
424
+ if (!canonical.ok || canonical.value !== value.body) {
425
+ throw Object.assign(new Error('delivery summary staging body is not canonical'), { code: 'SUMMARY_STAGING_INVALID' });
426
+ }
427
+ const digest = createHash('sha256').update(canonical.value).digest('hex');
428
+ if (digest !== value.sha256)
429
+ throw Object.assign(new Error('delivery summary staging digest does not match body'), { code: 'SUMMARY_STAGING_INVALID' });
430
+ return { body: canonical.value, sha256: value.sha256 };
431
+ }
432
+ async function syncPendingSummary(input) {
433
+ let { receipt } = input;
434
+ try {
435
+ const resolved = resolveTaskRef(input.taskId, { repoRoot: input.repoRoot });
436
+ if (!resolved.ok || resolved.state !== 'completed')
437
+ throw Object.assign(new Error('completed task is unavailable for summary sync'), { code: 'SUMMARY_STAGING_INVALID' });
438
+ if (!taskIssueIdentity(parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8')))) {
439
+ receipt = updateReceipt(input.repoRoot, receipt, {
440
+ summary: 'skipped', lastError: null
441
+ });
442
+ return { receipt, step: { status: 'skipped', changed: false, error: null }, changed: false, error: null };
443
+ }
444
+ const summary = readDeliverySummary(resolved.taskDir, input.taskId);
445
+ const result = await input.commentSync(input.taskId, {
446
+ kind: 'summary', agent: input.agent, body: summary.body, cwd: input.repoRoot,
447
+ summaryAuthorization: { sha256: summary.sha256 }
448
+ });
449
+ const step = commentStep(result);
450
+ if (result.status === 'applied' || result.status === 'no-op') {
451
+ const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
452
+ const resolvedWarning = receipt.warnings.some((warning) => warning.step === 'summary' && warning.status === 'open');
453
+ const warnings = resolveStepWarnings(receipt, 'summary');
454
+ receipt = updateReceipt(input.repoRoot, receipt, {
455
+ summary: skipped ? 'skipped' : 'done',
456
+ taskComment: resolvedWarning ? 'pending' : receipt.taskComment,
457
+ warningProjection: warnings.some((warning) => warning.status === 'open') ? 'pending' : 'done',
458
+ warnings,
459
+ lastError: null
460
+ });
461
+ return { receipt, step: skipped ? { ...step, status: 'skipped' } : step, changed: result.changed, error: null };
462
+ }
463
+ const error = step.error ?? { code: 'SUMMARY_SYNC_FAILED', message: 'delivery summary synchronization failed', retryable: true };
464
+ const warning = warningFromError('summary', error);
465
+ const alreadyOpen = receipt.warnings.some((item) => item.step === warning.step && item.code === warning.code && item.target === warning.target && item.status === 'open');
466
+ const warnings = replaceWarning(receipt, warning, 'open');
467
+ receipt = updateReceipt(input.repoRoot, receipt, { summary: 'pending', taskComment: alreadyOpen ? receipt.taskComment : 'pending', warningProjection: 'pending', warnings, lastError: error });
468
+ return { receipt, step, changed: result.changed, error };
469
+ }
470
+ catch (cause) {
471
+ const error = errorOf(cause, 'SUMMARY_SYNC_FAILED', true);
472
+ try {
473
+ const warning = warningFromError('summary', error);
474
+ const alreadyOpen = receipt.warnings.some((item) => item.step === warning.step && item.code === warning.code && item.target === warning.target && item.status === 'open');
475
+ const warnings = replaceWarning(receipt, warning, 'open');
476
+ receipt = updateReceipt(input.repoRoot, receipt, { summary: 'pending', taskComment: alreadyOpen ? receipt.taskComment : 'pending', warningProjection: 'pending', warnings, lastError: error });
477
+ }
478
+ catch { /* preserve primary error */ }
479
+ return { receipt, step: { status: 'blocked', changed: false, error }, changed: false, error };
480
+ }
481
+ }
406
482
  function terminalResult(taskId, receipt, steps, changed, error = null) {
407
483
  const pending = pendingSteps(receipt);
408
- const blocked = [steps.lifecycle, steps.taskComment, steps.verification].some((step) => step?.status === 'blocked');
484
+ const blocked = [steps.backfill, steps.lifecycle, steps.taskComment, steps.verification, steps.summary].some((step) => step?.status === 'blocked');
409
485
  const warnings = openWarnings(receipt);
410
486
  const postLifecyclePending = receipt.lifecycle === 'done' && (pending.some((step) => step !== 'lifecycle') || receipt.warningProjection === 'pending');
411
487
  const hardError = error?.code.startsWith('FINALIZATION_') || error?.code === 'TASK_FINALIZATION_RECEIPT_INVALID';
@@ -413,14 +489,18 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
413
489
  status: hardError ? (error?.retryable ? 'blocked' : 'failed') : pending.length === 0 ? 'completed' : postLifecyclePending ? 'completed' : blocked ? 'blocked' : 'failed',
414
490
  changed,
415
491
  taskId,
416
- ...steps,
492
+ backfill: steps.backfill ?? null,
493
+ lifecycle: steps.lifecycle ?? null,
494
+ taskComment: steps.taskComment ?? null,
495
+ verification: steps.verification ?? null,
496
+ summary: steps.summary ?? null,
417
497
  completedSteps: completedSteps(receipt),
418
498
  pendingSteps: pending,
419
- result: postLifecyclePending && (warnings.length > 0 || receipt.warningProjection === 'pending')
420
- ? 'completed_with_warnings'
421
- : hardError
422
- ? (error?.retryable ? 'blocked' : 'failed')
423
- : pending.length === 0 || postLifecyclePending
499
+ result: hardError
500
+ ? (error?.retryable ? 'blocked' : 'failed')
501
+ : postLifecyclePending
502
+ ? 'completed_with_warnings'
503
+ : pending.length === 0
424
504
  ? 'completed'
425
505
  : blocked
426
506
  ? 'blocked'
@@ -432,6 +512,7 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
432
512
  async function applyUnderLock(request, taskId, options) {
433
513
  const repoRoot = path.resolve(options.repoRoot);
434
514
  const lifecycle = options.lifecycle ?? applyTaskLifecycle;
515
+ const backfill = options.backfill ?? backfillCompletionComments;
435
516
  const commentSync = options.commentSync ?? syncPlatformComment;
436
517
  const verify = options.verify ?? verifyTaskEvent;
437
518
  const consumedCapabilities = new Set();
@@ -446,17 +527,9 @@ async function applyUnderLock(request, taskId, options) {
446
527
  catch (error) {
447
528
  return failed(taskId, errorOf(error, 'TASK_FINALIZATION_RECEIPT_INVALID'));
448
529
  }
449
- if (options.controlBinding && !controlBindingMatches(receipt, options.controlBinding)) {
450
- const error = {
451
- code: 'TASK_FINALIZATION_CONTROL_BINDING_CONFLICT',
452
- message: 'finalization receipt belongs to a different sandbox request or generation',
453
- retryable: false
454
- };
455
- return failed(taskId, error, {
456
- completedSteps: completedSteps(receipt),
457
- pendingSteps: pendingSteps(receipt)
458
- });
459
- }
530
+ if (options.controlBinding && (receipt.controlBinding?.generation !== options.controlBinding.generation
531
+ || receipt.controlBinding.requestId !== options.controlBinding.requestId))
532
+ receipt = updateReceipt(repoRoot, receipt, { controlBinding: options.controlBinding });
460
533
  const preflightState = resolveTaskRef(taskId, { repoRoot });
461
534
  if (options.preflight && preflightState.ok && preflightState.state === 'active') {
462
535
  try {
@@ -476,8 +549,36 @@ async function applyUnderLock(request, taskId, options) {
476
549
  return failed(taskId, errorOf(error, 'TASK_FINALIZATION_PREFLIGHT_FAILED', true));
477
550
  }
478
551
  }
479
- let lifecycleResult = null;
480
552
  let changed = false;
553
+ let backfillResult = null;
554
+ try {
555
+ const result = await backfill(taskId, { agent: request.agent, cwd: repoRoot });
556
+ backfillResult = commentStep(result);
557
+ const issueNotLinked = result.status === 'no-op' && result.error?.code === 'ISSUE_NOT_LINKED';
558
+ if (!issueNotLinked && (result.status !== 'applied' && result.status !== 'no-op' || result.error)) {
559
+ const detail = backfillResult.error ?? {
560
+ code: 'COMPLETION_BACKFILL_FAILED', message: 'completion artifact backfill did not reach a successful terminal result', retryable: true
561
+ };
562
+ return failed(taskId, detail, {
563
+ backfill: backfillResult,
564
+ completedSteps: completedSteps(receipt),
565
+ pendingSteps: pendingSteps(receipt)
566
+ });
567
+ }
568
+ changed = result.changed;
569
+ if (result.changed && receipt.taskComment !== 'pending') {
570
+ receipt = updateReceipt(repoRoot, receipt, { taskComment: 'pending' });
571
+ }
572
+ }
573
+ catch (error) {
574
+ const detail = errorOf(error, 'COMPLETION_BACKFILL_FAILED', true);
575
+ return failed(taskId, detail, {
576
+ backfill: { status: detail.retryable ? 'blocked' : 'failed', changed: false, error: detail },
577
+ completedSteps: completedSteps(receipt),
578
+ pendingSteps: pendingSteps(receipt)
579
+ });
580
+ }
581
+ let lifecycleResult = null;
481
582
  try {
482
583
  const resolved = resolveTaskRef(taskId, { repoRoot });
483
584
  const registry = resolved.ok && resolved.state === 'completed' ? inspectShortIdRegistry(repoRoot) : null;
@@ -489,6 +590,7 @@ async function applyUnderLock(request, taskId, options) {
489
590
  };
490
591
  receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
491
592
  return terminalResult(taskId, receipt, {
593
+ backfill: backfillResult,
492
594
  lifecycle: { status: 'failed', changed: false, error: detail },
493
595
  taskComment: null,
494
596
  verification: null
@@ -503,6 +605,7 @@ async function applyUnderLock(request, taskId, options) {
503
605
  };
504
606
  receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
505
607
  return terminalResult(taskId, receipt, {
608
+ backfill: backfillResult,
506
609
  lifecycle: { status: 'failed', changed: false, error: detail },
507
610
  taskComment: null,
508
611
  verification: null
@@ -518,9 +621,9 @@ async function applyUnderLock(request, taskId, options) {
518
621
  lifecycleResult = lifecycleStep(result);
519
622
  if (result.status !== 'applied' && result.status !== 'no-op') {
520
623
  receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: lifecycleResult.error });
521
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment: null, verification: null }, result.changed, lifecycleResult.error);
624
+ return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment: null, verification: null }, result.changed, lifecycleResult.error);
522
625
  }
523
- changed = result.changed;
626
+ changed = changed || result.changed;
524
627
  receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
525
628
  }
526
629
  }
@@ -530,7 +633,7 @@ async function applyUnderLock(request, taskId, options) {
530
633
  receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
531
634
  }
532
635
  catch { /* preserve the primary error */ }
533
- return failed(taskId, detail, { lifecycle: lifecycleResult });
636
+ return failed(taskId, detail, { backfill: backfillResult, lifecycle: lifecycleResult });
534
637
  }
535
638
  let taskComment = null;
536
639
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
@@ -541,18 +644,16 @@ async function applyUnderLock(request, taskId, options) {
541
644
  taskComment = initialComment.step;
542
645
  changed = changed || initialComment.changed;
543
646
  if (initialComment.error) {
544
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: null }, changed, initialComment.error);
647
+ return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: null }, changed, initialComment.error);
545
648
  }
546
649
  let verification = null;
547
650
  try {
548
651
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
549
- if (receipt.verification !== 'pending') {
550
- verification = { status: 'no-op', changed: false, error: null };
551
- }
552
- else {
553
- const result = await verify({ taskRef: taskId, event: 'complete-task.completed' }, { repoRoot });
554
- verification = verificationStep(result);
555
- if (result.status === 'pass') {
652
+ const verificationPending = receipt.verification === 'pending';
653
+ const result = await verify({ taskRef: taskId, event: 'complete-task.completed' }, { repoRoot });
654
+ verification = verificationStep(result);
655
+ if (result.status === 'pass') {
656
+ if (verificationPending) {
556
657
  const capability = issueCapability(receipt, 'verification');
557
658
  receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
558
659
  scope: 'verification', operation: 'succeeded'
@@ -566,34 +667,84 @@ async function applyUnderLock(request, taskId, options) {
566
667
  taskComment = finalComment.step;
567
668
  changed = changed || finalComment.changed;
568
669
  if (finalComment.error) {
569
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
670
+ return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
570
671
  }
571
672
  }
572
673
  else {
573
- const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
674
+ receipt = updateReceipt(repoRoot, receipt, { lastError: null });
675
+ }
676
+ }
677
+ else {
678
+ const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
679
+ if (verificationPending) {
574
680
  const capability = issueCapability(receipt, 'verification');
575
681
  receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
576
682
  scope: 'verification', operation: 'failed', error: detail
577
683
  }, consumedCapabilities);
578
684
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
579
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, verification.error);
580
685
  }
686
+ else {
687
+ const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
688
+ receipt = updateReceipt(repoRoot, receipt, {
689
+ verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
690
+ });
691
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
692
+ const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
693
+ receipt = warningComment.receipt;
694
+ if (warningComment.step.status !== 'no-op')
695
+ taskComment = warningComment.step;
696
+ changed = changed || warningComment.changed;
697
+ }
698
+ return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, detail);
581
699
  }
582
700
  }
583
701
  catch (error) {
584
702
  const detail = errorOf(error, 'VERIFY_FAILED', true);
585
703
  try {
586
- const capability = issueCapability(receipt, 'verification');
587
- receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
588
- scope: 'verification', operation: 'failed', error: detail
589
- }, consumedCapabilities);
704
+ if (receipt.verification === 'pending') {
705
+ const capability = issueCapability(receipt, 'verification');
706
+ receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
707
+ scope: 'verification', operation: 'failed', error: detail
708
+ }, consumedCapabilities);
709
+ }
710
+ else {
711
+ const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
712
+ receipt = updateReceipt(repoRoot, receipt, {
713
+ verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
714
+ });
715
+ }
590
716
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
591
717
  }
718
+ catch (writeError) {
719
+ const persistence = errorOf(writeError, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
720
+ const hardError = {
721
+ code: 'FINALIZATION_RECEIPT_WRITE_FAILED',
722
+ message: `Unable to persist verification failure (${detail.code}: ${detail.message}): ${persistence.message}`,
723
+ retryable: true
724
+ };
725
+ return terminalResult(taskId, receipt, {
726
+ backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail }
727
+ }, changed, hardError);
728
+ }
729
+ try {
730
+ const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
731
+ receipt = warningComment.receipt;
732
+ if (warningComment.step.status !== 'no-op')
733
+ taskComment = warningComment.step;
734
+ changed = changed || warningComment.changed;
735
+ }
592
736
  catch { /* preserve the primary error */ }
593
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
737
+ return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
738
+ }
739
+ let summary = await syncPendingSummary({ repoRoot, taskId, agent: request.agent, receipt, commentSync });
740
+ receipt = summary.receipt;
741
+ changed = changed || summary.changed;
742
+ if (summary.error) {
743
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
744
+ return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification, summary: summary.step }, changed, summary.error);
594
745
  }
595
746
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
596
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed);
747
+ return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification, summary: summary.step }, changed);
597
748
  }
598
749
  async function applyTaskFinalization(request, options) {
599
750
  if (request.intent !== 'complete' || !request.taskRef || !request.agent) {
@@ -613,5 +764,5 @@ async function applyTaskFinalization(request, options) {
613
764
  return failed(resolved.taskId, detail);
614
765
  }
615
766
  }
616
- export { applyFinalizationReceiptMutation, applyTaskFinalization, issueCapability as createFinalizationCapability, readTaskFinalizationReceipt };
767
+ export { applyFinalizationReceiptMutation, applyTaskFinalization, issueCapability as createFinalizationCapability, readTaskFinalizationReceipt, terminalResult };
617
768
  //# sourceMappingURL=finalization.js.map
@@ -1,4 +1,4 @@
1
- import { inspectOrchestrationStage, pauseOrchestration, planOrchestrationStageCompletion, readRun } from './orchestration.js';
1
+ import { inspectOrchestrationStage, pauseOrchestration, planOrchestrationStageCompletion } from './orchestration.js';
2
2
  import { resolveTaskRef } from './resolve-ref.js';
3
3
  function failure(mode, code, message) {
4
4
  return { ok: false, mode, completionPlan: null, error: { code, message } };
@@ -8,16 +8,7 @@ function inspectLifecycleExecution(taskRef, request, options = {}) {
8
8
  if (!resolved.ok)
9
9
  return failure(request.mode, resolved.code, resolved.message);
10
10
  if (request.mode === 'standalone') {
11
- try {
12
- const run = readRun(resolved.taskDir);
13
- if (run?.pendingDelegation) {
14
- return failure(request.mode, 'ORCHESTRATION_STANDALONE_BUSY', 'standalone lifecycle execution is blocked by a pending orchestration delegation');
15
- }
16
- return { ok: true, mode: request.mode, completionPlan: null, error: null };
17
- }
18
- catch (error) {
19
- return failure(request.mode, 'ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error));
20
- }
11
+ return { ok: true, mode: request.mode, completionPlan: null, error: null };
21
12
  }
22
13
  if (request.agent) {
23
14
  const planned = planOrchestrationStageCompletion(taskRef, {
@@ -151,7 +151,7 @@ function actionAndNote(request, restoredFiles = 0) {
151
151
  if (request.intent === 'close-dependabot')
152
152
  return { action: 'Close Dependabot', note: `Dependabot alert #${request.alertNumber} dismissed: ${request.reason}` };
153
153
  if (request.intent === 'recover-started')
154
- return { action: 'Recover Started', note: request.reason };
154
+ return { action: 'Recover Started', note: 'automatic recovery' };
155
155
  return { action: 'Restore Task', note: `Restored ${restoredFiles} files from Issue #${request.issueNumber}` };
156
156
  }
157
157
  function matchingCompletion(content, request, restoredFiles = 0) {