@fitlab-ai/agent-infra 0.11.1 → 0.11.3
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/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/platform-comment.js +59 -0
- package/dist/lib/internal/sandbox-control.js +2 -171
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +7 -2
- package/dist/lib/internal/task-review.js +5 -5
- package/dist/lib/internal/task-validate.js +0 -6
- package/dist/lib/platform/comment-safety.js +7 -1
- package/dist/lib/platform/github-provider.js +13 -3
- package/dist/lib/platform/issue-comments.js +93 -15
- package/dist/lib/platform/pr-summary.js +7 -2
- package/dist/lib/platform/provider-contract.d.ts +2 -0
- package/dist/lib/platform/provider-validation.js +7 -2
- package/dist/lib/platform/verification-sync.js +19 -2
- package/dist/lib/sandbox/control/client.js +3 -44
- package/dist/lib/sandbox/control/executor.js +14 -117
- package/dist/lib/sandbox/control/finalization-response.js +8 -2
- package/dist/lib/sandbox/control/protocol.js +5 -64
- package/dist/lib/sandbox/control/server.js +29 -206
- package/dist/lib/sandbox/control/state.js +1 -7
- package/dist/lib/sandbox/control/task-view.js +2 -1
- package/dist/lib/task/artifact-command.js +6 -6
- package/dist/lib/task/codex-orchestration.js +10 -208
- package/dist/lib/task/commit-operation.js +3 -18
- package/dist/lib/task/control-authority.js +35 -44
- package/dist/lib/task/control-recovery.js +3 -42
- package/dist/lib/task/delegation-receipts.js +16 -78
- package/dist/lib/task/events.js +89 -237
- package/dist/lib/task/finalization-artifacts.js +1 -3
- package/dist/lib/task/finalization-state.js +2 -1
- package/dist/lib/task/finalization.js +208 -57
- package/dist/lib/task/lifecycle-execution.js +2 -11
- package/dist/lib/task/lifecycle.js +1 -1
- package/dist/lib/task/local-artifact-finalization.js +25 -242
- package/dist/lib/task/manual-validation-receipt.js +8 -3
- package/dist/lib/task/orchestration.js +12 -24
- package/dist/lib/task/review-command.js +0 -1
- package/dist/lib/task/review-finalization.js +46 -211
- 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/platform-comment.ts +38 -0
- package/lib/internal/sandbox-control.ts +1 -185
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +8 -4
- package/lib/internal/task-review.ts +5 -5
- package/lib/internal/task-validate.ts +0 -5
- package/lib/platform/comment-safety.ts +8 -0
- package/lib/platform/github-provider.ts +11 -2
- package/lib/platform/issue-comments.ts +102 -17
- package/lib/platform/pr-summary.ts +10 -2
- package/lib/platform/provider-contract.ts +2 -0
- package/lib/platform/provider-validation.ts +6 -2
- package/lib/platform/verification-sync.ts +19 -2
- package/lib/sandbox/control/client.ts +5 -68
- package/lib/sandbox/control/executor.ts +14 -125
- package/lib/sandbox/control/finalization-response.ts +8 -2
- package/lib/sandbox/control/protocol.ts +7 -83
- package/lib/sandbox/control/server.ts +30 -220
- package/lib/sandbox/control/state.ts +1 -9
- package/lib/sandbox/control/task-view.ts +2 -1
- package/lib/task/artifact-command.ts +6 -7
- package/lib/task/codex-orchestration.ts +8 -241
- package/lib/task/commit-operation.ts +3 -23
- package/lib/task/control-authority.ts +39 -55
- package/lib/task/control-recovery.ts +3 -43
- package/lib/task/delegation-receipts.ts +14 -81
- package/lib/task/events.ts +106 -255
- package/lib/task/finalization-artifacts.ts +1 -3
- package/lib/task/finalization-state.ts +2 -1
- package/lib/task/finalization.ts +221 -65
- package/lib/task/lifecycle-execution.ts +10 -21
- package/lib/task/lifecycle.ts +2 -2
- package/lib/task/local-artifact-finalization.ts +23 -281
- package/lib/task/manual-validation-receipt.ts +8 -2
- package/lib/task/orchestration.ts +30 -53
- package/lib/task/review-command.ts +0 -1
- package/lib/task/review-finalization.ts +45 -193
- package/package.json +2 -2
- package/templates/.agents/rules/lifecycle-orchestration.en.md +23 -9
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +23 -9
- package/templates/.agents/rules/local-artifact-repair.en.md +14 -60
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -60
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- 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 +3 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- 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 +10 -9
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +10 -9
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
- package/dist/lib/sandbox/control/task-workflow.js +0 -74
- package/dist/lib/sandbox/control/workflow-executor.js +0 -157
- 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 -18
- package/dist/lib/task/workflow-dispatch.js +0 -161
- package/lib/sandbox/control/task-workflow.ts +0 -95
- package/lib/sandbox/control/workflow-executor.ts +0 -183
- package/lib/task/lifecycle-recovery.ts +0 -691
- package/lib/task/recovery-warning.ts +0 -21
- package/lib/task/workflow-command.ts +0 -19
- package/lib/task/workflow-dispatch.ts +0 -171
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { syncPlatformComment } from '../platform/issue-comments.js';
|
|
5
|
+
import { canonicalizeSummaryBody } from '../platform/comment-safety.js';
|
|
6
|
+
import { backfillCompletionComments } from '../platform/completion-backfill.js';
|
|
7
|
+
import { taskIssueIdentity } from '../platform/task-identities.js';
|
|
8
|
+
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
5
9
|
import { applyTaskLifecycle } from './lifecycle.js';
|
|
6
10
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
11
|
import { inspectShortIdRegistry } from './short-id.js';
|
|
@@ -9,8 +13,8 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
|
|
|
9
13
|
import { verifyTaskEvent } from './verification.js';
|
|
10
14
|
import { projectFinalizationWarning } from './workflow-warning-intents.js';
|
|
11
15
|
import { mergeOperationWarnings } from './operation-outcome.js';
|
|
12
|
-
const RECEIPT_VERSION =
|
|
13
|
-
const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification'];
|
|
16
|
+
const RECEIPT_VERSION = 4;
|
|
17
|
+
const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification', 'summary'];
|
|
14
18
|
function finalizationRoot(repoRoot) {
|
|
15
19
|
return path.join(repoRoot, '.agents', 'workspace', '.task-finalization');
|
|
16
20
|
}
|
|
@@ -31,9 +35,11 @@ function failed(taskId, error, overrides = {}) {
|
|
|
31
35
|
status: error.retryable ? 'blocked' : 'failed',
|
|
32
36
|
changed: false,
|
|
33
37
|
taskId,
|
|
38
|
+
backfill: null,
|
|
34
39
|
lifecycle: null,
|
|
35
40
|
taskComment: null,
|
|
36
41
|
verification: null,
|
|
42
|
+
summary: null,
|
|
37
43
|
completedSteps: [],
|
|
38
44
|
pendingSteps: [...FINALIZATION_STEPS],
|
|
39
45
|
result: error.retryable ? 'blocked' : 'failed',
|
|
@@ -52,6 +58,7 @@ function emptyReceipt(taskId, controlBinding) {
|
|
|
52
58
|
lifecycle: 'pending',
|
|
53
59
|
taskComment: 'pending',
|
|
54
60
|
verification: 'pending',
|
|
61
|
+
summary: 'pending',
|
|
55
62
|
warningProjection: 'done',
|
|
56
63
|
warnings: [],
|
|
57
64
|
...(controlBinding ? { controlBinding } : {}),
|
|
@@ -88,6 +95,7 @@ function validateReceipt(value, taskId) {
|
|
|
88
95
|
|| !states.includes(String(receipt.lifecycle))
|
|
89
96
|
|| !states.includes(String(receipt.taskComment))
|
|
90
97
|
|| !states.includes(String(receipt.verification))
|
|
98
|
+
|| !states.includes(String(receipt.summary))
|
|
91
99
|
|| !['pending', 'done'].includes(String(receipt.warningProjection))
|
|
92
100
|
|| !Array.isArray(receipt.warnings) || receipt.warnings.some((warning) => !validWarning(warning))
|
|
93
101
|
|| typeof receipt.updatedAt !== 'string'
|
|
@@ -109,10 +117,6 @@ function readReceipt(repoRoot, taskId) {
|
|
|
109
117
|
function readTaskFinalizationReceipt(repoRoot, taskId) {
|
|
110
118
|
return readReceipt(path.resolve(repoRoot), taskId);
|
|
111
119
|
}
|
|
112
|
-
function controlBindingMatches(receipt, binding) {
|
|
113
|
-
return receipt.controlBinding?.generation === binding.generation
|
|
114
|
-
&& receipt.controlBinding.requestId === binding.requestId;
|
|
115
|
-
}
|
|
116
120
|
function writeReceipt(repoRoot, receipt) {
|
|
117
121
|
const directory = finalizationRoot(repoRoot);
|
|
118
122
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
@@ -132,20 +136,22 @@ function writeReceipt(repoRoot, receipt) {
|
|
|
132
136
|
}
|
|
133
137
|
function updateReceipt(repoRoot, receipt, patch) {
|
|
134
138
|
const current = readReceipt(repoRoot, receipt.taskId);
|
|
135
|
-
if (!current || current.receiptId !== receipt.receiptId
|
|
136
|
-
|
|
137
|
-
Object.assign(error, { code: 'FINALIZATION_CAPABILITY_STALE' });
|
|
138
|
-
throw error;
|
|
139
|
-
}
|
|
139
|
+
if (!current || current.receiptId !== receipt.receiptId)
|
|
140
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization receipt does not belong to the current task');
|
|
140
141
|
const next = { ...current, ...patch, revision: current.revision + 1, updatedAt: now() };
|
|
141
142
|
writeReceipt(repoRoot, next);
|
|
142
143
|
return next;
|
|
143
144
|
}
|
|
144
145
|
function completedSteps(receipt) {
|
|
145
|
-
return FINALIZATION_STEPS.filter((step) => receipt
|
|
146
|
+
return FINALIZATION_STEPS.filter((step) => stepState(receipt, step) !== 'pending');
|
|
146
147
|
}
|
|
147
148
|
function pendingSteps(receipt) {
|
|
148
|
-
return FINALIZATION_STEPS.filter((step) => receipt
|
|
149
|
+
return FINALIZATION_STEPS.filter((step) => stepState(receipt, step) === 'pending');
|
|
150
|
+
}
|
|
151
|
+
function stepState(receipt, step) {
|
|
152
|
+
if (step === 'task-comment')
|
|
153
|
+
return receipt.taskComment;
|
|
154
|
+
return receipt[step];
|
|
149
155
|
}
|
|
150
156
|
function lifecycleStep(result) {
|
|
151
157
|
return {
|
|
@@ -234,9 +240,8 @@ function mutationKeys(value) {
|
|
|
234
240
|
return Object.keys(value).sort();
|
|
235
241
|
}
|
|
236
242
|
function validateCapabilityMutation(current, capability, mutation, consumed) {
|
|
237
|
-
if (capability.receiptId !== current.receiptId
|
|
238
|
-
throw capabilityError('
|
|
239
|
-
}
|
|
243
|
+
if (capability.receiptId !== current.receiptId)
|
|
244
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization mutation belongs to a different task receipt');
|
|
240
245
|
if (capability.scope !== mutation.scope) {
|
|
241
246
|
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability mutation scope does not match its capability');
|
|
242
247
|
}
|
|
@@ -403,9 +408,80 @@ async function syncPendingTaskComment(input) {
|
|
|
403
408
|
return { receipt, step: { status: 'blocked', changed: false, error: detail }, changed: false, error: detail };
|
|
404
409
|
}
|
|
405
410
|
}
|
|
411
|
+
function readDeliverySummary(taskDir, taskId) {
|
|
412
|
+
const file = path.join(taskDir, '.delivery-summary.json');
|
|
413
|
+
let value;
|
|
414
|
+
try {
|
|
415
|
+
value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
throw Object.assign(new Error('delivery summary staging record is unavailable'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
419
|
+
}
|
|
420
|
+
if (value.taskId !== taskId || typeof value.body !== 'string' || typeof value.sha256 !== 'string') {
|
|
421
|
+
throw Object.assign(new Error('delivery summary staging record is invalid'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
422
|
+
}
|
|
423
|
+
const canonical = canonicalizeSummaryBody(value.body);
|
|
424
|
+
if (!canonical.ok || canonical.value !== value.body) {
|
|
425
|
+
throw Object.assign(new Error('delivery summary staging body is not canonical'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
426
|
+
}
|
|
427
|
+
const digest = createHash('sha256').update(canonical.value).digest('hex');
|
|
428
|
+
if (digest !== value.sha256)
|
|
429
|
+
throw Object.assign(new Error('delivery summary staging digest does not match body'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
430
|
+
return { body: canonical.value, sha256: value.sha256 };
|
|
431
|
+
}
|
|
432
|
+
async function syncPendingSummary(input) {
|
|
433
|
+
let { receipt } = input;
|
|
434
|
+
try {
|
|
435
|
+
const resolved = resolveTaskRef(input.taskId, { repoRoot: input.repoRoot });
|
|
436
|
+
if (!resolved.ok || resolved.state !== 'completed')
|
|
437
|
+
throw Object.assign(new Error('completed task is unavailable for summary sync'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
438
|
+
if (!taskIssueIdentity(parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8')))) {
|
|
439
|
+
receipt = updateReceipt(input.repoRoot, receipt, {
|
|
440
|
+
summary: 'skipped', lastError: null
|
|
441
|
+
});
|
|
442
|
+
return { receipt, step: { status: 'skipped', changed: false, error: null }, changed: false, error: null };
|
|
443
|
+
}
|
|
444
|
+
const summary = readDeliverySummary(resolved.taskDir, input.taskId);
|
|
445
|
+
const result = await input.commentSync(input.taskId, {
|
|
446
|
+
kind: 'summary', agent: input.agent, body: summary.body, cwd: input.repoRoot,
|
|
447
|
+
summaryAuthorization: { sha256: summary.sha256 }
|
|
448
|
+
});
|
|
449
|
+
const step = commentStep(result);
|
|
450
|
+
if (result.status === 'applied' || result.status === 'no-op') {
|
|
451
|
+
const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
|
|
452
|
+
const resolvedWarning = receipt.warnings.some((warning) => warning.step === 'summary' && warning.status === 'open');
|
|
453
|
+
const warnings = resolveStepWarnings(receipt, 'summary');
|
|
454
|
+
receipt = updateReceipt(input.repoRoot, receipt, {
|
|
455
|
+
summary: skipped ? 'skipped' : 'done',
|
|
456
|
+
taskComment: resolvedWarning ? 'pending' : receipt.taskComment,
|
|
457
|
+
warningProjection: warnings.some((warning) => warning.status === 'open') ? 'pending' : 'done',
|
|
458
|
+
warnings,
|
|
459
|
+
lastError: null
|
|
460
|
+
});
|
|
461
|
+
return { receipt, step: skipped ? { ...step, status: 'skipped' } : step, changed: result.changed, error: null };
|
|
462
|
+
}
|
|
463
|
+
const error = step.error ?? { code: 'SUMMARY_SYNC_FAILED', message: 'delivery summary synchronization failed', retryable: true };
|
|
464
|
+
const warning = warningFromError('summary', error);
|
|
465
|
+
const alreadyOpen = receipt.warnings.some((item) => item.step === warning.step && item.code === warning.code && item.target === warning.target && item.status === 'open');
|
|
466
|
+
const warnings = replaceWarning(receipt, warning, 'open');
|
|
467
|
+
receipt = updateReceipt(input.repoRoot, receipt, { summary: 'pending', taskComment: alreadyOpen ? receipt.taskComment : 'pending', warningProjection: 'pending', warnings, lastError: error });
|
|
468
|
+
return { receipt, step, changed: result.changed, error };
|
|
469
|
+
}
|
|
470
|
+
catch (cause) {
|
|
471
|
+
const error = errorOf(cause, 'SUMMARY_SYNC_FAILED', true);
|
|
472
|
+
try {
|
|
473
|
+
const warning = warningFromError('summary', error);
|
|
474
|
+
const alreadyOpen = receipt.warnings.some((item) => item.step === warning.step && item.code === warning.code && item.target === warning.target && item.status === 'open');
|
|
475
|
+
const warnings = replaceWarning(receipt, warning, 'open');
|
|
476
|
+
receipt = updateReceipt(input.repoRoot, receipt, { summary: 'pending', taskComment: alreadyOpen ? receipt.taskComment : 'pending', warningProjection: 'pending', warnings, lastError: error });
|
|
477
|
+
}
|
|
478
|
+
catch { /* preserve primary error */ }
|
|
479
|
+
return { receipt, step: { status: 'blocked', changed: false, error }, changed: false, error };
|
|
480
|
+
}
|
|
481
|
+
}
|
|
406
482
|
function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
407
483
|
const pending = pendingSteps(receipt);
|
|
408
|
-
const blocked = [steps.lifecycle, steps.taskComment, steps.verification].some((step) => step?.status === 'blocked');
|
|
484
|
+
const blocked = [steps.backfill, steps.lifecycle, steps.taskComment, steps.verification, steps.summary].some((step) => step?.status === 'blocked');
|
|
409
485
|
const warnings = openWarnings(receipt);
|
|
410
486
|
const postLifecyclePending = receipt.lifecycle === 'done' && (pending.some((step) => step !== 'lifecycle') || receipt.warningProjection === 'pending');
|
|
411
487
|
const hardError = error?.code.startsWith('FINALIZATION_') || error?.code === 'TASK_FINALIZATION_RECEIPT_INVALID';
|
|
@@ -413,14 +489,18 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
413
489
|
status: hardError ? (error?.retryable ? 'blocked' : 'failed') : pending.length === 0 ? 'completed' : postLifecyclePending ? 'completed' : blocked ? 'blocked' : 'failed',
|
|
414
490
|
changed,
|
|
415
491
|
taskId,
|
|
416
|
-
|
|
492
|
+
backfill: steps.backfill ?? null,
|
|
493
|
+
lifecycle: steps.lifecycle ?? null,
|
|
494
|
+
taskComment: steps.taskComment ?? null,
|
|
495
|
+
verification: steps.verification ?? null,
|
|
496
|
+
summary: steps.summary ?? null,
|
|
417
497
|
completedSteps: completedSteps(receipt),
|
|
418
498
|
pendingSteps: pending,
|
|
419
|
-
result:
|
|
420
|
-
? '
|
|
421
|
-
:
|
|
422
|
-
?
|
|
423
|
-
: pending.length === 0
|
|
499
|
+
result: hardError
|
|
500
|
+
? (error?.retryable ? 'blocked' : 'failed')
|
|
501
|
+
: postLifecyclePending
|
|
502
|
+
? 'completed_with_warnings'
|
|
503
|
+
: pending.length === 0
|
|
424
504
|
? 'completed'
|
|
425
505
|
: blocked
|
|
426
506
|
? 'blocked'
|
|
@@ -432,6 +512,7 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
432
512
|
async function applyUnderLock(request, taskId, options) {
|
|
433
513
|
const repoRoot = path.resolve(options.repoRoot);
|
|
434
514
|
const lifecycle = options.lifecycle ?? applyTaskLifecycle;
|
|
515
|
+
const backfill = options.backfill ?? backfillCompletionComments;
|
|
435
516
|
const commentSync = options.commentSync ?? syncPlatformComment;
|
|
436
517
|
const verify = options.verify ?? verifyTaskEvent;
|
|
437
518
|
const consumedCapabilities = new Set();
|
|
@@ -446,17 +527,9 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
446
527
|
catch (error) {
|
|
447
528
|
return failed(taskId, errorOf(error, 'TASK_FINALIZATION_RECEIPT_INVALID'));
|
|
448
529
|
}
|
|
449
|
-
if (options.controlBinding &&
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
message: 'finalization receipt belongs to a different sandbox request or generation',
|
|
453
|
-
retryable: false
|
|
454
|
-
};
|
|
455
|
-
return failed(taskId, error, {
|
|
456
|
-
completedSteps: completedSteps(receipt),
|
|
457
|
-
pendingSteps: pendingSteps(receipt)
|
|
458
|
-
});
|
|
459
|
-
}
|
|
530
|
+
if (options.controlBinding && (receipt.controlBinding?.generation !== options.controlBinding.generation
|
|
531
|
+
|| receipt.controlBinding.requestId !== options.controlBinding.requestId))
|
|
532
|
+
receipt = updateReceipt(repoRoot, receipt, { controlBinding: options.controlBinding });
|
|
460
533
|
const preflightState = resolveTaskRef(taskId, { repoRoot });
|
|
461
534
|
if (options.preflight && preflightState.ok && preflightState.state === 'active') {
|
|
462
535
|
try {
|
|
@@ -476,8 +549,36 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
476
549
|
return failed(taskId, errorOf(error, 'TASK_FINALIZATION_PREFLIGHT_FAILED', true));
|
|
477
550
|
}
|
|
478
551
|
}
|
|
479
|
-
let lifecycleResult = null;
|
|
480
552
|
let changed = false;
|
|
553
|
+
let backfillResult = null;
|
|
554
|
+
try {
|
|
555
|
+
const result = await backfill(taskId, { agent: request.agent, cwd: repoRoot });
|
|
556
|
+
backfillResult = commentStep(result);
|
|
557
|
+
const issueNotLinked = result.status === 'no-op' && result.error?.code === 'ISSUE_NOT_LINKED';
|
|
558
|
+
if (!issueNotLinked && (result.status !== 'applied' && result.status !== 'no-op' || result.error)) {
|
|
559
|
+
const detail = backfillResult.error ?? {
|
|
560
|
+
code: 'COMPLETION_BACKFILL_FAILED', message: 'completion artifact backfill did not reach a successful terminal result', retryable: true
|
|
561
|
+
};
|
|
562
|
+
return failed(taskId, detail, {
|
|
563
|
+
backfill: backfillResult,
|
|
564
|
+
completedSteps: completedSteps(receipt),
|
|
565
|
+
pendingSteps: pendingSteps(receipt)
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
changed = result.changed;
|
|
569
|
+
if (result.changed && receipt.taskComment !== 'pending') {
|
|
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
|
+
}
|
|
581
|
+
let lifecycleResult = null;
|
|
481
582
|
try {
|
|
482
583
|
const resolved = resolveTaskRef(taskId, { repoRoot });
|
|
483
584
|
const registry = resolved.ok && resolved.state === 'completed' ? inspectShortIdRegistry(repoRoot) : null;
|
|
@@ -489,6 +590,7 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
489
590
|
};
|
|
490
591
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
|
|
491
592
|
return terminalResult(taskId, receipt, {
|
|
593
|
+
backfill: backfillResult,
|
|
492
594
|
lifecycle: { status: 'failed', changed: false, error: detail },
|
|
493
595
|
taskComment: null,
|
|
494
596
|
verification: null
|
|
@@ -503,6 +605,7 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
503
605
|
};
|
|
504
606
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
|
|
505
607
|
return terminalResult(taskId, receipt, {
|
|
608
|
+
backfill: backfillResult,
|
|
506
609
|
lifecycle: { status: 'failed', changed: false, error: detail },
|
|
507
610
|
taskComment: null,
|
|
508
611
|
verification: null
|
|
@@ -518,9 +621,9 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
518
621
|
lifecycleResult = lifecycleStep(result);
|
|
519
622
|
if (result.status !== 'applied' && result.status !== 'no-op') {
|
|
520
623
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: lifecycleResult.error });
|
|
521
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment: null, verification: null }, result.changed, lifecycleResult.error);
|
|
624
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment: null, verification: null }, result.changed, lifecycleResult.error);
|
|
522
625
|
}
|
|
523
|
-
changed = result.changed;
|
|
626
|
+
changed = changed || result.changed;
|
|
524
627
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
|
|
525
628
|
}
|
|
526
629
|
}
|
|
@@ -530,7 +633,7 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
530
633
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
|
|
531
634
|
}
|
|
532
635
|
catch { /* preserve the primary error */ }
|
|
533
|
-
return failed(taskId, detail, { lifecycle: lifecycleResult });
|
|
636
|
+
return failed(taskId, detail, { backfill: backfillResult, lifecycle: lifecycleResult });
|
|
534
637
|
}
|
|
535
638
|
let taskComment = null;
|
|
536
639
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
@@ -541,18 +644,16 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
541
644
|
taskComment = initialComment.step;
|
|
542
645
|
changed = changed || initialComment.changed;
|
|
543
646
|
if (initialComment.error) {
|
|
544
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: null }, changed, initialComment.error);
|
|
647
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: null }, changed, initialComment.error);
|
|
545
648
|
}
|
|
546
649
|
let verification = null;
|
|
547
650
|
try {
|
|
548
651
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
verification = verificationStep(result);
|
|
555
|
-
if (result.status === 'pass') {
|
|
652
|
+
const verificationPending = receipt.verification === 'pending';
|
|
653
|
+
const result = await verify({ taskRef: taskId, event: 'complete-task.completed' }, { repoRoot });
|
|
654
|
+
verification = verificationStep(result);
|
|
655
|
+
if (result.status === 'pass') {
|
|
656
|
+
if (verificationPending) {
|
|
556
657
|
const capability = issueCapability(receipt, 'verification');
|
|
557
658
|
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
558
659
|
scope: 'verification', operation: 'succeeded'
|
|
@@ -566,34 +667,84 @@ async function applyUnderLock(request, taskId, options) {
|
|
|
566
667
|
taskComment = finalComment.step;
|
|
567
668
|
changed = changed || finalComment.changed;
|
|
568
669
|
if (finalComment.error) {
|
|
569
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
|
|
670
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
|
|
570
671
|
}
|
|
571
672
|
}
|
|
572
673
|
else {
|
|
573
|
-
|
|
674
|
+
receipt = updateReceipt(repoRoot, receipt, { lastError: null });
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
|
|
679
|
+
if (verificationPending) {
|
|
574
680
|
const capability = issueCapability(receipt, 'verification');
|
|
575
681
|
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
576
682
|
scope: 'verification', operation: 'failed', error: detail
|
|
577
683
|
}, consumedCapabilities);
|
|
578
684
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
579
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, verification.error);
|
|
580
685
|
}
|
|
686
|
+
else {
|
|
687
|
+
const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
|
|
688
|
+
receipt = updateReceipt(repoRoot, receipt, {
|
|
689
|
+
verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
|
|
690
|
+
});
|
|
691
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
692
|
+
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
|
|
693
|
+
receipt = warningComment.receipt;
|
|
694
|
+
if (warningComment.step.status !== 'no-op')
|
|
695
|
+
taskComment = warningComment.step;
|
|
696
|
+
changed = changed || warningComment.changed;
|
|
697
|
+
}
|
|
698
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, detail);
|
|
581
699
|
}
|
|
582
700
|
}
|
|
583
701
|
catch (error) {
|
|
584
702
|
const detail = errorOf(error, 'VERIFY_FAILED', true);
|
|
585
703
|
try {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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 {
|
|
711
|
+
const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
|
|
712
|
+
receipt = updateReceipt(repoRoot, receipt, {
|
|
713
|
+
verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
|
|
714
|
+
});
|
|
715
|
+
}
|
|
590
716
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
591
717
|
}
|
|
718
|
+
catch (writeError) {
|
|
719
|
+
const persistence = errorOf(writeError, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
|
|
720
|
+
const hardError = {
|
|
721
|
+
code: 'FINALIZATION_RECEIPT_WRITE_FAILED',
|
|
722
|
+
message: `Unable to persist verification failure (${detail.code}: ${detail.message}): ${persistence.message}`,
|
|
723
|
+
retryable: true
|
|
724
|
+
};
|
|
725
|
+
return terminalResult(taskId, receipt, {
|
|
726
|
+
backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail }
|
|
727
|
+
}, changed, hardError);
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
|
|
731
|
+
receipt = warningComment.receipt;
|
|
732
|
+
if (warningComment.step.status !== 'no-op')
|
|
733
|
+
taskComment = warningComment.step;
|
|
734
|
+
changed = changed || warningComment.changed;
|
|
735
|
+
}
|
|
592
736
|
catch { /* preserve the primary error */ }
|
|
593
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
|
|
737
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
|
|
738
|
+
}
|
|
739
|
+
let summary = await syncPendingSummary({ repoRoot, taskId, agent: request.agent, receipt, commentSync });
|
|
740
|
+
receipt = summary.receipt;
|
|
741
|
+
changed = changed || summary.changed;
|
|
742
|
+
if (summary.error) {
|
|
743
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
744
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification, summary: summary.step }, changed, summary.error);
|
|
594
745
|
}
|
|
595
746
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
596
|
-
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed);
|
|
747
|
+
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification, summary: summary.step }, changed);
|
|
597
748
|
}
|
|
598
749
|
async function applyTaskFinalization(request, options) {
|
|
599
750
|
if (request.intent !== 'complete' || !request.taskRef || !request.agent) {
|
|
@@ -613,5 +764,5 @@ async function applyTaskFinalization(request, options) {
|
|
|
613
764
|
return failed(resolved.taskId, detail);
|
|
614
765
|
}
|
|
615
766
|
}
|
|
616
|
-
export { applyFinalizationReceiptMutation, applyTaskFinalization, issueCapability as createFinalizationCapability, readTaskFinalizationReceipt };
|
|
767
|
+
export { applyFinalizationReceiptMutation, applyTaskFinalization, issueCapability as createFinalizationCapability, readTaskFinalizationReceipt, terminalResult };
|
|
617
768
|
//# sourceMappingURL=finalization.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { inspectOrchestrationStage, pauseOrchestration, planOrchestrationStageCompletion
|
|
1
|
+
import { inspectOrchestrationStage, pauseOrchestration, planOrchestrationStageCompletion } from './orchestration.js';
|
|
2
2
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
3
3
|
function failure(mode, code, message) {
|
|
4
4
|
return { ok: false, mode, completionPlan: null, error: { code, message } };
|
|
@@ -8,16 +8,7 @@ function inspectLifecycleExecution(taskRef, request, options = {}) {
|
|
|
8
8
|
if (!resolved.ok)
|
|
9
9
|
return failure(request.mode, resolved.code, resolved.message);
|
|
10
10
|
if (request.mode === 'standalone') {
|
|
11
|
-
|
|
12
|
-
const run = readRun(resolved.taskDir);
|
|
13
|
-
if (run?.pendingDelegation) {
|
|
14
|
-
return failure(request.mode, 'ORCHESTRATION_STANDALONE_BUSY', 'standalone lifecycle execution is blocked by a pending orchestration delegation');
|
|
15
|
-
}
|
|
16
|
-
return { ok: true, mode: request.mode, completionPlan: null, error: null };
|
|
17
|
-
}
|
|
18
|
-
catch (error) {
|
|
19
|
-
return failure(request.mode, 'ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error));
|
|
20
|
-
}
|
|
11
|
+
return { ok: true, mode: request.mode, completionPlan: null, error: null };
|
|
21
12
|
}
|
|
22
13
|
if (request.agent) {
|
|
23
14
|
const planned = planOrchestrationStageCompletion(taskRef, {
|
|
@@ -151,7 +151,7 @@ function actionAndNote(request, restoredFiles = 0) {
|
|
|
151
151
|
if (request.intent === 'close-dependabot')
|
|
152
152
|
return { action: 'Close Dependabot', note: `Dependabot alert #${request.alertNumber} dismissed: ${request.reason}` };
|
|
153
153
|
if (request.intent === 'recover-started')
|
|
154
|
-
return { action: 'Recover Started', note:
|
|
154
|
+
return { action: 'Recover Started', note: 'automatic recovery' };
|
|
155
155
|
return { action: 'Restore Task', note: `Restored ${restoredFiles} files from Issue #${request.issueNumber}` };
|
|
156
156
|
}
|
|
157
157
|
function matchingCompletion(content, request, restoredFiles = 0) {
|