@fitlab-ai/agent-infra 0.11.2 → 0.11.4

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 (162) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +7 -2
  4. package/dist/lib/agent-clients/adapter.js +3 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
  8. package/dist/lib/agent-clients/adapters/codex.js +10 -0
  9. package/dist/lib/internal/cli-route-inventory.js +1 -1
  10. package/dist/lib/internal/manual-validation.js +22 -27
  11. package/dist/lib/internal/sandbox-control.js +1 -43
  12. package/dist/lib/internal/task-artifact.js +1 -1
  13. package/dist/lib/internal/task-finalization.js +41 -1
  14. package/dist/lib/internal/task-ledger.js +13 -5
  15. package/dist/lib/internal/task-lifecycle.js +1 -1
  16. package/dist/lib/internal/task-operation-registry.js +7 -2
  17. package/dist/lib/platform/completion-backfill.js +63 -17
  18. package/dist/lib/platform/issue-comments.js +2 -31
  19. package/dist/lib/platform/pr-summary.js +7 -2
  20. package/dist/lib/sandbox/control/client.js +3 -44
  21. package/dist/lib/sandbox/control/executor.js +14 -79
  22. package/dist/lib/sandbox/control/protocol.js +5 -64
  23. package/dist/lib/sandbox/control/server.js +49 -343
  24. package/dist/lib/sandbox/control/state.js +1 -7
  25. package/dist/lib/sandbox/control/task-view.js +1 -2
  26. package/dist/lib/sandbox/task-cleanup.js +13 -20
  27. package/dist/lib/task/artifact-command.js +11 -4
  28. package/dist/lib/task/artifact-lifecycle.js +217 -85
  29. package/dist/lib/task/artifact-operations.js +1 -26
  30. package/dist/lib/task/artifact-receipts.js +4 -1
  31. package/dist/lib/task/artifact-schema.js +1 -0
  32. package/dist/lib/task/artifact-selection.js +25 -0
  33. package/dist/lib/task/capabilities.js +158 -39
  34. package/dist/lib/task/commands/status.js +9 -2
  35. package/dist/lib/task/control-authority.js +37 -38
  36. package/dist/lib/task/control-recovery.js +3 -42
  37. package/dist/lib/task/decision-intents.js +12 -0
  38. package/dist/lib/task/delegation-receipts.js +2 -2
  39. package/dist/lib/task/delivery.js +1 -43
  40. package/dist/lib/task/events.js +80 -64
  41. package/dist/lib/task/finalization-artifacts.js +1 -3
  42. package/dist/lib/task/finalization-state.js +1 -1
  43. package/dist/lib/task/finalization.js +319 -234
  44. package/dist/lib/task/ledger-intents.js +157 -15
  45. package/dist/lib/task/lifecycle-path.js +75 -0
  46. package/dist/lib/task/lifecycle.js +11 -1
  47. package/dist/lib/task/local-artifact-finalization.js +1 -20
  48. package/dist/lib/task/manual-validation-receipt.js +8 -3
  49. package/dist/lib/task/orchestration.js +29 -13
  50. package/dist/lib/task/review-finalization.js +3 -16
  51. package/dist/lib/task/rework-intent.js +70 -4
  52. package/dist/lib/task/verification-engine.js +20 -30
  53. package/dist/lib/task/verification.js +1 -0
  54. package/dist/lib/task/workflow-warning-intents.js +21 -6
  55. package/dist/lib/task/workspace-snapshot.js +7 -2
  56. package/dist/lifecycle-build-manifest.json +3 -3
  57. package/lib/agent-clients/adapter.ts +46 -0
  58. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
  59. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
  60. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
  61. package/lib/agent-clients/adapters/codex.ts +10 -0
  62. package/lib/internal/cli-route-inventory.ts +1 -1
  63. package/lib/internal/manual-validation.ts +22 -26
  64. package/lib/internal/sandbox-control.ts +0 -42
  65. package/lib/internal/task-artifact.ts +1 -1
  66. package/lib/internal/task-finalization.ts +40 -1
  67. package/lib/internal/task-ledger.ts +12 -5
  68. package/lib/internal/task-lifecycle.ts +2 -2
  69. package/lib/internal/task-operation-registry.ts +8 -4
  70. package/lib/platform/completion-backfill.ts +78 -16
  71. package/lib/platform/issue-comments.ts +1 -35
  72. package/lib/platform/pr-summary.ts +10 -2
  73. package/lib/sandbox/control/client.ts +5 -68
  74. package/lib/sandbox/control/executor.ts +14 -81
  75. package/lib/sandbox/control/protocol.ts +7 -83
  76. package/lib/sandbox/control/server.ts +47 -362
  77. package/lib/sandbox/control/state.ts +1 -9
  78. package/lib/sandbox/control/task-view.ts +1 -2
  79. package/lib/sandbox/task-cleanup.ts +12 -20
  80. package/lib/task/artifact-command.ts +14 -4
  81. package/lib/task/artifact-lifecycle.ts +236 -85
  82. package/lib/task/artifact-operations.ts +1 -26
  83. package/lib/task/artifact-receipts.ts +4 -1
  84. package/lib/task/artifact-schema.ts +1 -0
  85. package/lib/task/artifact-selection.ts +53 -0
  86. package/lib/task/capabilities.ts +154 -40
  87. package/lib/task/commands/status.ts +11 -2
  88. package/lib/task/control-authority.ts +41 -48
  89. package/lib/task/control-recovery.ts +3 -43
  90. package/lib/task/decision-intents.ts +9 -0
  91. package/lib/task/delegation-receipts.ts +2 -2
  92. package/lib/task/delivery.ts +1 -40
  93. package/lib/task/events.ts +82 -64
  94. package/lib/task/finalization-artifacts.ts +1 -3
  95. package/lib/task/finalization-state.ts +1 -1
  96. package/lib/task/finalization.ts +310 -216
  97. package/lib/task/ledger-intents.ts +155 -15
  98. package/lib/task/lifecycle-path.ts +98 -0
  99. package/lib/task/lifecycle.ts +15 -2
  100. package/lib/task/local-artifact-finalization.ts +2 -25
  101. package/lib/task/manual-validation-receipt.ts +8 -2
  102. package/lib/task/orchestration.ts +56 -27
  103. package/lib/task/review-finalization.ts +3 -16
  104. package/lib/task/rework-intent.ts +75 -5
  105. package/lib/task/verification-engine.ts +19 -30
  106. package/lib/task/verification.ts +2 -1
  107. package/lib/task/workflow-warning-intents.ts +18 -6
  108. package/lib/task/workspace-snapshot.ts +3 -2
  109. package/package.json +1 -1
  110. package/templates/.agents/rules/lifecycle-orchestration.en.md +19 -5
  111. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +19 -5
  112. package/templates/.agents/rules/review-handshake.en.md +4 -4
  113. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  114. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
  115. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
  116. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  117. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  118. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  119. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  120. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  121. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  122. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  123. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  124. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  125. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  126. package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
  127. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
  128. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  129. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  130. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  131. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  132. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  133. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  134. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  135. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  136. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
  137. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
  138. package/templates/.agents/skills/run-task/SKILL.en.md +9 -8
  139. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -8
  140. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  141. package/templates/.agents/templates/task.en.md +2 -2
  142. package/templates/.agents/templates/task.zh-CN.md +2 -2
  143. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  144. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  145. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  146. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  147. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  148. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  149. package/dist/lib/sandbox/control/finalization-response.js +0 -35
  150. package/dist/lib/sandbox/control/task-workflow.js +0 -74
  151. package/dist/lib/sandbox/control/workflow-executor.js +0 -142
  152. package/dist/lib/task/lifecycle-recovery.js +0 -549
  153. package/dist/lib/task/recovery-warning.js +0 -19
  154. package/dist/lib/task/workflow-command.js +0 -21
  155. package/dist/lib/task/workflow-dispatch.js +0 -213
  156. package/lib/sandbox/control/finalization-response.ts +0 -37
  157. package/lib/sandbox/control/task-workflow.ts +0 -95
  158. package/lib/sandbox/control/workflow-executor.ts +0 -156
  159. package/lib/task/lifecycle-recovery.ts +0 -691
  160. package/lib/task/recovery-warning.ts +0 -21
  161. package/lib/task/workflow-command.ts +0 -22
  162. package/lib/task/workflow-dispatch.ts +0 -214
@@ -5,7 +5,7 @@ import { resolveTaskRef } from '../task/resolve-ref.js';
5
5
  import { applyTaskEvent } from '../task/events.js';
6
6
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
7
7
  import { normalizeAgentToken } from '../agent-clients/tokens.js';
8
- import { createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, readManualValidationReceipt, writeManualValidationReceiptAtomic } from '../task/manual-validation-receipt.js';
8
+ import { MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, readManualValidationReceipt, writeManualValidationReceiptAtomic } from '../task/manual-validation-receipt.js';
9
9
  import { archiveManualValidationGeneration, createManualValidationTransaction, readManualValidationTransaction, retryManualValidationTransaction, summaryPreimageDigest, transitionManualValidationTransaction, validateManualValidationGenerationArchive, writeManualValidationTransactionAtomic } from '../task/manual-validation-transaction.js';
10
10
  import { sha256File } from '../task/artifact-receipts.js';
11
11
  import { summaryCommentState, syncPullRequestSummary } from '../platform/pr-summary.js';
@@ -22,28 +22,22 @@ function printFailure(format, error) {
22
22
  process.stderr.write(`${error.code}: ${error.message}\n`);
23
23
  process.exitCode = 1;
24
24
  }
25
- const MANUAL_VALIDATION_STATUS_SECTION = /^###\s+(?:⚠️\s+(?:需人工校验|Manual Validation Required)|✅\s+(?:人工验证已通过|无需人工校验|Manual Validation Passed|No Manual Validation Required)|⏳\s+(?:人工验证待收尾|Manual Validation Pending))\s*$[\s\S]*?(?=^#{1,3}\s|(?![\s\S]))/gmu;
26
- const TRAILING_CANONICAL_REPORT_PLACEHOLDER = /\n*<!--\s*canonical-pr-change-report\s*-->\s*$/u;
27
- function manualSummaryBody(body, phase, transactionId, receiptDigest = '', evidenceDigest = '', prHeadSha = '') {
25
+ const MANUAL_VALIDATION_STATUS_HEADING = /^###\s+(?:⚠️\s+(?:需人工校验|Manual Validation Required)|✅\s+(?:人工验证已通过|无需人工校验|Manual Validation Passed|No Manual Validation Required)|⏳\s+(?:人工验证待收尾|Manual Validation Pending))\s*$/mu;
26
+ function manualSummaryBody(body, phase) {
28
27
  const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
29
- const section = phase === 'pending'
30
- ? chinese
31
- ? `### ⏳ 人工验证待收尾\n\n人工验证证据已暂存;事务 ${transactionId} 正在等待最终完成。`
32
- : `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
33
- : chinese
34
- ? `### ✅ 人工验证已通过\n\n人工验证已通过;transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`
35
- : `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
36
- let inserted = false;
37
- const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, (matched) => {
38
- if (inserted)
39
- return '';
40
- inserted = true;
41
- const trailingPlaceholder = TRAILING_CANONICAL_REPORT_PLACEHOLDER.exec(matched)?.[0];
42
- return trailingPlaceholder
43
- ? `${trailingPlaceholder.trim()}\n\n${section}\n\n`
44
- : `${section}\n\n`;
45
- });
46
- return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
28
+ const heading = phase === 'pending'
29
+ ? chinese ? '### ⏳ 人工验证待收尾' : '### ⏳ Manual Validation Pending'
30
+ : chinese ? '### ✅ 人工验证已通过' : '### ✅ Manual Validation Passed';
31
+ const marker = phase === 'final'
32
+ ? `\n\n${MANUAL_VALIDATION_RECEIPT_PLACEHOLDER}`
33
+ : '';
34
+ if (MANUAL_VALIDATION_STATUS_HEADING.test(body)) {
35
+ return `${body.replace(MANUAL_VALIDATION_STATUS_HEADING, `${heading}${marker}`).replace(/\s+$/u, '')}\n`;
36
+ }
37
+ const fallback = phase === 'pending'
38
+ ? chinese ? '人工验证正在等待最终完成。' : 'Manual validation is awaiting final promotion.'
39
+ : chinese ? '人工验证已通过。' : 'Manual validation passed.';
40
+ return `${body.replace(/\s+$/u, '')}\n\n${heading}${marker}\n\n${fallback}\n`;
47
41
  }
48
42
  function openManualValidationStarted(taskMdPath) {
49
43
  let content;
@@ -102,7 +96,9 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
102
96
  const pullRequest = currentState.pullRequest;
103
97
  const preimageBody = currentState.comment?.body ?? '';
104
98
  const preimage = { commentId: currentState.comment?.id ?? null, body: preimageBody, digest: summaryPreimageDigest(preimageBody) };
105
- const evidenceDigest = sha256File(path.resolve(cwd, values.summaryFile));
99
+ const summaryPath = path.resolve(cwd, values.summaryFile);
100
+ const finalBodyWithoutReceipt = fs.readFileSync(summaryPath, 'utf8');
101
+ const evidenceDigest = sha256File(summaryPath);
106
102
  const archiveGeneration = options.archiveGeneration ?? archiveManualValidationGeneration;
107
103
  let transactionResult = readManualValidationTransaction(resolved.taskDir, { taskId: resolved.taskId, prNumber: pullRequest.number, prHeadSha: pullRequest.head.sha, evidenceDigest, artifact: values.artifact });
108
104
  let previousTransaction = null;
@@ -135,8 +131,7 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
135
131
  const transactionId = transactionResult.ok
136
132
  ? transactionResult.value.transactionId
137
133
  : (openStarted.transactionId ?? values.transactionId ?? `mv-${Date.now()}`);
138
- const finalBodyWithoutReceipt = fs.readFileSync(path.resolve(cwd, values.summaryFile), 'utf8');
139
- const pendingBody = manualSummaryBody(finalBodyWithoutReceipt, 'pending', transactionId);
134
+ const pendingBody = manualSummaryBody(finalBodyWithoutReceipt, 'pending');
140
135
  const createTransaction = () => createManualValidationTransaction({
141
136
  transactionId,
142
137
  taskId: resolved.taskId,
@@ -145,7 +140,7 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
145
140
  evidenceDigest,
146
141
  summaryPreimage: preimage,
147
142
  pendingSummaryDigest: summaryPreimageDigest(pendingBody),
148
- finalSummaryDigest: manualValidationFinalSummaryDigest(manualSummaryBody(finalBodyWithoutReceipt, 'final', transactionId, '<receipt>', evidenceDigest, pullRequest.head.sha)),
143
+ finalSummaryDigest: manualValidationFinalSummaryDigest(manualSummaryBody(finalBodyWithoutReceipt, 'final')),
149
144
  artifact: values.artifact,
150
145
  attempt: 1,
151
146
  createdAt: new Date().toISOString(),
@@ -249,7 +244,7 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
249
244
  transitionAndPersist('final-promotion-in-progress');
250
245
  }
251
246
  const promoted = await syncPullRequestSummary(taskRef, {
252
- cwd, client: options.client, agent, body: manualSummaryBody(finalBodyWithoutReceipt, 'final', transaction.transactionId, receipt.receiptDigest, receipt.evidenceDigest, receipt.prHeadSha), changeReportFile: path.resolve(cwd, values.changeReportFile), primaryResult, strict: true,
247
+ cwd, client: options.client, agent, body: manualSummaryBody(finalBodyWithoutReceipt, 'final'), changeReportFile: path.resolve(cwd, values.changeReportFile), primaryResult, strict: true,
253
248
  manualValidation: { phase: 'final', transactionId: transaction.transactionId, receiptDigest: receipt.receiptDigest, prHeadSha: receipt.prHeadSha, authority: 'coordinator' }, lockAlreadyHeld: true
254
249
  });
255
250
  if (!['applied', 'no-op'].includes(promoted.status))
@@ -1,10 +1,9 @@
1
- import { recoverSandboxControl, requestSandboxControl, requestSandboxTaskFinalization, requestSandboxTaskWorkflow, resolveVisibleActiveShortId, SandboxControlClientError } from '../sandbox/control/client.js';
1
+ import { recoverSandboxControl, requestSandboxControl, requestSandboxTaskFinalization, SandboxControlClientError } from '../sandbox/control/client.js';
2
2
  import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
3
3
  import { serveSandboxControl } from '../sandbox/control/server.js';
4
4
  import { runSandboxControlExecutor } from '../sandbox/control/executor.js';
5
5
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
6
6
  import { parseTaskCreateResult, taskCreateExitCode } from '../task/create-service.js';
7
- import { createTaskWorkflowRequest } from '../sandbox/control/task-workflow.js';
8
7
  function writeFinalizationEnvelope(status, accepted, error, changed = false, result = null) {
9
8
  process.stdout.write(`${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`);
10
9
  }
@@ -132,47 +131,6 @@ async function sandboxControl(args) {
132
131
  sandboxFinalizationClient(commandArgs);
133
132
  return;
134
133
  }
135
- if (family === 'task-workflow') {
136
- const command = commandArgs[0];
137
- const taskId = process.env.AGENT_INFRA_TASK_ID;
138
- const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
139
- if (!command || !taskId || !generation) {
140
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'SANDBOX_CONTROL_REQUEST_INVALID', message: 'task-workflow requires a bound task and generation' } })}\n`);
141
- process.exitCode = 1;
142
- return;
143
- }
144
- let workflow;
145
- try {
146
- const args = commandArgs.slice(1);
147
- if (/^\d+$/.test(args[0] ?? '')) {
148
- const resolved = resolveVisibleActiveShortId(args[0]);
149
- if (resolved !== taskId)
150
- throw new Error('SANDBOX_TASK_REF_MISMATCH');
151
- args[0] = resolved;
152
- }
153
- workflow = createTaskWorkflowRequest(command, args, taskId, generation);
154
- }
155
- catch (error) {
156
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'SANDBOX_CONTROL_REQUEST_INVALID', message: error instanceof Error ? error.message : String(error) } })}\n`);
157
- process.exitCode = 1;
158
- return;
159
- }
160
- let response;
161
- try {
162
- response = requestSandboxTaskWorkflow({ workflow });
163
- }
164
- catch (error) {
165
- if (!(error instanceof SandboxControlClientError))
166
- throw error;
167
- writeClientError(error);
168
- process.exitCode = error.detail.retryable ? 75 : 1;
169
- return;
170
- }
171
- process.stdout.write(response.stdout);
172
- process.stderr.write(response.stderr);
173
- process.exitCode = response.phase === 'rejected' ? response.error?.retryable ? 75 : 1 : response.exitCode ?? 1;
174
- return;
175
- }
176
134
  let response;
177
135
  try {
178
136
  response = requestSandboxControl({ family, args: commandArgs });
@@ -1,6 +1,6 @@
1
1
  import { parseArtifactCommand, executeArtifactCommand } from '../task/artifact-command.js';
2
2
  import { ensureInternalHandlerRoute } from './cli-route-inventory.js';
3
- const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, preflight, or finalize a workflow artifact without changing task state.\n`;
3
+ const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family> [--source-finding <id> --source-artifact <name> --source-sha256 <digest>]\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, preflight, or finalize workflow artifact facts.\n`;
4
4
  function taskArtifact(args = []) {
5
5
  if (!ensureInternalHandlerRoute('task-artifact', args))
6
6
  return;
@@ -1,6 +1,9 @@
1
1
  import { createDirectHostExecutionContext, dispatchTaskControlOperation, parseTaskControlOperation } from '../task/control-authority.js';
2
- import { applyTaskFinalization } from '../task/finalization.js';
2
+ import { applyTaskFinalization, prepareTaskFinalization } from '../task/finalization.js';
3
3
  import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
4
+ import { verifyTaskEvent } from '../task/verification.js';
5
+ import { resolveSandboxControlTransport } from './task-operation-registry.js';
6
+ import { requestSandboxTaskFinalization, SandboxControlClientError } from '../sandbox/control/client.js';
4
7
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
5
8
  const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
6
9
  function envelope(status, changed, accepted, result, error) {
@@ -59,7 +62,44 @@ async function taskFinalization(args = []) {
59
62
  ...operation,
60
63
  request: { ...operation.request, taskRef: resolved.taskId }
61
64
  };
65
+ const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
66
+ if (transport.kind === 'sandbox-local') {
67
+ const prepared = await prepareTaskFinalization(boundOperation.request, {
68
+ repoRoot,
69
+ preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
70
+ });
71
+ if (prepared.status !== 'prepared') {
72
+ process.stdout.write(envelope(prepared.status, prepared.changed, true, prepared, prepared.error));
73
+ process.exitCode = exitCode(prepared.status);
74
+ return;
75
+ }
76
+ try {
77
+ const response = requestSandboxTaskFinalization({ agent: boundOperation.request.agent });
78
+ process.stdout.write(response.stdout);
79
+ process.stderr.write(response.stderr);
80
+ process.exitCode = response.exitCode ?? 1;
81
+ return;
82
+ }
83
+ catch (error) {
84
+ const detail = error instanceof SandboxControlClientError
85
+ ? error.detail
86
+ : { code: 'SANDBOX_CONTROL_CLIENT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
87
+ process.stdout.write(envelope(detail.retryable ? 'blocked' : 'failed', false, false, null, detail));
88
+ process.exitCode = detail.retryable ? 2 : 1;
89
+ return;
90
+ }
91
+ }
62
92
  const result = await dispatchTaskControlOperation(createDirectHostExecutionContext({ repoRoot }), boundOperation);
93
+ if (result.status === 'prepared') {
94
+ const detail = {
95
+ code: 'TASK_FINALIZATION_DIRECT_COMMIT_REQUIRED',
96
+ message: 'direct finalization prepared external work without committing the lifecycle transition',
97
+ retryable: false
98
+ };
99
+ process.stdout.write(envelope('failed', result.changed, true, result, detail));
100
+ process.exitCode = 1;
101
+ return;
102
+ }
63
103
  process.stdout.write(envelope(result.status, result.changed, true, result, result.error));
64
104
  process.exitCode = exitCode(result.status);
65
105
  }
@@ -5,17 +5,18 @@ import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, isReviewSt
5
5
  import { resolveTaskRef } from '../task/resolve-ref.js';
6
6
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
7
7
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
8
- const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, rework-intent-upsert, stage-status\n`;
8
+ const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, rework-intent-upsert, rework-intent-rebuild, stage-status\n`;
9
9
  const FLAGS = {
10
10
  '--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal',
11
11
  '--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
12
12
  '--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation',
13
13
  '--intent-id': 'intentId', '--finding-id': 'findingId', '--source-artifact': 'sourceArtifact',
14
- '--source-sha256': 'sourceSha256', '--target': 'target',
14
+ '--source-sha256': 'sourceSha256', '--classification': 'classification',
15
15
  '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
16
16
  };
17
17
  const NUMERIC = new Set(['ordinal', 'round']);
18
18
  const BOOLEAN = new Set(['needsImplementation']);
19
+ const REWORK_CLASSIFICATIONS = new Set(['scope-or-requirement', 'design', 'implementation', 'human-decision', 'insufficient-evidence']);
19
20
  function usageFailure(message) {
20
21
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LEDGER_PAYLOAD_INVALID', message } })}\n`);
21
22
  process.stderr.write(USAGE);
@@ -40,6 +41,7 @@ async function taskLedger(args = []) {
40
41
  internalHandlerRoute('task-ledger', 'decision-next-id', kind),
41
42
  internalHandlerRoute('task-ledger', 'decision-upsert', kind),
42
43
  internalHandlerRoute('task-ledger', 'rework-intent-upsert', kind),
44
+ internalHandlerRoute('task-ledger', 'rework-intent-rebuild', kind),
43
45
  internalHandlerRoute('task-ledger', 'stage-status', kind)
44
46
  ].some(Boolean)) {
45
47
  usageFailure(`unknown ledger intent '${kind}'`);
@@ -85,14 +87,16 @@ async function taskLedger(args = []) {
85
87
  'finding-review': ['id', 'status', 'evidence'],
86
88
  'decision-next-id': [],
87
89
  'decision-upsert': ['id', 'stage', 'artifact'],
88
- 'rework-intent-upsert': ['intentId', 'findingId', 'sourceArtifact', 'sourceSha256', 'target'],
90
+ 'rework-intent-upsert': ['intentId', 'findingId', 'sourceArtifact', 'sourceSha256', 'classification'],
91
+ 'rework-intent-rebuild': [],
89
92
  'stage-status': ['stage']
90
93
  };
91
94
  const optional = {
92
95
  'finding-review': ['needsImplementation'],
93
- 'decision-upsert': ['needsImplementation']
96
+ 'decision-upsert': ['needsImplementation'],
97
+ 'rework-intent-rebuild': ['findingId', 'sourceArtifact', 'sourceSha256', 'classification']
94
98
  };
95
- const overrideFields = kind === 'stage-status' || kind === 'decision-next-id' || kind === 'rework-intent-upsert'
99
+ const overrideFields = kind === 'stage-status' || kind === 'decision-next-id' || kind === 'rework-intent-upsert' || kind === 'rework-intent-rebuild'
96
100
  ? []
97
101
  : ['overrideTicket', 'overrideTarget', 'overrideScope'];
98
102
  const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind], ...(optional[kind] ?? []), ...overrideFields]);
@@ -111,6 +115,10 @@ async function taskLedger(args = []) {
111
115
  usageFailure(dryRunConflict.message);
112
116
  return;
113
117
  }
118
+ if (values.classification !== undefined && !REWORK_CLASSIFICATIONS.has(String(values.classification))) {
119
+ usageFailure(`invalid rework classification '${values.classification}'`);
120
+ return;
121
+ }
114
122
  if (kind === 'stage-status') {
115
123
  if (!isReviewStage(String(values.stage))) {
116
124
  usageFailure(`stage-status requires a valid review stage`);
@@ -2,7 +2,7 @@ import { createDirectHostExecutionContext, dispatchTaskControlOperation, parseTa
2
2
  import { lifecycleIntentCatalog } from '../task/lifecycle.js';
3
3
  import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
4
4
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
5
- const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --stage <stage> --round <round> --artifact <artifact> --reason <reason>\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
5
+ const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --auto\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
6
6
  function usageFailure(message) {
7
7
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
8
8
  process.stderr.write(USAGE);
@@ -85,6 +85,7 @@ function internalTaskRoutes() {
85
85
  descriptor('internal', 'task-ledger', 'finding-review', 'task-bound', 'progress'),
86
86
  descriptor('internal', 'task-ledger', 'decision-upsert', 'task-bound', 'progress'),
87
87
  descriptor('internal', 'task-ledger', 'rework-intent-upsert', 'task-bound', 'progress'),
88
+ descriptor('internal', 'task-ledger', 'rework-intent-rebuild', 'task-bound', 'progress'),
88
89
  descriptor('internal', 'task-warning', 'list', 'task-bound', 'diagnostic'),
89
90
  descriptor('internal', 'task-warning', 'add', 'task-bound', 'progress'),
90
91
  descriptor('internal', 'task-warning', 'set-status', 'task-bound', 'progress'),
@@ -229,6 +230,8 @@ export function resolveDelegatedTaskOperation(args) {
229
230
  const [operation, ...rest] = args;
230
231
  if (operation !== 'client' || !rest[0])
231
232
  return null;
233
+ if (rest[0] === 'task-orchestration')
234
+ return null;
232
235
  return resolveTaskOperation('internal', rest[0], rest.slice(1));
233
236
  }
234
237
  const TASK_MARKER_KEYS = [
@@ -352,14 +355,16 @@ export function resolveSandboxControlTransport(env = process.env, options = {})
352
355
  if (!hasCompleteConfig) {
353
356
  return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
354
357
  }
355
- return { kind: 'broker-client', reasonCode: null };
358
+ return { kind: options.localWorkflow ? 'sandbox-local' : 'broker-client', reasonCode: null };
356
359
  }
357
360
  if (!hasAnyMarker)
358
361
  return { kind: 'direct-host', reasonCode: null };
359
362
  if (!hasCompleteConfig || (taskBound && !runtime) || (!taskBound && runtime)) {
360
363
  return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
361
364
  }
362
- return { kind: 'broker-client', reasonCode: null };
365
+ return options.localWorkflow
366
+ ? { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_MISSING' }
367
+ : { kind: 'broker-client', reasonCode: null };
363
368
  }
364
369
  export function hasTaskBoundMarker(env = process.env) {
365
370
  return taskMarkerState(env) === 'task-bound';
@@ -13,21 +13,70 @@ import { taskIssueIdentity } from './task-identities.js';
13
13
  function result(base, artifacts, warnings) {
14
14
  return { ...base, artifacts, warnings };
15
15
  }
16
- function excludedPrReview(taskDir, message) {
17
- const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])(pr-review(?:-r[1-9]\d*)?\.md)(?=$|[^A-Za-z0-9_.-])/g)]
16
+ function referencedBackfillArtifact(taskDir, message) {
17
+ const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])([a-z][a-z-]*(?:-r[1-9]\d*)?\.md)(?=$|[^A-Za-z0-9_.-])/g)]
18
18
  .map((match) => match[1]);
19
19
  if (names.length !== 1)
20
- return null;
20
+ return { status: 'ignored', artifact: null, error: null };
21
21
  const parsed = parseArtifactName(names[0]);
22
- if (!parsed || parsed.family !== 'pr-review')
23
- return null;
22
+ if (!parsed || (parsed.family !== 'pr-review' && !COMPLETION_BACKFILL_FAMILIES.includes(parsed.family))) {
23
+ return { status: 'ignored', artifact: null, error: null };
24
+ }
24
25
  const artifactPath = path.join(taskDir, parsed.name);
25
26
  try {
26
27
  const stat = fs.lstatSync(artifactPath);
27
- return stat.isFile() && !stat.isSymbolicLink() ? parsed.name : null;
28
+ if (stat.isSymbolicLink())
29
+ return {
30
+ status: 'failed', artifact: null,
31
+ error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `SYMBOLIC_LINK: symbolic links are not workflow artifacts: ${parsed.name}`, retryable: false }
32
+ };
33
+ if (!stat.isFile())
34
+ return {
35
+ status: 'failed', artifact: null,
36
+ error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `NON_REGULAR_FILE: artifact is not a regular file: ${parsed.name}`, retryable: false }
37
+ };
38
+ fs.accessSync(artifactPath, fs.constants.R_OK);
39
+ return { status: 'ready', artifact: parsed.name, error: null };
40
+ }
41
+ catch (error) {
42
+ return {
43
+ status: 'failed', artifact: null,
44
+ error: { code: 'ARTIFACT_READ_FAILED', message: `Unable to inspect referenced artifact '${parsed.name}': ${error instanceof Error ? error.message : String(error)}`, retryable: false }
45
+ };
46
+ }
47
+ }
48
+ function matchingBackfillWarnings(taskDir, content) {
49
+ const matches = [];
50
+ for (const warning of getOpenWorkflowWarnings(content)) {
51
+ if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact')
52
+ continue;
53
+ const reference = referencedBackfillArtifact(taskDir, warning.message);
54
+ if (reference.status === 'failed')
55
+ return { status: 'failed', matches: [], error: reference.error };
56
+ if (reference.status === 'ready')
57
+ matches.push({ warning, artifact: reference.artifact });
58
+ }
59
+ return { status: 'ready', matches, error: null };
60
+ }
61
+ function inspectCompletionBackfillEligibility(taskRef, options = {}) {
62
+ const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
63
+ if (!resolved.ok)
64
+ return {
65
+ status: 'failed', eligible: false,
66
+ error: { code: resolved.code, message: resolved.message, retryable: false }
67
+ };
68
+ try {
69
+ const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
70
+ const matching = matchingBackfillWarnings(resolved.taskDir, content);
71
+ if (matching.status === 'failed')
72
+ return { status: 'failed', eligible: false, error: matching.error };
73
+ return { status: 'ready', eligible: matching.matches.length > 0, error: null };
28
74
  }
29
- catch {
30
- return null;
75
+ catch (error) {
76
+ return {
77
+ status: 'failed', eligible: false,
78
+ error: { code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }
79
+ };
31
80
  }
32
81
  }
33
82
  async function backfillCompletionComments(taskRef, options) {
@@ -46,6 +95,9 @@ async function backfillCompletionComments(taskRef, options) {
46
95
  }
47
96
  }), [], []);
48
97
  const initialContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
98
+ const matching = matchingBackfillWarnings(resolved.taskDir, initialContent);
99
+ if (matching.status === 'failed')
100
+ return result(platformResult('failed', { error: matching.error }), [], []);
49
101
  const issueIdentity = taskIssueIdentity(parseTaskFrontmatter(initialContent));
50
102
  if (!issueIdentity)
51
103
  return result(platformResult('no-op', {
@@ -79,18 +131,12 @@ async function backfillCompletionComments(taskRef, options) {
79
131
  error: latest.error
80
132
  }), artifacts, []);
81
133
  }
82
- const matching = getOpenWorkflowWarnings(initialContent).flatMap((warning) => {
83
- if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact')
84
- return [];
85
- const excluded = excludedPrReview(resolved.taskDir, warning.message);
86
- return excluded ? [{ warning, excluded }] : [];
87
- });
88
134
  const warnings = [];
89
135
  let metadata = null;
90
- for (const { warning, excluded } of matching) {
136
+ for (const { warning, artifact } of matching.matches) {
91
137
  metadata ??= captureTaskWriteMetadata();
92
138
  const resolution = [
93
- `excluded=${excluded}`,
139
+ `artifact=${artifact}`,
94
140
  `completionFamilies=${COMPLETION_BACKFILL_FAMILIES.join(',')}`,
95
141
  `synchronized=${inventory.artifacts.map((artifact) => artifact.name).join(',') || 'none'}`,
96
142
  `completedAt=${metadata.timestamp}`
@@ -119,5 +165,5 @@ async function backfillCompletionComments(taskRef, options) {
119
165
  error: null
120
166
  }), artifacts, warnings);
121
167
  }
122
- export { backfillCompletionComments };
168
+ export { backfillCompletionComments, inspectCompletionBackfillEligibility };
123
169
  //# sourceMappingURL=completion-backfill.js.map
@@ -10,7 +10,7 @@ import { resourceIdentityNumber } from './resource-identity.js';
10
10
  import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.js';
11
11
  import { recordForArtifact, recordForCancel, recordForSummary, recordForTask, renderActivityRecoveryMetadata } from './activity-recovery.js';
12
12
  import { projectTaskComment } from './task-comment-projection.js';
13
- import { canonicalizeCommentBody, canonicalizeSummaryBody, escapeHtmlText, fenceRanges, renderSafeCodeFence } from './comment-safety.js';
13
+ import { canonicalizeCommentBody, escapeHtmlText, fenceRanges, renderSafeCodeFence } from './comment-safety.js';
14
14
  function providerCommentId(id, provider) {
15
15
  return provider.identity?.comment === 'number' && /^\d+$/.test(id) ? Number(id) : id;
16
16
  }
@@ -29,8 +29,6 @@ const ARTIFACT_TITLES = {
29
29
  'review-plan': '技术方案审查',
30
30
  code: '实现报告',
31
31
  'review-code': '代码审查',
32
- 'manual-validation': '人工验证报告',
33
- 'validation-run': '验证运行证据',
34
32
  'pr-review': 'PR 审查报告'
35
33
  };
36
34
  function normalizeCommentContent(content) {
@@ -100,7 +98,7 @@ function taskCommentLanguage(repoRoot) {
100
98
  }
101
99
  function artifactIdentity(artifact) {
102
100
  const stem = path.basename(artifact, '.md');
103
- const match = stem.match(/^(analysis|review-analysis|plan|review-plan|code|review-code|manual-validation|validation-run|pr-review)(?:-r(\d+))?$/);
101
+ const match = stem.match(/^(analysis|review-analysis|plan|review-plan|code|review-code|pr-review)(?:-r(\d+))?$/);
104
102
  if (!match)
105
103
  throw new Error(`unsupported artifact '${artifact}'`);
106
104
  const base = ARTIFACT_TITLES[match[1]];
@@ -390,14 +388,6 @@ function summaryPosition(comments, taskId) {
390
388
  const sequence = summary[0].createdSequence;
391
389
  return { ok: managed.every((comment) => sequence >= comment.createdSequence), known: true, summary: summary[0] };
392
390
  }
393
- function summaryDigest(comment) {
394
- const body = normalizeCommentContent(comment.body)
395
- .replace(/^<!-- sync-issue:[^\n]+:summary -->\n## [^\n]+\n\n> [^\n]+\n\n/u, '')
396
- .replace(/^<details><summary>恢复元数据<\/summary>[\s\S]*?<\/details>\n\n/u, '')
397
- .replace(/\n---\n\*[^\n]*\*$/u, '');
398
- const canonical = canonicalizeSummaryBody(body);
399
- return canonical.ok ? createHash('sha256').update(canonical.value).digest('hex') : '';
400
- }
401
391
  async function listedComments(provider, loaded, parent) {
402
392
  return provider.comments?.list
403
393
  ? provider.comments.list({ context: providerOperationContext(loaded), parent }).then((response) => response.ok
@@ -510,25 +500,6 @@ async function syncPlatformComment(taskRef, options) {
510
500
  });
511
501
  }
512
502
  }
513
- if (options.verifyOnly) {
514
- if (options.kind !== 'summary' || !options.summaryAuthorization) {
515
- return platformResult('failed', {
516
- ...contextFields(context), resource: { kind: 'issue', number: issue },
517
- error: { code: 'SUMMARY_VERIFICATION_INVALID', message: 'summary verification requires durable authorization', retryable: false }
518
- });
519
- }
520
- const position = summaryPosition(listed.value, resolved.taskId);
521
- if (!position.ok || !position.summary || summaryDigest(position.summary) !== options.summaryAuthorization.sha256) {
522
- return platformResult('failed', {
523
- ...contextFields(context), resource: { kind: 'issue', number: issue },
524
- error: { code: 'SUMMARY_VERIFICATION_FAILED', message: 'summary marker, body digest, or managed comment order is invalid', retryable: true }
525
- });
526
- }
527
- return platformResult('no-op', {
528
- ...contextFields(context), changed: false, resource: { kind: 'issue', number: issue },
529
- comment: { kind: options.kind, marker: desired[0].marker, ids: [position.summary.id], parts: 1 }, error: null
530
- });
531
- }
532
503
  // Backfill only supplies missing artifact comments; valid existing marker sets stay untouched.
533
504
  if (options.kind === 'artifact' && options.backfill && existing.length > 0) {
534
505
  const operations = existing.map((comment) => ({
@@ -13,7 +13,7 @@ import { platformResult } from './types.js';
13
13
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.js';
14
14
  import { resourceIdentityEquals } from './resource-identity.js';
15
15
  import { buildPrChangeReport, CANONICAL_REPORT_PLACEHOLDER, readPrChangeReport, replaceCanonicalReportPlaceholder, runMechanicalChangeReport, taskIntentDigest, validateMechanicalReport, validatePrecheckCandidate, writePrChangeReportAtomic } from './pr-change-report.js';
16
- import { manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.js';
16
+ import { MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.js';
17
17
  import { readManualValidationCompletion } from '../task/manual-validation-completion.js';
18
18
  function summaryMarker(taskId) {
19
19
  return `<!-- sync-pr:${taskId}:summary -->`;
@@ -515,6 +515,7 @@ async function syncPullRequestSummary(taskRef, options) {
515
515
  if (!initial.ok)
516
516
  return fail(initial.status, context, initial.error);
517
517
  const manual = options.manualValidation;
518
+ let manualEvidenceDigest = null;
518
519
  const hasFinalManualValidation = /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(options.body);
519
520
  if (hasFinalManualValidation && (!manual || manual.phase !== 'final'))
520
521
  return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires the transaction coordinator', retryable: false }, prNumber);
@@ -538,6 +539,7 @@ async function syncPullRequestSummary(taskRef, options) {
538
539
  if (!completion.ok)
539
540
  return fail('failed', context, platformError(completion.error), prNumber);
540
541
  const { receipt, transaction } = completion.value;
542
+ manualEvidenceDigest = receipt.evidenceDigest;
541
543
  if (transaction.phase !== 'final-promotion-in-progress' || !transaction.eventAppended)
542
544
  return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'final summary requires a receipt-backed promotion intent', retryable: false }, prNumber);
543
545
  if (!manualValidationFinalSummaryProjectionMatches(options.body, receipt))
@@ -581,7 +583,10 @@ async function syncPullRequestSummary(taskRef, options) {
581
583
  const replaced = replaceCanonicalReportPlaceholder(`${prefix.value}${placeholder}${suffix.value}`, report.value);
582
584
  if (!replaced.ok)
583
585
  return fail('failed', context, platformError(replaced.error));
584
- const desired = buildPullRequestSummary(resolved.taskId, replaced.value, initial.value.head.sha, renderHumanOverrideAudit(taskContent));
586
+ const renderedBody = manual?.phase === 'final'
587
+ ? replaced.value.replace(MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, `<!-- manual-validation-receipt: transaction=${manual.transactionId}; receipt=${manual.receiptDigest}; evidence=${manualEvidenceDigest}; head=${manual.prHeadSha} -->`)
588
+ : replaced.value;
589
+ const desired = buildPullRequestSummary(resolved.taskId, renderedBody, initial.value.head.sha, renderHumanOverrideAudit(taskContent));
585
590
  if (!isSafeSummaryEnvelope(desired, resolved.taskId))
586
591
  return fail('failed', context, {
587
592
  code: 'PR_SUMMARY_RENDER_INVALID',