@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.
- package/README.md +17 -8
- package/README.zh-CN.md +17 -8
- package/dist/bin/internal-cli.js +7 -2
- package/dist/lib/agent-clients/adapter.js +3 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
- package/dist/lib/agent-clients/adapters/codex.js +10 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +22 -27
- package/dist/lib/internal/sandbox-control.js +1 -43
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +41 -1
- package/dist/lib/internal/task-ledger.js +13 -5
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +7 -2
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/platform/issue-comments.js +2 -31
- package/dist/lib/platform/pr-summary.js +7 -2
- package/dist/lib/sandbox/control/client.js +3 -44
- package/dist/lib/sandbox/control/executor.js +14 -79
- package/dist/lib/sandbox/control/protocol.js +5 -64
- package/dist/lib/sandbox/control/server.js +49 -343
- package/dist/lib/sandbox/control/state.js +1 -7
- package/dist/lib/sandbox/control/task-view.js +1 -2
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- package/dist/lib/task/artifact-command.js +11 -4
- package/dist/lib/task/artifact-lifecycle.js +217 -85
- package/dist/lib/task/artifact-operations.js +1 -26
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +1 -0
- package/dist/lib/task/artifact-selection.js +25 -0
- package/dist/lib/task/capabilities.js +158 -39
- package/dist/lib/task/commands/status.js +9 -2
- package/dist/lib/task/control-authority.js +37 -38
- package/dist/lib/task/control-recovery.js +3 -42
- package/dist/lib/task/decision-intents.js +12 -0
- package/dist/lib/task/delegation-receipts.js +2 -2
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +80 -64
- package/dist/lib/task/finalization-artifacts.js +1 -3
- package/dist/lib/task/finalization-state.js +1 -1
- package/dist/lib/task/finalization.js +319 -234
- package/dist/lib/task/ledger-intents.js +157 -15
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +11 -1
- package/dist/lib/task/local-artifact-finalization.js +1 -20
- package/dist/lib/task/manual-validation-receipt.js +8 -3
- package/dist/lib/task/orchestration.js +29 -13
- package/dist/lib/task/review-finalization.js +3 -16
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/verification-engine.js +20 -30
- package/dist/lib/task/verification.js +1 -0
- package/dist/lib/task/workflow-warning-intents.js +21 -6
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +46 -0
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
- package/lib/agent-clients/adapters/codex.ts +10 -0
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +22 -26
- package/lib/internal/sandbox-control.ts +0 -42
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +40 -1
- package/lib/internal/task-ledger.ts +12 -5
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +8 -4
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/platform/issue-comments.ts +1 -35
- package/lib/platform/pr-summary.ts +10 -2
- package/lib/sandbox/control/client.ts +5 -68
- package/lib/sandbox/control/executor.ts +14 -81
- package/lib/sandbox/control/protocol.ts +7 -83
- package/lib/sandbox/control/server.ts +47 -362
- package/lib/sandbox/control/state.ts +1 -9
- package/lib/sandbox/control/task-view.ts +1 -2
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +1 -26
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +1 -0
- package/lib/task/artifact-selection.ts +53 -0
- package/lib/task/capabilities.ts +154 -40
- package/lib/task/commands/status.ts +11 -2
- package/lib/task/control-authority.ts +41 -48
- package/lib/task/control-recovery.ts +3 -43
- package/lib/task/decision-intents.ts +9 -0
- package/lib/task/delegation-receipts.ts +2 -2
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +82 -64
- package/lib/task/finalization-artifacts.ts +1 -3
- package/lib/task/finalization-state.ts +1 -1
- package/lib/task/finalization.ts +310 -216
- package/lib/task/ledger-intents.ts +155 -15
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +15 -2
- package/lib/task/local-artifact-finalization.ts +2 -25
- package/lib/task/manual-validation-receipt.ts +8 -2
- package/lib/task/orchestration.ts +56 -27
- package/lib/task/review-finalization.ts +3 -16
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/verification-engine.ts +19 -30
- package/lib/task/verification.ts +2 -1
- package/lib/task/workflow-warning-intents.ts +18 -6
- package/lib/task/workspace-snapshot.ts +3 -2
- package/package.json +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +19 -5
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +19 -5
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -8
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -8
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/feature-development.en.yaml +11 -6
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/refactoring.en.yaml +11 -6
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
- package/dist/lib/sandbox/control/finalization-response.js +0 -35
- package/dist/lib/sandbox/control/task-workflow.js +0 -74
- package/dist/lib/sandbox/control/workflow-executor.js +0 -142
- package/dist/lib/task/lifecycle-recovery.js +0 -549
- package/dist/lib/task/recovery-warning.js +0 -19
- package/dist/lib/task/workflow-command.js +0 -21
- package/dist/lib/task/workflow-dispatch.js +0 -213
- package/lib/sandbox/control/finalization-response.ts +0 -37
- package/lib/sandbox/control/task-workflow.ts +0 -95
- package/lib/sandbox/control/workflow-executor.ts +0 -156
- package/lib/task/lifecycle-recovery.ts +0 -691
- package/lib/task/recovery-warning.ts +0 -21
- package/lib/task/workflow-command.ts +0 -22
- 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
|
|
26
|
-
|
|
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
|
|
30
|
-
? chinese
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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
|
|
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'
|
|
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'
|
|
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,
|
|
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
|
|
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', '--
|
|
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', '
|
|
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: --
|
|
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
|
|
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
|
|
17
|
-
const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])(
|
|
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
|
-
|
|
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
|
|
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,
|
|
136
|
+
for (const { warning, artifact } of matching.matches) {
|
|
91
137
|
metadata ??= captureTaskWriteMetadata();
|
|
92
138
|
const resolution = [
|
|
93
|
-
`
|
|
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,
|
|
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|
|
|
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
|
|
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',
|