@fitlab-ai/agent-infra 0.11.7 → 0.11.9
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +8 -2
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/agent-clients/config.js +11 -3
- package/dist/lib/agent-clients/custom-tool-invocations.js +92 -0
- package/dist/lib/agent-clients/next-steps.js +1 -1
- package/dist/lib/agent-clients/reconcile.js +3 -3
- package/dist/lib/agent-clients/schema.js +18 -10
- package/dist/lib/defaults.json +6 -3
- package/dist/lib/init.js +2 -2
- package/dist/lib/internal/agent-client.js +3 -3
- package/dist/lib/internal/cli-route-inventory.js +3 -0
- package/dist/lib/internal/platform-pr-review.js +20 -3
- package/dist/lib/internal/task-operation-registry.js +3 -1
- package/dist/lib/internal/task-platform-recovery.js +56 -0
- package/dist/lib/merge.js +25 -5
- package/dist/lib/platform/completion-backfill.js +2 -1
- package/dist/lib/platform/in-label-sync.js +12 -1
- package/dist/lib/platform/issue-comments.js +58 -4
- package/dist/lib/platform/issues.js +105 -5
- package/dist/lib/platform/operation-coordinator.js +74 -0
- package/dist/lib/platform/pr-review.js +107 -1
- package/dist/lib/platform/pr-summary.js +79 -57
- package/dist/lib/platform/pull-requests.js +96 -4
- package/dist/lib/sandbox/commands/create.js +6 -3
- package/dist/lib/sandbox/commands/ls.js +4 -3
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/config.js +22 -4
- package/dist/lib/sandbox/git-metadata.js +50 -0
- package/dist/lib/sandbox/index.js +1 -1
- package/dist/lib/sandbox/mounts.js +12 -0
- package/dist/lib/sandbox/recovery.js +12 -22
- package/dist/lib/sandbox/removal.js +58 -20
- package/dist/lib/sandbox/tools.js +31 -7
- package/dist/lib/task/activity-intent.js +4 -3
- package/dist/lib/task/create-service.js +5 -3
- package/dist/lib/task/create.js +22 -5
- package/dist/lib/task/finalization-artifacts.js +4 -1
- package/dist/lib/task/finalization.js +243 -113
- package/dist/lib/task/lifecycle.js +38 -2
- package/dist/lib/task/platform-operation-journal.js +152 -0
- package/dist/lib/task/platform-operation-recovery.js +459 -0
- package/dist/lib/task/verification.js +0 -1
- package/dist/lib/task/workflow-warning-intents.js +7 -1
- package/dist/lib/update.js +2 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/agent-clients/config.ts +9 -3
- package/lib/agent-clients/custom-tool-invocations.ts +129 -0
- package/lib/agent-clients/next-steps.ts +3 -3
- package/lib/agent-clients/reconcile.ts +3 -3
- package/lib/agent-clients/schema.ts +18 -10
- package/lib/defaults.json +6 -3
- package/lib/init.ts +2 -2
- package/lib/internal/agent-client.ts +3 -3
- package/lib/internal/cli-route-inventory.ts +2 -0
- package/lib/internal/platform-pr-review.ts +14 -3
- package/lib/internal/task-operation-registry.ts +3 -1
- package/lib/internal/task-platform-recovery.ts +53 -0
- package/lib/merge.ts +26 -6
- package/lib/platform/completion-backfill.ts +2 -1
- package/lib/platform/in-label-sync.ts +12 -0
- package/lib/platform/issue-comments.ts +59 -3
- package/lib/platform/issues.ts +103 -5
- package/lib/platform/operation-coordinator.ts +96 -0
- package/lib/platform/pr-review.ts +113 -1
- package/lib/platform/pr-summary.ts +22 -1
- package/lib/platform/pull-requests.ts +105 -5
- package/lib/sandbox/commands/create.ts +7 -8
- package/lib/sandbox/commands/ls.ts +4 -3
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/config.ts +23 -5
- package/lib/sandbox/git-metadata.ts +66 -0
- package/lib/sandbox/index.ts +1 -1
- package/lib/sandbox/mounts.ts +14 -0
- package/lib/sandbox/recovery.ts +13 -25
- package/lib/sandbox/removal.ts +57 -19
- package/lib/sandbox/tool-types.ts +2 -1
- package/lib/sandbox/tools.ts +38 -7
- package/lib/task/activity-intent.ts +4 -3
- package/lib/task/create-service.ts +5 -3
- package/lib/task/create.ts +17 -5
- package/lib/task/finalization-artifacts.ts +4 -1
- package/lib/task/finalization.ts +242 -106
- package/lib/task/lifecycle.ts +35 -3
- package/lib/task/platform-operation-journal.ts +230 -0
- package/lib/task/platform-operation-recovery.ts +435 -0
- package/lib/task/verification.ts +1 -2
- package/lib/task/workflow-warning-intents.ts +9 -1
- package/lib/update.ts +2 -3
- package/package.json +1 -1
- package/templates/.agents/README.en.md +20 -66
- package/templates/.agents/README.zh-CN.md +19 -65
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +1 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +12 -0
- package/templates/.agents/rules/task-management.zh-CN.md +12 -0
- package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
- package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +15 -6
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +15 -6
- package/templates/.agents/skills/review-pr/SKILL.en.md +11 -11
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +11 -11
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -0
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -0
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +21 -274
- package/dist/lib/agent-clients/custom-tuis.js +0 -99
- package/lib/agent-clients/custom-tuis.ts +0 -149
|
@@ -5,8 +5,11 @@ import { syncPlatformComment } from '../platform/issue-comments.js';
|
|
|
5
5
|
import { canonicalizeSummaryBody } from '../platform/comment-safety.js';
|
|
6
6
|
import { backfillCompletionComments, inspectCompletionBackfillEligibility } from '../platform/completion-backfill.js';
|
|
7
7
|
import { taskIssueIdentity } from '../platform/task-identities.js';
|
|
8
|
+
import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.js';
|
|
9
|
+
import { resourceIdentityEquals } from '../platform/resource-identity.js';
|
|
8
10
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
9
|
-
import {
|
|
11
|
+
import { recoverPlatformOperations } from './platform-operation-recovery.js';
|
|
12
|
+
import { applyTaskLifecycle, inspectTaskLifecycleProgress, previewTaskCompletion } from './lifecycle.js';
|
|
10
13
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
11
14
|
import { inspectShortIdRegistry } from './short-id.js';
|
|
12
15
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
@@ -329,19 +332,16 @@ function capabilityError(code, message) {
|
|
|
329
332
|
Object.assign(error, { code });
|
|
330
333
|
return error;
|
|
331
334
|
}
|
|
332
|
-
function
|
|
335
|
+
function hasFinalizationTaskDocument(repoRoot, taskId) {
|
|
333
336
|
const resolved = resolveTaskRef(taskId, { repoRoot });
|
|
334
|
-
if (!resolved.ok)
|
|
335
|
-
return false;
|
|
336
|
-
if (resolved.state === 'completed')
|
|
337
|
-
return true;
|
|
338
|
-
if (resolved.state !== 'active')
|
|
337
|
+
if (!resolved.ok || (resolved.state !== 'active' && resolved.state !== 'completed'))
|
|
339
338
|
return false;
|
|
340
339
|
try {
|
|
341
|
-
|
|
340
|
+
const journal = JSON.parse(fs.readFileSync(path.join(resolved.taskDir, '.task-lifecycle.json'), 'utf8'));
|
|
341
|
+
return journal.taskId === taskId && journal.intent === 'complete';
|
|
342
342
|
}
|
|
343
343
|
catch {
|
|
344
|
-
return
|
|
344
|
+
return resolved.state === 'completed';
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
function mutationKeys(value) {
|
|
@@ -388,7 +388,14 @@ function mutationPatch(current, mutation) {
|
|
|
388
388
|
if (mutation.scope === 'task-comment') {
|
|
389
389
|
if (mutation.operation === 'succeeded') {
|
|
390
390
|
const warnings = resolveStepWarnings(current, 'task-comment');
|
|
391
|
-
|
|
391
|
+
const retryWithResolvedWarning = mutation.state === 'done'
|
|
392
|
+
&& current.warnings.some((warning) => warning.step === 'task-comment' && warning.status === 'open');
|
|
393
|
+
return {
|
|
394
|
+
taskComment: retryWithResolvedWarning ? 'pending' : mutation.state,
|
|
395
|
+
warningProjection: warnings.length > 0 || retryWithResolvedWarning ? 'pending' : 'done',
|
|
396
|
+
warnings,
|
|
397
|
+
lastError: null
|
|
398
|
+
};
|
|
392
399
|
}
|
|
393
400
|
const warnings = replaceWarning(current, warningFromError('task-comment', mutation.error), 'open');
|
|
394
401
|
return { taskComment: 'pending', warningProjection: 'pending', warnings, lastError: mutation.error };
|
|
@@ -413,8 +420,8 @@ function mutationPatch(current, mutation) {
|
|
|
413
420
|
: { warningProjection: 'pending', lastError: mutation.error };
|
|
414
421
|
}
|
|
415
422
|
function applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, consumed) {
|
|
416
|
-
if (!
|
|
417
|
-
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active
|
|
423
|
+
if (!hasFinalizationTaskDocument(repoRoot, receipt.taskId)) {
|
|
424
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active task or a completed task');
|
|
418
425
|
}
|
|
419
426
|
const current = readReceipt(repoRoot, receipt.taskId);
|
|
420
427
|
if (!current || current.receiptId !== receipt.receiptId || current.revision !== receipt.revision) {
|
|
@@ -426,8 +433,8 @@ function applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability
|
|
|
426
433
|
return next;
|
|
427
434
|
}
|
|
428
435
|
function applyFinalizationReceiptMutation(repoRoot, receipt, capability, mutation) {
|
|
429
|
-
if (!
|
|
430
|
-
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active
|
|
436
|
+
if (!hasFinalizationTaskDocument(repoRoot, receipt.taskId)) {
|
|
437
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active task or a completed task');
|
|
431
438
|
}
|
|
432
439
|
return withTaskExecutionLock(repoRoot, receipt.taskId, 'task-finalization.receipt-mutation', () => applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, new Set()));
|
|
433
440
|
}
|
|
@@ -483,7 +490,15 @@ async function syncPendingTaskComment(input) {
|
|
|
483
490
|
};
|
|
484
491
|
}
|
|
485
492
|
try {
|
|
486
|
-
const result = await commentSync(taskId, {
|
|
493
|
+
const result = await commentSync(taskId, {
|
|
494
|
+
kind: 'task', agent, cwd: repoRoot, dependency: 'required',
|
|
495
|
+
...(input.projection ? {
|
|
496
|
+
taskProjection: {
|
|
497
|
+
content: input.projection.content,
|
|
498
|
+
sha256: createHash('sha256').update(input.projection.content).digest('hex')
|
|
499
|
+
}
|
|
500
|
+
} : {})
|
|
501
|
+
});
|
|
487
502
|
const step = commentStep(result);
|
|
488
503
|
if (result.status === 'applied' || result.status === 'no-op') {
|
|
489
504
|
const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
|
|
@@ -492,6 +507,14 @@ async function syncPendingTaskComment(input) {
|
|
|
492
507
|
scope: 'task-comment', operation: 'succeeded', state: skipped ? 'skipped' : 'done'
|
|
493
508
|
}, consumedCapabilities);
|
|
494
509
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
510
|
+
if (!skipped && receipt.taskComment === 'pending') {
|
|
511
|
+
const retry = await syncPendingTaskComment({
|
|
512
|
+
...input,
|
|
513
|
+
receipt,
|
|
514
|
+
projection: input.refreshProjection?.() ?? input.projection
|
|
515
|
+
});
|
|
516
|
+
return { ...retry, changed: result.changed || retry.changed };
|
|
517
|
+
}
|
|
495
518
|
return { receipt, step: skipped ? { ...step, status: 'skipped' } : step, changed: result.changed, error: null };
|
|
496
519
|
}
|
|
497
520
|
const detail = step.error ?? { code: 'COMMENT_SYNC_FAILED', message: 'task comment synchronization failed', retryable: true };
|
|
@@ -540,8 +563,8 @@ async function syncPendingSummary(input) {
|
|
|
540
563
|
let { receipt } = input;
|
|
541
564
|
try {
|
|
542
565
|
const resolved = resolveTaskRef(input.taskId, { repoRoot: input.repoRoot });
|
|
543
|
-
if (!resolved.ok ||
|
|
544
|
-
throw Object.assign(new Error('
|
|
566
|
+
if (!resolved.ok || (resolved.state !== 'active' && resolved.state !== 'completed'))
|
|
567
|
+
throw Object.assign(new Error('task is unavailable for summary sync'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
545
568
|
if (!fs.existsSync(path.join(resolved.taskDir, '.delivery-summary.json'))) {
|
|
546
569
|
receipt = updateReceipt(input.repoRoot, receipt, { summary: 'skipped', lastError: null });
|
|
547
570
|
return { receipt, step: { status: 'skipped', changed: false, error: null }, changed: false, error: null };
|
|
@@ -555,7 +578,8 @@ async function syncPendingSummary(input) {
|
|
|
555
578
|
const summary = readDeliverySummary(resolved.taskDir, input.taskId);
|
|
556
579
|
const result = await input.commentSync(input.taskId, {
|
|
557
580
|
kind: 'summary', agent: input.agent, body: summary.body, cwd: input.repoRoot,
|
|
558
|
-
summaryAuthorization: { sha256: summary.sha256 }
|
|
581
|
+
summaryAuthorization: { sha256: summary.sha256 },
|
|
582
|
+
dependency: 'required'
|
|
559
583
|
});
|
|
560
584
|
const step = commentStep(result);
|
|
561
585
|
if (result.status === 'applied' || result.status === 'no-op') {
|
|
@@ -565,7 +589,7 @@ async function syncPendingSummary(input) {
|
|
|
565
589
|
receipt = updateReceipt(input.repoRoot, receipt, {
|
|
566
590
|
summary: skipped ? 'skipped' : 'done',
|
|
567
591
|
taskComment: resolvedWarning ? 'pending' : receipt.taskComment,
|
|
568
|
-
warningProjection: warnings.some((warning) => warning.status === 'open') ? 'pending' : 'done',
|
|
592
|
+
warningProjection: warnings.some((warning) => warning.status === 'open') || resolvedWarning ? 'pending' : 'done',
|
|
569
593
|
warnings,
|
|
570
594
|
lastError: null
|
|
571
595
|
});
|
|
@@ -595,9 +619,12 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
595
619
|
const blocked = [steps.backfill, steps.lifecycle, steps.taskComment, steps.verification, steps.summary].some((step) => step?.status === 'blocked');
|
|
596
620
|
const warnings = openWarnings(receipt);
|
|
597
621
|
const postLifecyclePending = receipt.lifecycle === 'done' && (pending.some((step) => step !== 'lifecycle') || receipt.warningProjection === 'pending');
|
|
598
|
-
const
|
|
622
|
+
const terminalVerificationPending = receipt.lifecycle === 'done' && receipt.verification === 'pending';
|
|
623
|
+
const resultError = error ?? (terminalVerificationPending ? steps.verification?.error : null) ?? null;
|
|
624
|
+
const hardError = error?.code.startsWith('FINALIZATION_') || error?.code === 'TASK_FINALIZATION_RECEIPT_INVALID'
|
|
625
|
+
|| terminalVerificationPending;
|
|
599
626
|
return {
|
|
600
|
-
status: hardError ? (
|
|
627
|
+
status: hardError ? (resultError?.retryable ? 'blocked' : 'failed') : pending.length === 0 ? 'completed' : postLifecyclePending ? 'completed' : blocked ? 'blocked' : 'failed',
|
|
601
628
|
changed,
|
|
602
629
|
taskId,
|
|
603
630
|
backfill: steps.backfill ?? null,
|
|
@@ -608,7 +635,7 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
608
635
|
completedSteps: completedSteps(receipt),
|
|
609
636
|
pendingSteps: pending,
|
|
610
637
|
result: hardError
|
|
611
|
-
? (
|
|
638
|
+
? (resultError?.retryable ? 'blocked' : 'failed')
|
|
612
639
|
: postLifecyclePending
|
|
613
640
|
? 'completed_with_warnings'
|
|
614
641
|
: pending.length === 0
|
|
@@ -617,7 +644,7 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
617
644
|
? 'blocked'
|
|
618
645
|
: 'failed',
|
|
619
646
|
warnings,
|
|
620
|
-
error: hardError ?
|
|
647
|
+
error: hardError ? resultError : postLifecyclePending ? null : resultError
|
|
621
648
|
};
|
|
622
649
|
}
|
|
623
650
|
function preparedResult(taskId, receipt, steps, changed) {
|
|
@@ -637,7 +664,8 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
637
664
|
const lifecycle = options.lifecycle ?? applyTaskLifecycle;
|
|
638
665
|
const backfill = options.backfill ?? backfillCompletionComments;
|
|
639
666
|
const commentSync = options.commentSync ?? syncPlatformComment;
|
|
640
|
-
const
|
|
667
|
+
const issueSync = options.issueSync ?? syncPlatformIssue;
|
|
668
|
+
const issueInspect = options.issueInspect ?? inspectPlatformIssue;
|
|
641
669
|
const consumedCapabilities = new Set();
|
|
642
670
|
let receipt;
|
|
643
671
|
try {
|
|
@@ -679,6 +707,101 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
679
707
|
return failed(taskId, errorOf(error, 'TASK_FINALIZATION_PREFLIGHT_FAILED', true));
|
|
680
708
|
}
|
|
681
709
|
}
|
|
710
|
+
if (preflightState.ok && preflightState.state === 'active') {
|
|
711
|
+
const recovery = await recoverPlatformOperations(taskId, 'all', { agent: request.agent, cwd: repoRoot });
|
|
712
|
+
if (recovery.status !== 'applied' && recovery.status !== 'no-op') {
|
|
713
|
+
return failed(taskId, recovery.error ?? {
|
|
714
|
+
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
715
|
+
message: 'Pending platform operations remain unresolved',
|
|
716
|
+
retryable: true
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
const taskContent = fs.readFileSync(preflightState.taskMdPath, 'utf8');
|
|
720
|
+
const taskFrontmatter = parseTaskFrontmatter(taskContent);
|
|
721
|
+
const issueIdentity = taskIssueIdentity(taskFrontmatter);
|
|
722
|
+
if (issueIdentity) {
|
|
723
|
+
const base = typeof taskFrontmatter.delivery_base_ref === 'string' ? taskFrontmatter.delivery_base_ref.trim() : '';
|
|
724
|
+
let synced;
|
|
725
|
+
try {
|
|
726
|
+
synced = await issueSync(taskId, {
|
|
727
|
+
agent: request.agent, cwd: repoRoot, requirements: true, issueType: true, fields: true,
|
|
728
|
+
...(base ? { inLabels: 'from-diff', base } : {}), dependency: 'required'
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
catch (error) {
|
|
732
|
+
const detail = errorOf(error, 'FINALIZATION_ISSUE_METADATA_FAILED', true);
|
|
733
|
+
return failed(taskId, detail);
|
|
734
|
+
}
|
|
735
|
+
const issueOperations = synced.operations;
|
|
736
|
+
const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields', ...(base ? ['labels:in'] : [])].includes(operation.name)
|
|
737
|
+
&& operation.status !== 'applied' && operation.status !== 'no-op'
|
|
738
|
+
&& !(operation.name === 'requirements' && operation.status === 'skipped' && operation.reasonCode === 'NO_REQUIREMENTS_ANCHOR'));
|
|
739
|
+
if ((synced.status !== 'applied' && synced.status !== 'no-op') || synced.error || badOperation) {
|
|
740
|
+
const error = synced.error ?? {
|
|
741
|
+
code: badOperation?.reasonCode || 'FINALIZATION_ISSUE_METADATA_UNCONFIRMED',
|
|
742
|
+
message: `Required Issue metadata operation '${badOperation?.name ?? 'sync'}' was not confirmed`,
|
|
743
|
+
retryable: true
|
|
744
|
+
};
|
|
745
|
+
return failed(taskId, errorOf(error, 'FINALIZATION_ISSUE_METADATA_FAILED', true));
|
|
746
|
+
}
|
|
747
|
+
let inspected;
|
|
748
|
+
try {
|
|
749
|
+
inspected = await issueInspect(taskId, { cwd: repoRoot });
|
|
750
|
+
}
|
|
751
|
+
catch (error) {
|
|
752
|
+
return failed(taskId, errorOf(error, 'FINALIZATION_ISSUE_INSPECT_FAILED', true));
|
|
753
|
+
}
|
|
754
|
+
if (inspected.status !== 'no-op' || inspected.error || !inspected.issue
|
|
755
|
+
|| !resourceIdentityEquals(inspected.issue.identity, issueIdentity)) {
|
|
756
|
+
return failed(taskId, inspected.error ?? {
|
|
757
|
+
code: 'FINALIZATION_ISSUE_IDENTITY_UNCONFIRMED',
|
|
758
|
+
message: 'Required Issue metadata could not be reread under the bound Issue identity',
|
|
759
|
+
retryable: true
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
for (const name of ['requirements', 'issue-type', 'fields', ...(base ? ['labels:in'] : [])]) {
|
|
763
|
+
const operation = issueOperations?.find((candidate) => candidate.name === name);
|
|
764
|
+
if (!operation)
|
|
765
|
+
return failed(taskId, {
|
|
766
|
+
code: 'FINALIZATION_ISSUE_METADATA_UNCONFIRMED',
|
|
767
|
+
message: `Required Issue metadata operation '${name}' was not reported`, retryable: true
|
|
768
|
+
});
|
|
769
|
+
const before = synced.issue;
|
|
770
|
+
const remote = inspected.issue;
|
|
771
|
+
if (name === 'requirements') {
|
|
772
|
+
const expected = typeof operation.value === 'string' ? operation.value : before?.body;
|
|
773
|
+
if (expected === undefined || remote.body !== expected)
|
|
774
|
+
return failed(taskId, {
|
|
775
|
+
code: 'FINALIZATION_ISSUE_REQUIREMENTS_UNCONFIRMED', message: 'Issue requirements did not match the verified target', retryable: true
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
else if (name === 'issue-type') {
|
|
779
|
+
const expected = typeof operation.value === 'string' ? operation.value : before?.issueType;
|
|
780
|
+
if (!expected || remote.issueType !== expected)
|
|
781
|
+
return failed(taskId, {
|
|
782
|
+
code: 'FINALIZATION_ISSUE_TYPE_UNCONFIRMED', message: 'Issue Type did not match the verified target', retryable: true
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
else if (name === 'labels:in') {
|
|
786
|
+
const expected = Array.isArray(operation.value) ? operation.value : before?.labels;
|
|
787
|
+
if (!expected || expected.filter((label) => label.startsWith('in:')).sort().join('\0')
|
|
788
|
+
!== remote.labels.filter((label) => label.startsWith('in:')).sort().join('\0'))
|
|
789
|
+
return failed(taskId, {
|
|
790
|
+
code: 'FINALIZATION_IN_LABELS_UNCONFIRMED', message: 'Issue in-labels did not match the verified target', retryable: true
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
const expected = operation.value && typeof operation.value === 'object'
|
|
795
|
+
? operation.value
|
|
796
|
+
: before?.fields;
|
|
797
|
+
if (!expected || Object.entries(expected).some(([field, value]) => remote.fields[field] !== value))
|
|
798
|
+
return failed(taskId, {
|
|
799
|
+
code: 'FINALIZATION_ISSUE_FIELDS_UNCONFIRMED', message: 'Pinned Issue fields did not match the verified target', retryable: true
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
682
805
|
let changed = false;
|
|
683
806
|
let backfillResult = null;
|
|
684
807
|
let runBackfill = shouldRunBackfill(receipt);
|
|
@@ -748,9 +871,10 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
748
871
|
else
|
|
749
872
|
backfillResult = { status: 'no-op', changed: false, error: null };
|
|
750
873
|
let lifecycleResult = null;
|
|
874
|
+
let projection;
|
|
751
875
|
try {
|
|
752
876
|
const resolved = resolveTaskRef(taskId, { repoRoot });
|
|
753
|
-
if (
|
|
877
|
+
if (resolved.ok && resolved.state === 'completed') {
|
|
754
878
|
lifecycleResult = { status: 'no-op', changed: false, error: null };
|
|
755
879
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
|
|
756
880
|
}
|
|
@@ -762,6 +886,12 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
762
886
|
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment: null, verification: null }, result.changed, lifecycleResult.error);
|
|
763
887
|
}
|
|
764
888
|
changed = changed || result.changed;
|
|
889
|
+
if (result.timestamp && result.agentInfraVersion) {
|
|
890
|
+
projection = previewTaskCompletion(taskId, request.agent, {
|
|
891
|
+
repoRoot,
|
|
892
|
+
metadataProvider: () => ({ timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion })
|
|
893
|
+
});
|
|
894
|
+
}
|
|
765
895
|
}
|
|
766
896
|
}
|
|
767
897
|
catch (error) {
|
|
@@ -774,88 +904,15 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
774
904
|
}
|
|
775
905
|
let taskComment = null;
|
|
776
906
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
? 'complete-task.completed'
|
|
787
|
-
: 'complete-task.prepared';
|
|
788
|
-
const result = await verify({ taskRef: taskId, event: verificationEvent }, { repoRoot });
|
|
789
|
-
verification = verificationStep(result);
|
|
790
|
-
if (result.status === 'pass') {
|
|
791
|
-
const resolvedTaskWarning = receipt.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
|
|
792
|
-
const warnings = applyVerificationWarnings(receipt, result);
|
|
793
|
-
receipt = updateReceipt(repoRoot, receipt, {
|
|
794
|
-
verification: 'done', taskComment: resolvedTaskWarning ? 'pending' : receipt.taskComment,
|
|
795
|
-
warningProjection: warnings.length > 0 ? 'pending' : 'done', warnings, lastError: null
|
|
796
|
-
});
|
|
797
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
798
|
-
const finalComment = await syncPendingTaskComment({
|
|
799
|
-
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
800
|
-
});
|
|
801
|
-
receipt = finalComment.receipt;
|
|
802
|
-
if (finalComment.step.status !== 'no-op')
|
|
803
|
-
taskComment = finalComment.step;
|
|
804
|
-
changed = changed || finalComment.changed;
|
|
805
|
-
if (finalComment.error) {
|
|
806
|
-
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
else {
|
|
810
|
-
const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
|
|
811
|
-
const warnings = applyVerificationWarnings(receipt, result);
|
|
812
|
-
receipt = updateReceipt(repoRoot, receipt, {
|
|
813
|
-
verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
|
|
814
|
-
});
|
|
815
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
816
|
-
const warningComment = await syncPendingTaskComment({
|
|
817
|
-
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
818
|
-
});
|
|
819
|
-
receipt = warningComment.receipt;
|
|
820
|
-
taskComment = warningComment.step;
|
|
821
|
-
changed = changed || warningComment.changed;
|
|
822
|
-
if (warningComment.error) {
|
|
823
|
-
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, warningComment.error);
|
|
824
|
-
}
|
|
825
|
-
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, detail);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
catch (error) {
|
|
829
|
-
const detail = errorOf(error, 'VERIFY_FAILED', true);
|
|
830
|
-
try {
|
|
831
|
-
const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
|
|
832
|
-
receipt = updateReceipt(repoRoot, receipt, {
|
|
833
|
-
verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
|
|
834
|
-
});
|
|
835
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
836
|
-
}
|
|
837
|
-
catch (writeError) {
|
|
838
|
-
const persistence = errorOf(writeError, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
|
|
839
|
-
const hardError = {
|
|
840
|
-
code: 'FINALIZATION_RECEIPT_WRITE_FAILED',
|
|
841
|
-
message: `Unable to persist verification failure (${detail.code}: ${detail.message}): ${persistence.message}`,
|
|
842
|
-
retryable: true
|
|
843
|
-
};
|
|
844
|
-
return terminalResult(taskId, receipt, {
|
|
845
|
-
backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail }
|
|
846
|
-
}, changed, hardError);
|
|
847
|
-
}
|
|
848
|
-
try {
|
|
849
|
-
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
|
|
850
|
-
receipt = warningComment.receipt;
|
|
851
|
-
if (warningComment.step.status !== 'no-op')
|
|
852
|
-
taskComment = warningComment.step;
|
|
853
|
-
changed = changed || warningComment.changed;
|
|
854
|
-
}
|
|
855
|
-
catch { /* preserve the primary error */ }
|
|
856
|
-
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
907
|
+
const currentProjection = () => projection
|
|
908
|
+
? previewTaskCompletion(taskId, request.agent, {
|
|
909
|
+
repoRoot,
|
|
910
|
+
metadataProvider: () => ({ timestamp: projection.timestamp, agentInfraVersion: projection.agentInfraVersion })
|
|
911
|
+
})
|
|
912
|
+
: undefined;
|
|
913
|
+
const verification = receipt.verification === 'done'
|
|
914
|
+
? { status: 'no-op', changed: false, error: null }
|
|
915
|
+
: { status: 'skipped', changed: false, error: null };
|
|
859
916
|
let summary = receipt.summary === 'pending'
|
|
860
917
|
? await syncPendingSummary({ repoRoot, taskId, agent: request.agent, receipt, commentSync })
|
|
861
918
|
: { receipt, step: { status: 'no-op', changed: false, error: null }, changed: false, error: null };
|
|
@@ -868,7 +925,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
868
925
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
869
926
|
if (receipt.taskComment === 'pending') {
|
|
870
927
|
const finalComment = await syncPendingTaskComment({
|
|
871
|
-
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
928
|
+
repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities
|
|
872
929
|
});
|
|
873
930
|
receipt = finalComment.receipt;
|
|
874
931
|
taskComment = finalComment.step;
|
|
@@ -906,7 +963,7 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
906
963
|
if (!resolved.ok)
|
|
907
964
|
return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
|
|
908
965
|
try {
|
|
909
|
-
return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.commit', () => {
|
|
966
|
+
return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.commit', async () => {
|
|
910
967
|
if (options.controlBinding && options.manifestPath) {
|
|
911
968
|
try {
|
|
912
969
|
const manifest = readSandboxControlManifest(options.manifestPath);
|
|
@@ -958,7 +1015,7 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
958
1015
|
return failed(resolved.taskId, {
|
|
959
1016
|
code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation receipt is unavailable', retryable: true
|
|
960
1017
|
});
|
|
961
|
-
if (receipt.lifecycle === 'done' && resolved.state === 'completed') {
|
|
1018
|
+
if (receipt.lifecycle === 'done' && receipt.verification !== 'pending' && resolved.state === 'completed') {
|
|
962
1019
|
return terminalResult(resolved.taskId, receipt, { lifecycle: { status: 'no-op', changed: false, error: null } }, false);
|
|
963
1020
|
}
|
|
964
1021
|
if (options.controlBinding) {
|
|
@@ -997,7 +1054,7 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
997
1054
|
}
|
|
998
1055
|
}
|
|
999
1056
|
}
|
|
1000
|
-
if (receipt.taskComment === 'pending' || receipt.
|
|
1057
|
+
if (receipt.taskComment === 'pending' || receipt.summary === 'pending' || receipt.warningProjection === 'pending') {
|
|
1001
1058
|
return failed(resolved.taskId, {
|
|
1002
1059
|
code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation has pending external steps', retryable: true
|
|
1003
1060
|
}, { completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt) });
|
|
@@ -1009,6 +1066,7 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
1009
1066
|
message: `cannot verify canonical short-id registry: ${registry.error.code}: ${registry.error.message}`,
|
|
1010
1067
|
retryable: false
|
|
1011
1068
|
});
|
|
1069
|
+
receipt = updateReceipt(repoRoot, receipt, { verification: 'pending' });
|
|
1012
1070
|
const lifecycle = options.lifecycle ?? applyTaskLifecycle;
|
|
1013
1071
|
const result = lifecycle({ taskRef: resolved.taskId, intent: 'complete', agent: request.agent }, { repoRoot, ...(options.metadataProvider ? { metadataProvider: options.metadataProvider } : {}) });
|
|
1014
1072
|
const step = lifecycleStep(result);
|
|
@@ -1016,8 +1074,80 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
1016
1074
|
const updated = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: step.error });
|
|
1017
1075
|
return terminalResult(resolved.taskId, updated, { lifecycle: step }, result.changed, step.error);
|
|
1018
1076
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1077
|
+
let verificationResult;
|
|
1078
|
+
try {
|
|
1079
|
+
verificationResult = await (options.verify ?? verifyTaskEvent)({ taskRef: resolved.taskId, event: 'complete-task.completed' }, { repoRoot });
|
|
1080
|
+
}
|
|
1081
|
+
catch (error) {
|
|
1082
|
+
const detail = errorOf(error, 'VERIFY_FAILED', true);
|
|
1083
|
+
const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
|
|
1084
|
+
let updated;
|
|
1085
|
+
try {
|
|
1086
|
+
updated = updateReceipt(repoRoot, receipt, {
|
|
1087
|
+
lifecycle: 'done', verification: 'pending', taskComment: 'pending', warningProjection: 'pending',
|
|
1088
|
+
warnings, lastError: detail
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
catch (persistCause) {
|
|
1092
|
+
const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
|
|
1093
|
+
persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
|
|
1094
|
+
persistError.message = `${persistError.message}; verification also failed: ${detail.code}: ${detail.message}`;
|
|
1095
|
+
return failed(resolved.taskId, persistError, {
|
|
1096
|
+
changed: result.changed,
|
|
1097
|
+
lifecycle: step,
|
|
1098
|
+
verification: { status: 'blocked', changed: false, error: persistError },
|
|
1099
|
+
completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
const verification = { status: detail.retryable ? 'blocked' : 'failed', changed: false, error: detail };
|
|
1103
|
+
return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed, detail);
|
|
1104
|
+
}
|
|
1105
|
+
const verification = verificationStep(verificationResult);
|
|
1106
|
+
if (verificationResult.status === 'pass') {
|
|
1107
|
+
const resolvedVerificationWarning = receipt.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
|
|
1108
|
+
const warnings = applyVerificationWarnings(receipt, verificationResult);
|
|
1109
|
+
const hasOpenWarnings = warnings.some((warning) => warning.status === 'open');
|
|
1110
|
+
let updated;
|
|
1111
|
+
try {
|
|
1112
|
+
updated = updateReceipt(repoRoot, receipt, {
|
|
1113
|
+
lifecycle: 'done', verification: 'done',
|
|
1114
|
+
taskComment: resolvedVerificationWarning ? 'pending' : receipt.taskComment,
|
|
1115
|
+
warningProjection: hasOpenWarnings ? 'pending' : 'done', warnings, lastError: null
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
catch (persistCause) {
|
|
1119
|
+
const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
|
|
1120
|
+
persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
|
|
1121
|
+
return failed(resolved.taskId, persistError, {
|
|
1122
|
+
changed: result.changed,
|
|
1123
|
+
lifecycle: step,
|
|
1124
|
+
verification,
|
|
1125
|
+
completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed);
|
|
1129
|
+
}
|
|
1130
|
+
const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
|
|
1131
|
+
const warnings = applyVerificationWarnings(receipt, verificationResult);
|
|
1132
|
+
let updated;
|
|
1133
|
+
try {
|
|
1134
|
+
updated = updateReceipt(repoRoot, receipt, {
|
|
1135
|
+
lifecycle: 'done', verification: 'pending', taskComment: 'pending', warningProjection: 'pending',
|
|
1136
|
+
warnings, lastError: detail
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
catch (persistCause) {
|
|
1140
|
+
const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
|
|
1141
|
+
persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
|
|
1142
|
+
persistError.message = `${persistError.message}; verification also failed: ${detail.code}: ${detail.message}`;
|
|
1143
|
+
return failed(resolved.taskId, persistError, {
|
|
1144
|
+
changed: result.changed,
|
|
1145
|
+
lifecycle: step,
|
|
1146
|
+
verification: { status: 'blocked', changed: false, error: persistError },
|
|
1147
|
+
completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed, detail);
|
|
1021
1151
|
});
|
|
1022
1152
|
}
|
|
1023
1153
|
catch (error) {
|
|
@@ -4,7 +4,8 @@ import path from 'node:path';
|
|
|
4
4
|
import { appendActivityEntry, locateActivityLog } from './activity-log.js';
|
|
5
5
|
import { artifactFamilyCatalog, inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
6
6
|
import { parseArtifactName } from './artifact-name.js';
|
|
7
|
-
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
7
|
+
import { parseTypedTaskFrontmatter, updateTaskFrontmatter } from './frontmatter.js';
|
|
8
|
+
import { upsertSection } from './sections.js';
|
|
8
9
|
import { locateHotTaskDirs, resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
|
|
9
10
|
import { configuredShortIdLength, executeShortIdCommand, loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.js';
|
|
10
11
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
@@ -261,6 +262,31 @@ function mutationsFor(request, content, metadata, restoredFiles, allowMissingAct
|
|
|
261
262
|
}
|
|
262
263
|
return mutations;
|
|
263
264
|
}
|
|
265
|
+
function previewTaskCompletion(taskRef, agent, options = {}) {
|
|
266
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
267
|
+
if (!resolved.ok)
|
|
268
|
+
throw Object.assign(new Error(resolved.message), { code: resolved.code });
|
|
269
|
+
if (resolved.state !== 'active')
|
|
270
|
+
throw Object.assign(new Error(`task ${resolved.taskId} is ${resolved.state}, expected active`), { code: 'TASK_STATE_MISMATCH' });
|
|
271
|
+
const original = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
272
|
+
const metadata = (options.metadataProvider ?? captureTaskWriteMetadata)();
|
|
273
|
+
if (!isValidAgentInfraVersion(metadata.agentInfraVersion))
|
|
274
|
+
throw Object.assign(new Error('metadata agentInfraVersion is invalid'), { code: 'LIFECYCLE_METADATA_FAILED' });
|
|
275
|
+
const mutations = mutationsFor({ taskRef: resolved.taskId, intent: 'complete', agent }, original, metadata, 0);
|
|
276
|
+
if (!Array.isArray(mutations))
|
|
277
|
+
throw Object.assign(new Error(mutations.message), { code: mutations.code });
|
|
278
|
+
let content = original;
|
|
279
|
+
for (const mutation of mutations) {
|
|
280
|
+
if (mutation.kind === 'frontmatter')
|
|
281
|
+
content = updateTaskFrontmatter(content, mutation.set, mutation.remove);
|
|
282
|
+
else if (mutation.kind === 'section')
|
|
283
|
+
content = upsertSection(content, mutation).content;
|
|
284
|
+
else
|
|
285
|
+
throw Object.assign(new Error('completion projection contains an unsupported mutation'), { code: 'LIFECYCLE_PROJECTION_INVALID' });
|
|
286
|
+
}
|
|
287
|
+
content = updateTaskFrontmatter(content, { updated_at: metadata.timestamp, agent_infra_version: metadata.agentInfraVersion });
|
|
288
|
+
return { taskId: resolved.taskId, content, timestamp: metadata.timestamp, agentInfraVersion: metadata.agentInfraVersion };
|
|
289
|
+
}
|
|
264
290
|
function writeJournal(file, journal, io, create = false) {
|
|
265
291
|
const temporary = `${file}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
266
292
|
io.writeFileSync(temporary, `${JSON.stringify(journal, null, 2)}\n`, 'wx');
|
|
@@ -593,6 +619,16 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
593
619
|
completed.add('registry-committed');
|
|
594
620
|
}
|
|
595
621
|
let taskOperations = [];
|
|
622
|
+
if (options.prepareOnly && !matchingCompletion(io.readFileSync(path.join(sourcePath, 'task.md')), request, restoredFiles)) {
|
|
623
|
+
return {
|
|
624
|
+
...failed(request, { code: '', message: '' }), status: 'applied', changed: true, error: null,
|
|
625
|
+
taskId, sourceState, targetState: spec.target, sourcePath, targetPath,
|
|
626
|
+
task: { operations: [] }, directory: { effect: 'unchanged', changed: false },
|
|
627
|
+
shortId: { effect: 'unchanged', shortId: null, changed: false },
|
|
628
|
+
timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion,
|
|
629
|
+
journalPath, completedSteps: [...completed], pendingSteps: STEPS.filter((step) => !completed.has(step))
|
|
630
|
+
};
|
|
631
|
+
}
|
|
596
632
|
if (matchingCompletion(io.readFileSync(path.join(sourcePath, 'task.md')), request, restoredFiles))
|
|
597
633
|
completed.add('task-written');
|
|
598
634
|
if (!completed.has('task-written')) {
|
|
@@ -724,5 +760,5 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
724
760
|
journalPath, completedSteps: [...completed], pendingSteps: []
|
|
725
761
|
};
|
|
726
762
|
}
|
|
727
|
-
export { lifecycleIntentCatalog, lifecycleFailureCatalog, lifecycleProducerCatalog, applyTaskLifecycle };
|
|
763
|
+
export { lifecycleIntentCatalog, lifecycleFailureCatalog, lifecycleProducerCatalog, applyTaskLifecycle, previewTaskCompletion };
|
|
728
764
|
//# sourceMappingURL=lifecycle.js.map
|