@fitlab-ai/agent-infra 0.11.3 → 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 +1 -1
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- 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-operation-registry.js +1 -0
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/sandbox/control/server.js +26 -143
- 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 +3 -2
- package/dist/lib/task/decision-intents.js +12 -0
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +80 -64
- package/dist/lib/task/finalization.js +301 -143
- package/dist/lib/task/ledger-intents.js +157 -15
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +10 -0
- package/dist/lib/task/local-artifact-finalization.js +1 -20
- package/dist/lib/task/orchestration.js +17 -7
- 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/internal/cli-route-inventory.ts +1 -1
- 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-operation-registry.ts +1 -0
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/sandbox/control/server.ts +21 -146
- 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 +3 -1
- package/lib/task/decision-intents.ts +9 -0
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +82 -64
- package/lib/task/finalization.ts +269 -111
- package/lib/task/ledger-intents.ts +155 -15
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +13 -0
- package/lib/task/local-artifact-finalization.ts +2 -25
- package/lib/task/orchestration.ts +26 -8
- 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/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-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- 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/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 -33
- package/lib/sandbox/control/finalization-response.ts +0 -35
|
@@ -3,7 +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 } from '../platform/completion-backfill.js';
|
|
6
|
+
import { backfillCompletionComments, inspectCompletionBackfillEligibility } from '../platform/completion-backfill.js';
|
|
7
7
|
import { taskIssueIdentity } from '../platform/task-identities.js';
|
|
8
8
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
9
9
|
import { applyTaskLifecycle } from './lifecycle.js';
|
|
@@ -221,6 +221,77 @@ function resolveStepWarnings(receipt, step) {
|
|
|
221
221
|
? { ...warning, status: 'resolved', resolvedAt: now() }
|
|
222
222
|
: warning);
|
|
223
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
|
+
}
|
|
224
295
|
function issueCapability(receipt, scope) {
|
|
225
296
|
return {
|
|
226
297
|
receiptId: receipt.receiptId,
|
|
@@ -236,6 +307,21 @@ function capabilityError(code, message) {
|
|
|
236
307
|
Object.assign(error, { code });
|
|
237
308
|
return error;
|
|
238
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
|
+
}
|
|
239
325
|
function mutationKeys(value) {
|
|
240
326
|
return Object.keys(value).sort();
|
|
241
327
|
}
|
|
@@ -305,9 +391,8 @@ function mutationPatch(current, mutation) {
|
|
|
305
391
|
: { warningProjection: 'pending', lastError: mutation.error };
|
|
306
392
|
}
|
|
307
393
|
function applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, consumed) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
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');
|
|
311
396
|
}
|
|
312
397
|
const current = readReceipt(repoRoot, receipt.taskId);
|
|
313
398
|
if (!current || current.receiptId !== receipt.receiptId || current.revision !== receipt.revision) {
|
|
@@ -319,11 +404,10 @@ function applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability
|
|
|
319
404
|
return next;
|
|
320
405
|
}
|
|
321
406
|
function applyFinalizationReceiptMutation(repoRoot, receipt, capability, mutation) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
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');
|
|
325
409
|
}
|
|
326
|
-
return withTaskExecutionLock(repoRoot,
|
|
410
|
+
return withTaskExecutionLock(repoRoot, receipt.taskId, 'task-finalization.receipt-mutation', () => applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, new Set()));
|
|
327
411
|
}
|
|
328
412
|
function warningKey(warning) {
|
|
329
413
|
return `${warning.step}\0${warning.code}\0${warning.target}`;
|
|
@@ -334,7 +418,8 @@ function reconcileWarningProjection(repoRoot, taskId, receipt, consumed) {
|
|
|
334
418
|
const warnings = [...new Map(receipt.warnings.map((warning) => [warningKey(warning), warning])).values()];
|
|
335
419
|
for (const warning of warnings) {
|
|
336
420
|
try {
|
|
337
|
-
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' });
|
|
338
423
|
if (projected.status === 'failed') {
|
|
339
424
|
const detail = {
|
|
340
425
|
code: projected.error?.code || 'FINALIZATION_WARNING_PROJECTION_FAILED',
|
|
@@ -433,8 +518,12 @@ async function syncPendingSummary(input) {
|
|
|
433
518
|
let { receipt } = input;
|
|
434
519
|
try {
|
|
435
520
|
const resolved = resolveTaskRef(input.taskId, { repoRoot: input.repoRoot });
|
|
436
|
-
if (!resolved.ok ||
|
|
437
|
-
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
|
+
}
|
|
438
527
|
if (!taskIssueIdentity(parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8')))) {
|
|
439
528
|
receipt = updateReceipt(input.repoRoot, receipt, {
|
|
440
529
|
summary: 'skipped', lastError: null
|
|
@@ -509,7 +598,19 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
509
598
|
error: hardError ? error : postLifecyclePending ? null : error
|
|
510
599
|
};
|
|
511
600
|
}
|
|
512
|
-
|
|
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) {
|
|
513
614
|
const repoRoot = path.resolve(options.repoRoot);
|
|
514
615
|
const lifecycle = options.lifecycle ?? applyTaskLifecycle;
|
|
515
616
|
const backfill = options.backfill ?? backfillCompletionComments;
|
|
@@ -551,80 +652,87 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
551
652
|
}
|
|
552
653
|
let changed = false;
|
|
553
654
|
let backfillResult = null;
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
+
}
|
|
702
|
+
}
|
|
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
|
+
}
|
|
562
713
|
return failed(taskId, detail, {
|
|
563
|
-
backfill:
|
|
714
|
+
backfill: { status: detail.retryable ? 'blocked' : 'failed', changed: false, error: detail },
|
|
564
715
|
completedSteps: completedSteps(receipt),
|
|
565
716
|
pendingSteps: pendingSteps(receipt)
|
|
566
717
|
});
|
|
567
718
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
receipt = updateReceipt(repoRoot, receipt, { taskComment: 'pending' });
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
catch (error) {
|
|
574
|
-
const detail = errorOf(error, 'COMPLETION_BACKFILL_FAILED', true);
|
|
575
|
-
return failed(taskId, detail, {
|
|
576
|
-
backfill: { status: detail.retryable ? 'blocked' : 'failed', changed: false, error: detail },
|
|
577
|
-
completedSteps: completedSteps(receipt),
|
|
578
|
-
pendingSteps: pendingSteps(receipt)
|
|
579
|
-
});
|
|
580
|
-
}
|
|
719
|
+
else
|
|
720
|
+
backfillResult = { status: 'no-op', changed: false, error: null };
|
|
581
721
|
let lifecycleResult = null;
|
|
582
722
|
try {
|
|
583
723
|
const resolved = resolveTaskRef(taskId, { repoRoot });
|
|
584
|
-
|
|
585
|
-
if (registry && registry.status !== 'valid') {
|
|
586
|
-
const detail = {
|
|
587
|
-
code: 'TASK_FINALIZATION_SHORT_ID_REGISTRY_UNAVAILABLE',
|
|
588
|
-
message: `cannot verify canonical short-id registry: ${registry.error.code}: ${registry.error.message}`,
|
|
589
|
-
retryable: false
|
|
590
|
-
};
|
|
591
|
-
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
|
|
592
|
-
return terminalResult(taskId, receipt, {
|
|
593
|
-
backfill: backfillResult,
|
|
594
|
-
lifecycle: { status: 'failed', changed: false, error: detail },
|
|
595
|
-
taskComment: null,
|
|
596
|
-
verification: null
|
|
597
|
-
}, changed, detail);
|
|
598
|
-
}
|
|
599
|
-
const shortIds = registry?.status === 'valid' ? registry.shortIds : null;
|
|
600
|
-
if (shortIds?.has(taskId)) {
|
|
601
|
-
const detail = {
|
|
602
|
-
code: 'TASK_FINALIZATION_CANONICAL_STATE_INVALID',
|
|
603
|
-
message: 'completed task still has an active short-id registry entry',
|
|
604
|
-
retryable: false
|
|
605
|
-
};
|
|
606
|
-
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
|
|
607
|
-
return terminalResult(taskId, receipt, {
|
|
608
|
-
backfill: backfillResult,
|
|
609
|
-
lifecycle: { status: 'failed', changed: false, error: detail },
|
|
610
|
-
taskComment: null,
|
|
611
|
-
verification: null
|
|
612
|
-
}, changed, detail);
|
|
613
|
-
}
|
|
614
|
-
const lifecycleDone = receipt.lifecycle === 'done' && resolved.ok && resolved.state === 'completed' && shortIds !== null;
|
|
615
|
-
if (lifecycleDone) {
|
|
724
|
+
if (receipt.lifecycle === 'done' && resolved.ok && resolved.state === 'completed') {
|
|
616
725
|
lifecycleResult = { status: 'no-op', changed: false, error: null };
|
|
617
726
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
|
|
618
727
|
}
|
|
619
728
|
else {
|
|
620
|
-
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 } : {}) });
|
|
621
730
|
lifecycleResult = lifecycleStep(result);
|
|
622
731
|
if (result.status !== 'applied' && result.status !== 'no-op') {
|
|
623
732
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: lifecycleResult.error });
|
|
624
733
|
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment: null, verification: null }, result.changed, lifecycleResult.error);
|
|
625
734
|
}
|
|
626
735
|
changed = changed || result.changed;
|
|
627
|
-
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
|
|
628
736
|
}
|
|
629
737
|
}
|
|
630
738
|
catch (error) {
|
|
@@ -637,27 +745,26 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
637
745
|
}
|
|
638
746
|
let taskComment = null;
|
|
639
747
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
640
|
-
const initialComment = await syncPendingTaskComment({
|
|
641
|
-
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
642
|
-
});
|
|
643
|
-
receipt = initialComment.receipt;
|
|
644
|
-
taskComment = initialComment.step;
|
|
645
|
-
changed = changed || initialComment.changed;
|
|
646
|
-
if (initialComment.error) {
|
|
647
|
-
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: null }, changed, initialComment.error);
|
|
648
|
-
}
|
|
649
748
|
let verification = null;
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
+
});
|
|
661
768
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
662
769
|
const finalComment = await syncPendingTaskComment({
|
|
663
770
|
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
@@ -671,72 +778,58 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
671
778
|
}
|
|
672
779
|
}
|
|
673
780
|
else {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
677
|
-
else {
|
|
678
|
-
const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
|
|
679
|
-
if (verificationPending) {
|
|
680
|
-
const capability = issueCapability(receipt, 'verification');
|
|
681
|
-
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
682
|
-
scope: 'verification', operation: 'failed', error: detail
|
|
683
|
-
}, consumedCapabilities);
|
|
684
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
685
|
-
}
|
|
686
|
-
else {
|
|
687
|
-
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);
|
|
688
783
|
receipt = updateReceipt(repoRoot, receipt, {
|
|
689
784
|
verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
|
|
690
785
|
});
|
|
691
786
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
692
|
-
const warningComment = await syncPendingTaskComment({
|
|
787
|
+
const warningComment = await syncPendingTaskComment({
|
|
788
|
+
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
789
|
+
});
|
|
693
790
|
receipt = warningComment.receipt;
|
|
694
|
-
|
|
695
|
-
taskComment = warningComment.step;
|
|
791
|
+
taskComment = warningComment.step;
|
|
696
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);
|
|
697
797
|
}
|
|
698
|
-
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, detail);
|
|
699
798
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
try {
|
|
704
|
-
if (receipt.verification === 'pending') {
|
|
705
|
-
const capability = issueCapability(receipt, 'verification');
|
|
706
|
-
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
707
|
-
scope: 'verification', operation: 'failed', error: detail
|
|
708
|
-
}, consumedCapabilities);
|
|
709
|
-
}
|
|
710
|
-
else {
|
|
799
|
+
catch (error) {
|
|
800
|
+
const detail = errorOf(error, 'VERIFY_FAILED', true);
|
|
801
|
+
try {
|
|
711
802
|
const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
|
|
712
803
|
receipt = updateReceipt(repoRoot, receipt, {
|
|
713
804
|
verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
|
|
714
805
|
});
|
|
806
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
715
807
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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);
|
|
818
|
+
}
|
|
819
|
+
try {
|
|
820
|
+
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
|
|
821
|
+
receipt = warningComment.receipt;
|
|
822
|
+
if (warningComment.step.status !== 'no-op')
|
|
823
|
+
taskComment = warningComment.step;
|
|
824
|
+
changed = changed || warningComment.changed;
|
|
825
|
+
}
|
|
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);
|
|
735
828
|
}
|
|
736
|
-
catch { /* preserve the primary error */ }
|
|
737
|
-
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
|
|
738
829
|
}
|
|
739
|
-
let summary =
|
|
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 };
|
|
740
833
|
receipt = summary.receipt;
|
|
741
834
|
changed = changed || summary.changed;
|
|
742
835
|
if (summary.error) {
|
|
@@ -744,9 +837,20 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
744
837
|
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification, summary: summary.step }, changed, summary.error);
|
|
745
838
|
}
|
|
746
839
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
747
|
-
|
|
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
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return preparedResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification, summary: summary.step }, changed);
|
|
748
852
|
}
|
|
749
|
-
async function
|
|
853
|
+
async function prepareTaskFinalization(request, options) {
|
|
750
854
|
if (request.intent !== 'complete' || !request.taskRef || !request.agent) {
|
|
751
855
|
return failed(null, { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'complete finalization requires taskRef and agent', retryable: false });
|
|
752
856
|
}
|
|
@@ -755,7 +859,7 @@ async function applyTaskFinalization(request, options) {
|
|
|
755
859
|
if (!resolved.ok)
|
|
756
860
|
return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
|
|
757
861
|
try {
|
|
758
|
-
return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.
|
|
862
|
+
return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.prepare', () => prepareUnderLock(request, resolved.taskId, options));
|
|
759
863
|
}
|
|
760
864
|
catch (error) {
|
|
761
865
|
const detail = error instanceof TaskExecutionLockError
|
|
@@ -764,5 +868,59 @@ async function applyTaskFinalization(request, options) {
|
|
|
764
868
|
return failed(resolved.taskId, detail);
|
|
765
869
|
}
|
|
766
870
|
}
|
|
767
|
-
|
|
871
|
+
async function commitPreparedTaskFinalization(request, options) {
|
|
872
|
+
if (request.intent !== 'complete' || !request.taskRef || !request.agent) {
|
|
873
|
+
return failed(null, { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'complete finalization requires taskRef and agent', retryable: false });
|
|
874
|
+
}
|
|
875
|
+
const repoRoot = path.resolve(options.repoRoot);
|
|
876
|
+
const resolved = resolveTaskRef(request.taskRef, { repoRoot });
|
|
877
|
+
if (!resolved.ok)
|
|
878
|
+
return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
|
|
879
|
+
try {
|
|
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
|
+
});
|
|
911
|
+
}
|
|
912
|
+
catch (error) {
|
|
913
|
+
const detail = error instanceof TaskExecutionLockError
|
|
914
|
+
? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
|
|
915
|
+
: errorOf(error, 'TASK_FINALIZATION_FAILED');
|
|
916
|
+
return failed(resolved.taskId, detail);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
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 };
|
|
768
926
|
//# sourceMappingURL=finalization.js.map
|