@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
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { syncPlatformComment } from '../platform/issue-comments.js';
|
|
5
5
|
import { canonicalizeSummaryBody } from '../platform/comment-safety.js';
|
|
6
|
+
import { backfillCompletionComments, inspectCompletionBackfillEligibility } from '../platform/completion-backfill.js';
|
|
6
7
|
import { taskIssueIdentity } from '../platform/task-identities.js';
|
|
7
8
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
8
9
|
import { applyTaskLifecycle } from './lifecycle.js';
|
|
@@ -12,8 +13,8 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
|
|
|
12
13
|
import { verifyTaskEvent } from './verification.js';
|
|
13
14
|
import { projectFinalizationWarning } from './workflow-warning-intents.js';
|
|
14
15
|
import { mergeOperationWarnings } from './operation-outcome.js';
|
|
15
|
-
const RECEIPT_VERSION =
|
|
16
|
-
const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification', 'summary'
|
|
16
|
+
const RECEIPT_VERSION = 4;
|
|
17
|
+
const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification', 'summary'];
|
|
17
18
|
function finalizationRoot(repoRoot) {
|
|
18
19
|
return path.join(repoRoot, '.agents', 'workspace', '.task-finalization');
|
|
19
20
|
}
|
|
@@ -34,11 +35,11 @@ function failed(taskId, error, overrides = {}) {
|
|
|
34
35
|
status: error.retryable ? 'blocked' : 'failed',
|
|
35
36
|
changed: false,
|
|
36
37
|
taskId,
|
|
38
|
+
backfill: null,
|
|
37
39
|
lifecycle: null,
|
|
38
40
|
taskComment: null,
|
|
39
41
|
verification: null,
|
|
40
42
|
summary: null,
|
|
41
|
-
postSummaryVerification: null,
|
|
42
43
|
completedSteps: [],
|
|
43
44
|
pendingSteps: [...FINALIZATION_STEPS],
|
|
44
45
|
result: error.retryable ? 'blocked' : 'failed',
|
|
@@ -58,10 +59,8 @@ function emptyReceipt(taskId, controlBinding) {
|
|
|
58
59
|
taskComment: 'pending',
|
|
59
60
|
verification: 'pending',
|
|
60
61
|
summary: 'pending',
|
|
61
|
-
postSummaryVerification: 'pending',
|
|
62
62
|
warningProjection: 'done',
|
|
63
63
|
warnings: [],
|
|
64
|
-
summarySha256: null,
|
|
65
64
|
...(controlBinding ? { controlBinding } : {}),
|
|
66
65
|
updatedAt: now(),
|
|
67
66
|
lastError: null
|
|
@@ -97,10 +96,8 @@ function validateReceipt(value, taskId) {
|
|
|
97
96
|
|| !states.includes(String(receipt.taskComment))
|
|
98
97
|
|| !states.includes(String(receipt.verification))
|
|
99
98
|
|| !states.includes(String(receipt.summary))
|
|
100
|
-
|| !states.includes(String(receipt.postSummaryVerification))
|
|
101
99
|
|| !['pending', 'done'].includes(String(receipt.warningProjection))
|
|
102
100
|
|| !Array.isArray(receipt.warnings) || receipt.warnings.some((warning) => !validWarning(warning))
|
|
103
|
-
|| (receipt.summarySha256 !== null && (typeof receipt.summarySha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(receipt.summarySha256)))
|
|
104
101
|
|| typeof receipt.updatedAt !== 'string'
|
|
105
102
|
|| (receipt.lastError !== null && (typeof receipt.lastError !== 'object' || Array.isArray(receipt.lastError)))
|
|
106
103
|
|| (receipt.controlBinding !== undefined && (!controlBinding || typeof controlBinding !== 'object'
|
|
@@ -154,8 +151,6 @@ function pendingSteps(receipt) {
|
|
|
154
151
|
function stepState(receipt, step) {
|
|
155
152
|
if (step === 'task-comment')
|
|
156
153
|
return receipt.taskComment;
|
|
157
|
-
if (step === 'post-summary-verification')
|
|
158
|
-
return receipt.postSummaryVerification;
|
|
159
154
|
return receipt[step];
|
|
160
155
|
}
|
|
161
156
|
function lifecycleStep(result) {
|
|
@@ -226,6 +221,77 @@ function resolveStepWarnings(receipt, step) {
|
|
|
226
221
|
? { ...warning, status: 'resolved', resolvedAt: now() }
|
|
227
222
|
: warning);
|
|
228
223
|
}
|
|
224
|
+
function verificationWarnings(result) {
|
|
225
|
+
const warnings = [];
|
|
226
|
+
for (const invocation of result.invocations) {
|
|
227
|
+
const checks = Array.isArray(invocation.payload.checks) ? invocation.payload.checks : [invocation.payload];
|
|
228
|
+
for (const value of checks) {
|
|
229
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
230
|
+
continue;
|
|
231
|
+
const check = value;
|
|
232
|
+
const status = check.effectiveStatus ?? check.status;
|
|
233
|
+
const target = typeof check.checkId === 'string' && check.checkId ? check.checkId : typeof check.type === 'string' ? check.type : null;
|
|
234
|
+
if (!target || status === 'pass')
|
|
235
|
+
continue;
|
|
236
|
+
const blocked = status === 'blocked';
|
|
237
|
+
const message = [check.reason, check.message, check.action]
|
|
238
|
+
.filter((item) => typeof item === 'string' && item.length > 0)
|
|
239
|
+
.join(' - ');
|
|
240
|
+
warnings.push({
|
|
241
|
+
step: 'verification', target, code: blocked ? 'CHECK_BLOCKED' : 'CHECK_FAILED',
|
|
242
|
+
retryable: blocked, severity: 'ACTION_REQUIRED',
|
|
243
|
+
message: message || `verification ${String(status)}`
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return mergeOperationWarnings(warnings);
|
|
248
|
+
}
|
|
249
|
+
function applyVerificationWarnings(receipt, result) {
|
|
250
|
+
if (result.status === 'pass')
|
|
251
|
+
return resolveStepWarnings(receipt, 'verification');
|
|
252
|
+
const observed = new Map();
|
|
253
|
+
for (const invocation of result.invocations) {
|
|
254
|
+
const checks = Array.isArray(invocation.payload.checks) ? invocation.payload.checks : [invocation.payload];
|
|
255
|
+
for (const value of checks) {
|
|
256
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
257
|
+
continue;
|
|
258
|
+
const check = value;
|
|
259
|
+
const target = typeof check.checkId === 'string' && check.checkId ? check.checkId : typeof check.type === 'string' ? check.type : null;
|
|
260
|
+
if (!target)
|
|
261
|
+
continue;
|
|
262
|
+
const status = check.effectiveStatus ?? check.status;
|
|
263
|
+
if (status === 'pass')
|
|
264
|
+
observed.set(target, 'pass');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (const warning of verificationWarnings(result))
|
|
268
|
+
observed.set(warning.target, warning);
|
|
269
|
+
if (observed.size === 0) {
|
|
270
|
+
return replaceWarning(receipt, warningFromError('verification', verificationFailure(result)), 'open');
|
|
271
|
+
}
|
|
272
|
+
let warnings = [...receipt.warnings];
|
|
273
|
+
for (const [target, current] of observed) {
|
|
274
|
+
if (current === 'pass') {
|
|
275
|
+
warnings = warnings.map((warning) => warning.step === 'verification' && warning.target === target && warning.status === 'open'
|
|
276
|
+
? { ...warning, status: 'resolved', resolvedAt: now() }
|
|
277
|
+
: warning);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
warnings = warnings.map((warning) => warning.step === 'verification' && warning.target === target
|
|
281
|
+
&& warning.status === 'open' && warning.code !== current.code
|
|
282
|
+
? { ...warning, status: 'resolved', resolvedAt: now() }
|
|
283
|
+
: warning);
|
|
284
|
+
const key = warningKey(current);
|
|
285
|
+
warnings = [...warnings.filter((warning) => warningKey(warning) !== key), warningRecord(current, 'open')];
|
|
286
|
+
}
|
|
287
|
+
return warnings;
|
|
288
|
+
}
|
|
289
|
+
function shouldRunBackfill(receipt) {
|
|
290
|
+
return receipt.lifecycle !== 'done'
|
|
291
|
+
|| receipt.taskComment === 'pending'
|
|
292
|
+
|| receipt.verification === 'pending'
|
|
293
|
+
|| receipt.warnings.some((warning) => warning.step === 'backfill' && warning.status === 'open');
|
|
294
|
+
}
|
|
229
295
|
function issueCapability(receipt, scope) {
|
|
230
296
|
return {
|
|
231
297
|
receiptId: receipt.receiptId,
|
|
@@ -241,6 +307,21 @@ function capabilityError(code, message) {
|
|
|
241
307
|
Object.assign(error, { code });
|
|
242
308
|
return error;
|
|
243
309
|
}
|
|
310
|
+
function hasPreparedTaskDocument(repoRoot, taskId) {
|
|
311
|
+
const resolved = resolveTaskRef(taskId, { repoRoot });
|
|
312
|
+
if (!resolved.ok)
|
|
313
|
+
return false;
|
|
314
|
+
if (resolved.state === 'completed')
|
|
315
|
+
return true;
|
|
316
|
+
if (resolved.state !== 'active')
|
|
317
|
+
return false;
|
|
318
|
+
try {
|
|
319
|
+
return parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8')).status === 'completed';
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
244
325
|
function mutationKeys(value) {
|
|
245
326
|
return Object.keys(value).sort();
|
|
246
327
|
}
|
|
@@ -310,9 +391,8 @@ function mutationPatch(current, mutation) {
|
|
|
310
391
|
: { warningProjection: 'pending', lastError: mutation.error };
|
|
311
392
|
}
|
|
312
393
|
function applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, consumed) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires a completed task');
|
|
394
|
+
if (!hasPreparedTaskDocument(repoRoot, receipt.taskId)) {
|
|
395
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active prepared task or a completed task');
|
|
316
396
|
}
|
|
317
397
|
const current = readReceipt(repoRoot, receipt.taskId);
|
|
318
398
|
if (!current || current.receiptId !== receipt.receiptId || current.revision !== receipt.revision) {
|
|
@@ -324,11 +404,10 @@ function applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability
|
|
|
324
404
|
return next;
|
|
325
405
|
}
|
|
326
406
|
function applyFinalizationReceiptMutation(repoRoot, receipt, capability, mutation) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires a completed task');
|
|
407
|
+
if (!hasPreparedTaskDocument(repoRoot, receipt.taskId)) {
|
|
408
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active prepared task or a completed task');
|
|
330
409
|
}
|
|
331
|
-
return withTaskExecutionLock(repoRoot,
|
|
410
|
+
return withTaskExecutionLock(repoRoot, receipt.taskId, 'task-finalization.receipt-mutation', () => applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, new Set()));
|
|
332
411
|
}
|
|
333
412
|
function warningKey(warning) {
|
|
334
413
|
return `${warning.step}\0${warning.code}\0${warning.target}`;
|
|
@@ -339,7 +418,8 @@ function reconcileWarningProjection(repoRoot, taskId, receipt, consumed) {
|
|
|
339
418
|
const warnings = [...new Map(receipt.warnings.map((warning) => [warningKey(warning), warning])).values()];
|
|
340
419
|
for (const warning of warnings) {
|
|
341
420
|
try {
|
|
342
|
-
const
|
|
421
|
+
const superseded = warning.status === 'resolved' && receipt.warnings.some((item) => item.status === 'open' && item.step === warning.step && item.target === warning.target && item.code !== warning.code);
|
|
422
|
+
const projected = projectFinalizationWarning(taskId, warning, { repoRoot, resolution: superseded ? 'superseded' : 'passed' });
|
|
343
423
|
if (projected.status === 'failed') {
|
|
344
424
|
const detail = {
|
|
345
425
|
code: projected.error?.code || 'FINALIZATION_WARNING_PROJECTION_FAILED',
|
|
@@ -436,16 +516,17 @@ function readDeliverySummary(taskDir, taskId) {
|
|
|
436
516
|
}
|
|
437
517
|
async function syncPendingSummary(input) {
|
|
438
518
|
let { receipt } = input;
|
|
439
|
-
if (receipt.summary !== 'pending') {
|
|
440
|
-
return { receipt, step: { status: receipt.summary === 'skipped' ? 'skipped' : 'no-op', changed: false, error: null }, changed: false, error: null };
|
|
441
|
-
}
|
|
442
519
|
try {
|
|
443
520
|
const resolved = resolveTaskRef(input.taskId, { repoRoot: input.repoRoot });
|
|
444
|
-
if (!resolved.ok ||
|
|
445
|
-
throw Object.assign(new Error('
|
|
521
|
+
if (!resolved.ok || !hasPreparedTaskDocument(input.repoRoot, input.taskId))
|
|
522
|
+
throw Object.assign(new Error('prepared task is unavailable for summary sync'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
523
|
+
if (!fs.existsSync(path.join(resolved.taskDir, '.delivery-summary.json'))) {
|
|
524
|
+
receipt = updateReceipt(input.repoRoot, receipt, { summary: 'skipped', lastError: null });
|
|
525
|
+
return { receipt, step: { status: 'skipped', changed: false, error: null }, changed: false, error: null };
|
|
526
|
+
}
|
|
446
527
|
if (!taskIssueIdentity(parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8')))) {
|
|
447
528
|
receipt = updateReceipt(input.repoRoot, receipt, {
|
|
448
|
-
summary: 'skipped',
|
|
529
|
+
summary: 'skipped', lastError: null
|
|
449
530
|
});
|
|
450
531
|
return { receipt, step: { status: 'skipped', changed: false, error: null }, changed: false, error: null };
|
|
451
532
|
}
|
|
@@ -460,8 +541,7 @@ async function syncPendingSummary(input) {
|
|
|
460
541
|
const resolvedWarning = receipt.warnings.some((warning) => warning.step === 'summary' && warning.status === 'open');
|
|
461
542
|
const warnings = resolveStepWarnings(receipt, 'summary');
|
|
462
543
|
receipt = updateReceipt(input.repoRoot, receipt, {
|
|
463
|
-
summary: skipped ? 'skipped' : 'done',
|
|
464
|
-
summarySha256: skipped ? null : summary.sha256,
|
|
544
|
+
summary: skipped ? 'skipped' : 'done',
|
|
465
545
|
taskComment: resolvedWarning ? 'pending' : receipt.taskComment,
|
|
466
546
|
warningProjection: warnings.some((warning) => warning.status === 'open') ? 'pending' : 'done',
|
|
467
547
|
warnings,
|
|
@@ -488,14 +568,9 @@ async function syncPendingSummary(input) {
|
|
|
488
568
|
return { receipt, step: { status: 'blocked', changed: false, error }, changed: false, error };
|
|
489
569
|
}
|
|
490
570
|
}
|
|
491
|
-
function clearDeliverySummary(taskDir) {
|
|
492
|
-
const file = path.join(taskDir, '.delivery-summary.json');
|
|
493
|
-
if (fs.existsSync(file))
|
|
494
|
-
fs.unlinkSync(file);
|
|
495
|
-
}
|
|
496
571
|
function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
497
572
|
const pending = pendingSteps(receipt);
|
|
498
|
-
const blocked = [steps.lifecycle, steps.taskComment, steps.verification, steps.summary
|
|
573
|
+
const blocked = [steps.backfill, steps.lifecycle, steps.taskComment, steps.verification, steps.summary].some((step) => step?.status === 'blocked');
|
|
499
574
|
const warnings = openWarnings(receipt);
|
|
500
575
|
const postLifecyclePending = receipt.lifecycle === 'done' && (pending.some((step) => step !== 'lifecycle') || receipt.warningProjection === 'pending');
|
|
501
576
|
const hardError = error?.code.startsWith('FINALIZATION_') || error?.code === 'TASK_FINALIZATION_RECEIPT_INVALID';
|
|
@@ -503,18 +578,18 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
503
578
|
status: hardError ? (error?.retryable ? 'blocked' : 'failed') : pending.length === 0 ? 'completed' : postLifecyclePending ? 'completed' : blocked ? 'blocked' : 'failed',
|
|
504
579
|
changed,
|
|
505
580
|
taskId,
|
|
581
|
+
backfill: steps.backfill ?? null,
|
|
506
582
|
lifecycle: steps.lifecycle ?? null,
|
|
507
583
|
taskComment: steps.taskComment ?? null,
|
|
508
584
|
verification: steps.verification ?? null,
|
|
509
585
|
summary: steps.summary ?? null,
|
|
510
|
-
postSummaryVerification: steps.postSummaryVerification ?? null,
|
|
511
586
|
completedSteps: completedSteps(receipt),
|
|
512
587
|
pendingSteps: pending,
|
|
513
588
|
result: hardError
|
|
514
589
|
? (error?.retryable ? 'blocked' : 'failed')
|
|
515
|
-
: postLifecyclePending
|
|
590
|
+
: postLifecyclePending
|
|
516
591
|
? 'completed_with_warnings'
|
|
517
|
-
: pending.length === 0
|
|
592
|
+
: pending.length === 0
|
|
518
593
|
? 'completed'
|
|
519
594
|
: blocked
|
|
520
595
|
? 'blocked'
|
|
@@ -523,9 +598,22 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
523
598
|
error: hardError ? error : postLifecyclePending ? null : error
|
|
524
599
|
};
|
|
525
600
|
}
|
|
526
|
-
|
|
601
|
+
function preparedResult(taskId, receipt, steps, changed) {
|
|
602
|
+
return {
|
|
603
|
+
status: 'prepared', changed, taskId,
|
|
604
|
+
backfill: steps.backfill ?? null,
|
|
605
|
+
lifecycle: steps.lifecycle ?? null,
|
|
606
|
+
taskComment: steps.taskComment ?? null,
|
|
607
|
+
verification: steps.verification ?? null,
|
|
608
|
+
summary: steps.summary ?? null,
|
|
609
|
+
completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt),
|
|
610
|
+
result: 'prepared', warnings: openWarnings(receipt), error: null
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
async function prepareUnderLock(request, taskId, options) {
|
|
527
614
|
const repoRoot = path.resolve(options.repoRoot);
|
|
528
615
|
const lifecycle = options.lifecycle ?? applyTaskLifecycle;
|
|
616
|
+
const backfill = options.backfill ?? backfillCompletionComments;
|
|
529
617
|
const commentSync = options.commentSync ?? syncPlatformComment;
|
|
530
618
|
const verify = options.verify ?? verifyTaskEvent;
|
|
531
619
|
const consumedCapabilities = new Set();
|
|
@@ -562,52 +650,89 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
562
650
|
return failed(taskId, errorOf(error, 'TASK_FINALIZATION_PREFLIGHT_FAILED', true));
|
|
563
651
|
}
|
|
564
652
|
}
|
|
565
|
-
let lifecycleResult = null;
|
|
566
653
|
let changed = false;
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
654
|
+
let backfillResult = null;
|
|
655
|
+
let runBackfill = shouldRunBackfill(receipt);
|
|
656
|
+
if (!runBackfill && receipt.lifecycle === 'done') {
|
|
657
|
+
const eligibility = inspectCompletionBackfillEligibility(taskId, { cwd: repoRoot });
|
|
658
|
+
if (eligibility.status === 'failed')
|
|
659
|
+
return failed(taskId, eligibility.error, {
|
|
660
|
+
completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
|
|
661
|
+
});
|
|
662
|
+
runBackfill = eligibility.eligible;
|
|
663
|
+
}
|
|
664
|
+
if (receipt.lifecycle === 'done' && runBackfill && receipt.summary === 'done') {
|
|
665
|
+
receipt = updateReceipt(repoRoot, receipt, { summary: 'pending' });
|
|
666
|
+
}
|
|
667
|
+
if (runBackfill)
|
|
668
|
+
try {
|
|
669
|
+
const result = await backfill(taskId, { agent: request.agent, cwd: repoRoot });
|
|
670
|
+
backfillResult = commentStep(result);
|
|
671
|
+
const issueNotLinked = result.status === 'no-op' && result.error?.code === 'ISSUE_NOT_LINKED';
|
|
672
|
+
if (!issueNotLinked && (result.status !== 'applied' && result.status !== 'no-op' || result.error)) {
|
|
673
|
+
const detail = backfillResult.error ?? {
|
|
674
|
+
code: 'COMPLETION_BACKFILL_FAILED', message: 'completion artifact backfill did not reach a successful terminal result', retryable: true
|
|
675
|
+
};
|
|
676
|
+
if (receipt.lifecycle !== 'done')
|
|
677
|
+
return failed(taskId, detail, {
|
|
678
|
+
backfill: backfillResult,
|
|
679
|
+
completedSteps: completedSteps(receipt),
|
|
680
|
+
pendingSteps: pendingSteps(receipt)
|
|
681
|
+
});
|
|
682
|
+
const warnings = replaceWarning(receipt, warningFromError('backfill', detail), 'open');
|
|
683
|
+
receipt = updateReceipt(repoRoot, receipt, { warningProjection: 'pending', warnings, lastError: detail });
|
|
684
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
685
|
+
if (!detail.retryable)
|
|
686
|
+
return failed(taskId, detail, {
|
|
687
|
+
changed, backfill: backfillResult, completedSteps: completedSteps(receipt),
|
|
688
|
+
pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
|
|
689
|
+
});
|
|
690
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult }, changed, detail);
|
|
691
|
+
}
|
|
692
|
+
changed = result.changed;
|
|
693
|
+
if (receipt.warnings.some((warning) => warning.step === 'backfill' && warning.status === 'open')) {
|
|
694
|
+
const warnings = receipt.warnings.map((warning) => warning.step === 'backfill' && warning.status === 'open'
|
|
695
|
+
? { ...warning, status: 'resolved', resolvedAt: now() }
|
|
696
|
+
: warning);
|
|
697
|
+
receipt = updateReceipt(repoRoot, receipt, { warningProjection: 'pending', warnings, lastError: null });
|
|
698
|
+
}
|
|
699
|
+
if (result.changed && receipt.taskComment !== 'pending') {
|
|
700
|
+
receipt = updateReceipt(repoRoot, receipt, { taskComment: 'pending' });
|
|
701
|
+
}
|
|
582
702
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
703
|
+
catch (error) {
|
|
704
|
+
const detail = errorOf(error, 'COMPLETION_BACKFILL_FAILED', true);
|
|
705
|
+
if (receipt.lifecycle === 'done') {
|
|
706
|
+
const warnings = replaceWarning(receipt, warningFromError('backfill', detail), 'open');
|
|
707
|
+
receipt = updateReceipt(repoRoot, receipt, { warningProjection: 'pending', warnings, lastError: detail });
|
|
708
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
709
|
+
return terminalResult(taskId, receipt, {
|
|
710
|
+
backfill: { status: 'blocked', changed: false, error: detail }
|
|
711
|
+
}, changed, detail);
|
|
712
|
+
}
|
|
713
|
+
return failed(taskId, detail, {
|
|
714
|
+
backfill: { status: detail.retryable ? 'blocked' : 'failed', changed: false, error: detail },
|
|
715
|
+
completedSteps: completedSteps(receipt),
|
|
716
|
+
pendingSteps: pendingSteps(receipt)
|
|
717
|
+
});
|
|
596
718
|
}
|
|
597
|
-
|
|
598
|
-
|
|
719
|
+
else
|
|
720
|
+
backfillResult = { status: 'no-op', changed: false, error: null };
|
|
721
|
+
let lifecycleResult = null;
|
|
722
|
+
try {
|
|
723
|
+
const resolved = resolveTaskRef(taskId, { repoRoot });
|
|
724
|
+
if (receipt.lifecycle === 'done' && resolved.ok && resolved.state === 'completed') {
|
|
599
725
|
lifecycleResult = { status: 'no-op', changed: false, error: null };
|
|
600
726
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
|
|
601
727
|
}
|
|
602
728
|
else {
|
|
603
|
-
const result = lifecycle({ taskRef: taskId, intent: 'complete', agent: request.agent }, { repoRoot, ...(options.metadataProvider ? { metadataProvider: options.metadataProvider } : {}) });
|
|
729
|
+
const result = lifecycle({ taskRef: taskId, intent: 'complete', agent: request.agent }, { repoRoot, prepareOnly: true, ...(options.metadataProvider ? { metadataProvider: options.metadataProvider } : {}) });
|
|
604
730
|
lifecycleResult = lifecycleStep(result);
|
|
605
731
|
if (result.status !== 'applied' && result.status !== 'no-op') {
|
|
606
732
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: lifecycleResult.error });
|
|
607
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment: null, verification: null }, result.changed, lifecycleResult.error);
|
|
733
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment: null, verification: null }, result.changed, lifecycleResult.error);
|
|
608
734
|
}
|
|
609
|
-
changed = result.changed;
|
|
610
|
-
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
|
|
735
|
+
changed = changed || result.changed;
|
|
611
736
|
}
|
|
612
737
|
}
|
|
613
738
|
catch (error) {
|
|
@@ -616,31 +741,30 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
616
741
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
|
|
617
742
|
}
|
|
618
743
|
catch { /* preserve the primary error */ }
|
|
619
|
-
return failed(taskId, detail, { lifecycle: lifecycleResult });
|
|
744
|
+
return failed(taskId, detail, { backfill: backfillResult, lifecycle: lifecycleResult });
|
|
620
745
|
}
|
|
621
746
|
let taskComment = null;
|
|
622
747
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
623
|
-
const initialComment = await syncPendingTaskComment({
|
|
624
|
-
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
625
|
-
});
|
|
626
|
-
receipt = initialComment.receipt;
|
|
627
|
-
taskComment = initialComment.step;
|
|
628
|
-
changed = changed || initialComment.changed;
|
|
629
|
-
if (initialComment.error) {
|
|
630
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: null }, changed, initialComment.error);
|
|
631
|
-
}
|
|
632
748
|
let verification = null;
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
749
|
+
if (receipt.verification !== 'pending') {
|
|
750
|
+
verification = { status: receipt.verification === 'skipped' ? 'skipped' : 'no-op', changed: false, error: null };
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
try {
|
|
754
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
755
|
+
const verificationState = resolveTaskRef(taskId, { repoRoot });
|
|
756
|
+
const verificationEvent = verificationState.ok && verificationState.state === 'completed'
|
|
757
|
+
? 'complete-task.completed'
|
|
758
|
+
: 'complete-task.prepared';
|
|
759
|
+
const result = await verify({ taskRef: taskId, event: verificationEvent }, { repoRoot });
|
|
760
|
+
verification = verificationStep(result);
|
|
761
|
+
if (result.status === 'pass') {
|
|
762
|
+
const resolvedTaskWarning = receipt.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
|
|
763
|
+
const warnings = applyVerificationWarnings(receipt, result);
|
|
764
|
+
receipt = updateReceipt(repoRoot, receipt, {
|
|
765
|
+
verification: 'done', taskComment: resolvedTaskWarning ? 'pending' : receipt.taskComment,
|
|
766
|
+
warningProjection: warnings.length > 0 ? 'pending' : 'done', warnings, lastError: null
|
|
767
|
+
});
|
|
644
768
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
645
769
|
const finalComment = await syncPendingTaskComment({
|
|
646
770
|
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
@@ -650,176 +774,101 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
650
774
|
taskComment = finalComment.step;
|
|
651
775
|
changed = changed || finalComment.changed;
|
|
652
776
|
if (finalComment.error) {
|
|
653
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
|
|
777
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
|
|
654
778
|
}
|
|
655
779
|
}
|
|
656
780
|
else {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
else {
|
|
661
|
-
const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
|
|
662
|
-
if (verificationPending) {
|
|
663
|
-
const capability = issueCapability(receipt, 'verification');
|
|
664
|
-
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
665
|
-
scope: 'verification', operation: 'failed', error: detail
|
|
666
|
-
}, consumedCapabilities);
|
|
667
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
668
|
-
}
|
|
669
|
-
else {
|
|
670
|
-
const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
|
|
781
|
+
const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
|
|
782
|
+
const warnings = applyVerificationWarnings(receipt, result);
|
|
671
783
|
receipt = updateReceipt(repoRoot, receipt, {
|
|
672
784
|
verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
|
|
673
785
|
});
|
|
674
786
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
675
|
-
const warningComment = await syncPendingTaskComment({
|
|
787
|
+
const warningComment = await syncPendingTaskComment({
|
|
788
|
+
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
789
|
+
});
|
|
676
790
|
receipt = warningComment.receipt;
|
|
677
|
-
|
|
678
|
-
taskComment = warningComment.step;
|
|
791
|
+
taskComment = warningComment.step;
|
|
679
792
|
changed = changed || warningComment.changed;
|
|
793
|
+
if (warningComment.error) {
|
|
794
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, warningComment.error);
|
|
795
|
+
}
|
|
796
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, detail);
|
|
680
797
|
}
|
|
681
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, detail);
|
|
682
798
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
try {
|
|
687
|
-
if (receipt.verification === 'pending') {
|
|
688
|
-
const capability = issueCapability(receipt, 'verification');
|
|
689
|
-
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
690
|
-
scope: 'verification', operation: 'failed', error: detail
|
|
691
|
-
}, consumedCapabilities);
|
|
692
|
-
}
|
|
693
|
-
else {
|
|
799
|
+
catch (error) {
|
|
800
|
+
const detail = errorOf(error, 'VERIFY_FAILED', true);
|
|
801
|
+
try {
|
|
694
802
|
const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
|
|
695
803
|
receipt = updateReceipt(repoRoot, receipt, {
|
|
696
804
|
verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
|
|
697
805
|
});
|
|
698
|
-
}
|
|
699
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
700
|
-
}
|
|
701
|
-
catch (writeError) {
|
|
702
|
-
const persistence = errorOf(writeError, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
|
|
703
|
-
const hardError = {
|
|
704
|
-
code: 'FINALIZATION_RECEIPT_WRITE_FAILED',
|
|
705
|
-
message: `Unable to persist verification failure (${detail.code}: ${detail.message}): ${persistence.message}`,
|
|
706
|
-
retryable: true
|
|
707
|
-
};
|
|
708
|
-
return terminalResult(taskId, receipt, {
|
|
709
|
-
lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail }
|
|
710
|
-
}, changed, hardError);
|
|
711
|
-
}
|
|
712
|
-
try {
|
|
713
|
-
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
|
|
714
|
-
receipt = warningComment.receipt;
|
|
715
|
-
if (warningComment.step.status !== 'no-op')
|
|
716
|
-
taskComment = warningComment.step;
|
|
717
|
-
changed = changed || warningComment.changed;
|
|
718
|
-
}
|
|
719
|
-
catch { /* preserve the primary error */ }
|
|
720
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
|
|
721
|
-
}
|
|
722
|
-
const summary = await syncPendingSummary({ repoRoot, taskId, agent: request.agent, receipt, commentSync });
|
|
723
|
-
receipt = summary.receipt;
|
|
724
|
-
changed = changed || summary.changed;
|
|
725
|
-
if (summary.error) {
|
|
726
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
727
|
-
const warningComment = await syncPendingTaskComment({
|
|
728
|
-
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
729
|
-
});
|
|
730
|
-
receipt = warningComment.receipt;
|
|
731
|
-
if (warningComment.step.status !== 'no-op')
|
|
732
|
-
taskComment = warningComment.step;
|
|
733
|
-
changed = changed || warningComment.changed;
|
|
734
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification, summary: summary.step }, changed, summary.error);
|
|
735
|
-
}
|
|
736
|
-
let postSummaryVerification = null;
|
|
737
|
-
if (receipt.postSummaryVerification === 'skipped') {
|
|
738
|
-
postSummaryVerification = { status: 'skipped', changed: false, error: null };
|
|
739
|
-
}
|
|
740
|
-
else if (receipt.postSummaryVerification !== 'pending') {
|
|
741
|
-
const replay = await commentSync(taskId, {
|
|
742
|
-
kind: 'summary', agent: request.agent, body: '', cwd: repoRoot, verifyOnly: true,
|
|
743
|
-
summaryAuthorization: { sha256: receipt.summarySha256 ?? '' }
|
|
744
|
-
});
|
|
745
|
-
postSummaryVerification = commentStep(replay);
|
|
746
|
-
if (replay.status !== 'applied' && replay.status !== 'no-op') {
|
|
747
|
-
const error = postSummaryVerification.error ?? { code: 'SUMMARY_VERIFICATION_FAILED', message: 'summary replay verification failed', retryable: true };
|
|
748
|
-
const warnings = replaceWarning(receipt, warningFromError('post-summary-verification', error), 'open');
|
|
749
|
-
receipt = updateReceipt(repoRoot, receipt, { postSummaryVerification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: error });
|
|
750
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
751
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification, summary: summary.step, postSummaryVerification }, changed, error);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
else {
|
|
755
|
-
try {
|
|
756
|
-
const summaryVerification = await commentSync(taskId, {
|
|
757
|
-
kind: 'summary', agent: request.agent, body: '', cwd: repoRoot, verifyOnly: true,
|
|
758
|
-
summaryAuthorization: { sha256: receipt.summarySha256 ?? '' }
|
|
759
|
-
});
|
|
760
|
-
if (summaryVerification.status !== 'applied' && summaryVerification.status !== 'no-op') {
|
|
761
|
-
const error = commentStep(summaryVerification).error ?? { code: 'SUMMARY_VERIFICATION_FAILED', message: 'summary verification failed', retryable: true };
|
|
762
|
-
const warnings = replaceWarning(receipt, warningFromError('post-summary-verification', error), 'open');
|
|
763
|
-
receipt = updateReceipt(repoRoot, receipt, { postSummaryVerification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: error });
|
|
764
806
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
765
|
-
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
|
|
766
|
-
receipt = warningComment.receipt;
|
|
767
|
-
if (warningComment.step.status !== 'no-op')
|
|
768
|
-
taskComment = warningComment.step;
|
|
769
|
-
changed = changed || warningComment.changed;
|
|
770
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification, summary: summary.step, postSummaryVerification: commentStep(summaryVerification) }, changed, error);
|
|
771
807
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
|
|
783
|
-
receipt = warningComment.receipt;
|
|
784
|
-
if (warningComment.step.status !== 'no-op')
|
|
785
|
-
taskComment = warningComment.step;
|
|
786
|
-
changed = changed || warningComment.changed;
|
|
808
|
+
catch (writeError) {
|
|
809
|
+
const persistence = errorOf(writeError, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
|
|
810
|
+
const hardError = {
|
|
811
|
+
code: 'FINALIZATION_RECEIPT_WRITE_FAILED',
|
|
812
|
+
message: `Unable to persist verification failure (${detail.code}: ${detail.message}): ${persistence.message}`,
|
|
813
|
+
retryable: true
|
|
814
|
+
};
|
|
815
|
+
return terminalResult(taskId, receipt, {
|
|
816
|
+
backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail }
|
|
817
|
+
}, changed, hardError);
|
|
787
818
|
}
|
|
788
|
-
|
|
789
|
-
const error = postSummaryVerification.error ?? { code: 'VERIFY_FAILED', message: 'post-summary verification failed', retryable: true };
|
|
790
|
-
const warnings = replaceWarning(receipt, warningFromError('post-summary-verification', error), 'open');
|
|
791
|
-
receipt = updateReceipt(repoRoot, receipt, { postSummaryVerification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: error });
|
|
792
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
819
|
+
try {
|
|
793
820
|
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
|
|
794
821
|
receipt = warningComment.receipt;
|
|
795
822
|
if (warningComment.step.status !== 'no-op')
|
|
796
823
|
taskComment = warningComment.step;
|
|
797
824
|
changed = changed || warningComment.changed;
|
|
798
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification, summary: summary.step, postSummaryVerification }, changed, error);
|
|
799
825
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const detail = errorOf(error, 'POST_SUMMARY_VERIFY_FAILED', true);
|
|
803
|
-
const warnings = replaceWarning(receipt, warningFromError('post-summary-verification', detail), 'open');
|
|
804
|
-
receipt = updateReceipt(repoRoot, receipt, { postSummaryVerification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail });
|
|
805
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
806
|
-
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
|
|
807
|
-
receipt = warningComment.receipt;
|
|
808
|
-
if (warningComment.step.status !== 'no-op')
|
|
809
|
-
taskComment = warningComment.step;
|
|
810
|
-
changed = changed || warningComment.changed;
|
|
811
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification, summary: summary.step, postSummaryVerification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
|
|
826
|
+
catch { /* preserve the primary error */ }
|
|
827
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
|
|
812
828
|
}
|
|
813
829
|
}
|
|
830
|
+
let summary = receipt.summary === 'pending'
|
|
831
|
+
? await syncPendingSummary({ repoRoot, taskId, agent: request.agent, receipt, commentSync })
|
|
832
|
+
: { receipt, step: { status: 'no-op', changed: false, error: null }, changed: false, error: null };
|
|
833
|
+
receipt = summary.receipt;
|
|
834
|
+
changed = changed || summary.changed;
|
|
835
|
+
if (summary.error) {
|
|
836
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
837
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification, summary: summary.step }, changed, summary.error);
|
|
838
|
+
}
|
|
814
839
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
815
|
-
if (receipt.
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
840
|
+
if (receipt.taskComment === 'pending') {
|
|
841
|
+
const finalComment = await syncPendingTaskComment({
|
|
842
|
+
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
843
|
+
});
|
|
844
|
+
receipt = finalComment.receipt;
|
|
845
|
+
taskComment = finalComment.step;
|
|
846
|
+
changed = changed || finalComment.changed;
|
|
847
|
+
if (finalComment.error) {
|
|
848
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification, summary: summary.step }, changed, finalComment.error);
|
|
849
|
+
}
|
|
819
850
|
}
|
|
820
|
-
return
|
|
851
|
+
return preparedResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification, summary: summary.step }, changed);
|
|
821
852
|
}
|
|
822
|
-
async function
|
|
853
|
+
async function prepareTaskFinalization(request, options) {
|
|
854
|
+
if (request.intent !== 'complete' || !request.taskRef || !request.agent) {
|
|
855
|
+
return failed(null, { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'complete finalization requires taskRef and agent', retryable: false });
|
|
856
|
+
}
|
|
857
|
+
const repoRoot = path.resolve(options.repoRoot);
|
|
858
|
+
const resolved = resolveTaskRef(request.taskRef, { repoRoot });
|
|
859
|
+
if (!resolved.ok)
|
|
860
|
+
return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
|
|
861
|
+
try {
|
|
862
|
+
return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.prepare', () => prepareUnderLock(request, resolved.taskId, options));
|
|
863
|
+
}
|
|
864
|
+
catch (error) {
|
|
865
|
+
const detail = error instanceof TaskExecutionLockError
|
|
866
|
+
? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
|
|
867
|
+
: errorOf(error, 'TASK_FINALIZATION_FAILED');
|
|
868
|
+
return failed(resolved.taskId, detail);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
async function commitPreparedTaskFinalization(request, options) {
|
|
823
872
|
if (request.intent !== 'complete' || !request.taskRef || !request.agent) {
|
|
824
873
|
return failed(null, { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'complete finalization requires taskRef and agent', retryable: false });
|
|
825
874
|
}
|
|
@@ -828,7 +877,37 @@ async function applyTaskFinalization(request, options) {
|
|
|
828
877
|
if (!resolved.ok)
|
|
829
878
|
return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
|
|
830
879
|
try {
|
|
831
|
-
return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.
|
|
880
|
+
return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.commit', () => {
|
|
881
|
+
const receipt = readReceipt(repoRoot, resolved.taskId);
|
|
882
|
+
if (!receipt)
|
|
883
|
+
return failed(resolved.taskId, {
|
|
884
|
+
code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation receipt is unavailable', retryable: true
|
|
885
|
+
});
|
|
886
|
+
if (receipt.lifecycle === 'done' && resolved.state === 'completed') {
|
|
887
|
+
return terminalResult(resolved.taskId, receipt, { lifecycle: { status: 'no-op', changed: false, error: null } }, false);
|
|
888
|
+
}
|
|
889
|
+
if (receipt.taskComment === 'pending' || receipt.verification === 'pending' || receipt.summary === 'pending' || receipt.warningProjection === 'pending') {
|
|
890
|
+
return failed(resolved.taskId, {
|
|
891
|
+
code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation has pending external steps', retryable: true
|
|
892
|
+
}, { completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt) });
|
|
893
|
+
}
|
|
894
|
+
const registry = inspectShortIdRegistry(repoRoot);
|
|
895
|
+
if (registry.status !== 'valid')
|
|
896
|
+
return failed(resolved.taskId, {
|
|
897
|
+
code: 'TASK_FINALIZATION_SHORT_ID_REGISTRY_UNAVAILABLE',
|
|
898
|
+
message: `cannot verify canonical short-id registry: ${registry.error.code}: ${registry.error.message}`,
|
|
899
|
+
retryable: false
|
|
900
|
+
});
|
|
901
|
+
const lifecycle = options.lifecycle ?? applyTaskLifecycle;
|
|
902
|
+
const result = lifecycle({ taskRef: resolved.taskId, intent: 'complete', agent: request.agent }, { repoRoot, ...(options.metadataProvider ? { metadataProvider: options.metadataProvider } : {}) });
|
|
903
|
+
const step = lifecycleStep(result);
|
|
904
|
+
if (result.status !== 'applied' && result.status !== 'no-op') {
|
|
905
|
+
const updated = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: step.error });
|
|
906
|
+
return terminalResult(resolved.taskId, updated, { lifecycle: step }, result.changed, step.error);
|
|
907
|
+
}
|
|
908
|
+
const updated = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
|
|
909
|
+
return terminalResult(resolved.taskId, updated, { lifecycle: step }, result.changed);
|
|
910
|
+
});
|
|
832
911
|
}
|
|
833
912
|
catch (error) {
|
|
834
913
|
const detail = error instanceof TaskExecutionLockError
|
|
@@ -837,5 +916,11 @@ async function applyTaskFinalization(request, options) {
|
|
|
837
916
|
return failed(resolved.taskId, detail);
|
|
838
917
|
}
|
|
839
918
|
}
|
|
840
|
-
|
|
919
|
+
async function applyTaskFinalization(request, options) {
|
|
920
|
+
const prepared = await prepareTaskFinalization(request, options);
|
|
921
|
+
if (prepared.status !== 'prepared')
|
|
922
|
+
return prepared;
|
|
923
|
+
return commitPreparedTaskFinalization(request, options);
|
|
924
|
+
}
|
|
925
|
+
export { applyFinalizationReceiptMutation, commitPreparedTaskFinalization, applyTaskFinalization, prepareTaskFinalization, issueCapability as createFinalizationCapability, readTaskFinalizationReceipt, terminalResult };
|
|
841
926
|
//# sourceMappingURL=finalization.js.map
|