@fitlab-ai/agent-infra 0.9.15 → 0.10.0
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/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/fs/durable-write.js +2 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +17 -8
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +0 -1
- package/dist/lib/internal/task-review.js +4 -4
- package/dist/lib/platform/issue-comments.js +16 -10
- package/dist/lib/platform/issues.js +1 -1
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-summary.js +64 -32
- package/dist/lib/platform/pull-requests.js +3 -3
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +6 -2
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/sandbox/commands/rm.js +21 -2
- package/dist/lib/sandbox/control/server.js +29 -9
- package/dist/lib/sandbox/control/state.js +7 -1
- package/dist/lib/sandbox/control/workflow-executor.js +4 -14
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/removal.js +116 -1011
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/task/artifact-command.js +6 -28
- package/dist/lib/task/artifact-operations.js +16 -230
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +73 -39
- package/dist/lib/task/commit-operation.js +31 -4
- package/dist/lib/task/control-authority.js +38 -7
- package/dist/lib/task/control-recovery.js +14 -2
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +164 -22
- package/dist/lib/task/human-override.js +5 -8
- package/dist/lib/task/intermediate-cleanup.js +66 -8
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +12 -3
- package/dist/lib/task/local-artifact-finalization.js +148 -123
- package/dist/lib/task/manual-validation-receipt.js +5 -1
- package/dist/lib/task/orchestration.js +52 -2
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-command.js +1 -0
- package/dist/lib/task/review-finalization.js +104 -77
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +3 -14
- package/dist/lib/task/workflow-command.js +0 -1
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +1 -0
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +17 -8
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +0 -1
- package/lib/internal/task-review.ts +4 -4
- package/lib/platform/issue-comments.ts +18 -10
- package/lib/platform/issues.ts +1 -1
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-summary.ts +64 -32
- package/lib/platform/pull-requests.ts +3 -3
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +6 -2
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/sandbox/commands/rm.ts +34 -2
- package/lib/sandbox/control/protocol.ts +5 -0
- package/lib/sandbox/control/server.ts +30 -10
- package/lib/sandbox/control/state.ts +9 -1
- package/lib/sandbox/control/workflow-executor.ts +4 -14
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/removal.ts +126 -1128
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/task/artifact-command.ts +8 -27
- package/lib/task/artifact-operations.ts +21 -251
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +99 -51
- package/lib/task/commit-operation.ts +35 -4
- package/lib/task/control-authority.ts +47 -14
- package/lib/task/control-recovery.ts +13 -1
- package/lib/task/create-service.ts +1 -10
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +187 -23
- package/lib/task/human-override.ts +5 -8
- package/lib/task/intermediate-cleanup.ts +50 -12
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +13 -4
- package/lib/task/local-artifact-finalization.ts +176 -132
- package/lib/task/manual-validation-receipt.ts +5 -1
- package/lib/task/orchestration.ts +65 -0
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-command.ts +1 -0
- package/lib/task/review-finalization.ts +106 -87
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +3 -17
- package/lib/task/verification-types.ts +0 -2
- package/lib/task/workflow-command.ts +0 -1
- package/lib/version.ts +1 -15
- package/package.json +1 -1
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.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/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- 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/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- 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-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- 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/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
4
2
|
import { parseArtifactName } from './artifact-name.js';
|
|
5
3
|
import { validateCompletedArtifact, hasOpenArtifactRound, validateArtifactPublication } from './artifact-lifecycle.js';
|
|
6
4
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
@@ -12,18 +10,15 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
|
|
|
12
10
|
import { allowsManualOverride } from './guard-override.js';
|
|
13
11
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
14
12
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
15
|
-
import {
|
|
13
|
+
import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
14
|
+
import { beginArtifactRecovery, commitArtifactRecovery, prepareArtifactRecoveryCommit, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
|
|
16
15
|
const STAGES = {
|
|
17
16
|
analysis: { family: 'review-analysis' },
|
|
18
17
|
plan: { family: 'review-plan' },
|
|
19
18
|
code: { family: 'review-code' }
|
|
20
19
|
};
|
|
21
20
|
const DEFAULT_FILE_SYSTEM = {
|
|
22
|
-
readFileSync: (file) => fs.readFileSync(file, 'utf8')
|
|
23
|
-
statModeSync: (file) => fs.statSync(file).mode,
|
|
24
|
-
writeFileSync: (file, content, mode) => fs.writeFileSync(file, content, { encoding: 'utf8', flag: 'wx', mode }),
|
|
25
|
-
renameSync: (from, to) => fs.renameSync(from, to),
|
|
26
|
-
unlinkSync: (file) => fs.unlinkSync(file)
|
|
21
|
+
readFileSync: (file) => fs.readFileSync(file, 'utf8')
|
|
27
22
|
};
|
|
28
23
|
function failed(request, code, message, taskId = null, stageStatus = null, extra = {}) {
|
|
29
24
|
return {
|
|
@@ -37,21 +32,11 @@ function failed(request, code, message, taskId = null, stageStatus = null, extra
|
|
|
37
32
|
stageStatus,
|
|
38
33
|
artifactSha256: null,
|
|
39
34
|
semanticDigest: null,
|
|
40
|
-
repairable: false,
|
|
41
|
-
operation: null,
|
|
42
35
|
operations: [],
|
|
43
36
|
error: { code, message },
|
|
44
37
|
...extra
|
|
45
38
|
};
|
|
46
39
|
}
|
|
47
|
-
function cleanupTemp(fileSystem, tempPath) {
|
|
48
|
-
try {
|
|
49
|
-
fileSystem.unlinkSync(tempPath);
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
// Best-effort cleanup preserves the primary failure.
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
40
|
/** Prepare domain validation, summary bytes and provenance before any publication. */
|
|
56
41
|
function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
57
42
|
let taskId = null;
|
|
@@ -85,43 +70,37 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
85
70
|
catch (error) {
|
|
86
71
|
return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error));
|
|
87
72
|
}
|
|
73
|
+
let recovery;
|
|
74
|
+
if (request.recoveryId) {
|
|
75
|
+
try {
|
|
76
|
+
const intent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
|
|
77
|
+
if (!intent || intent.recoveryOperationId !== request.recoveryId)
|
|
78
|
+
return reject('REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal');
|
|
79
|
+
if (!['awaiting-recovery', 'finalize-ready', 'commit-started'].includes(intent.state))
|
|
80
|
+
return reject('REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`);
|
|
81
|
+
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
82
|
+
artifactContent = fs.existsSync(recovery.stagingPath)
|
|
83
|
+
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
84
|
+
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return reject('REVIEW_PROVENANCE_INVALID', String(error));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
88
90
|
const artifactSha256 = sha256Content(artifactContent);
|
|
89
91
|
const semanticDigest = canonicalSemanticDigest(artifactContent);
|
|
90
92
|
const digests = { artifactSha256, semanticDigest };
|
|
91
93
|
let repairIntent;
|
|
92
94
|
try {
|
|
93
|
-
repairIntent =
|
|
95
|
+
repairIntent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
|
|
94
96
|
}
|
|
95
97
|
catch (error) {
|
|
96
98
|
return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
|
|
97
99
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const repairable = structure.repair !== null && structure.diagnostics.length === 1 && structure.diagnostics[0]?.repairable === true;
|
|
102
|
-
if (repairable && repairIntent && (repairIntent.state !== 'awaiting-repair'
|
|
103
|
-
|| repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== structure.semanticDigest)) {
|
|
104
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', digests);
|
|
100
|
+
if (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed') {
|
|
101
|
+
if (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest) {
|
|
102
|
+
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
|
|
105
103
|
}
|
|
106
|
-
return {
|
|
107
|
-
...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
|
|
108
|
-
artifactSha256, semanticDigest: structure.semanticDigest, repairable, operation: structure.repair
|
|
109
|
-
}),
|
|
110
|
-
...(repairable && !repairIntent ? { provenance: {
|
|
111
|
-
version: 2, taskId: taskId, family: spec.family, artifact: request.artifact,
|
|
112
|
-
state: 'awaiting-repair', baselineSemanticDigest: structure.semanticDigest,
|
|
113
|
-
artifactSha256, semanticDigest: structure.semanticDigest,
|
|
114
|
-
recoveryOperationId: null, phase: null, authorityDigest: null,
|
|
115
|
-
requestId: `review-finalize:${taskId}`, createdAt: Date.now(), updatedAt: Date.now()
|
|
116
|
-
}, expectedIntent: null } : {})
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== semanticDigest) {
|
|
120
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', digests);
|
|
121
|
-
}
|
|
122
|
-
if ((repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')
|
|
123
|
-
&& (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== semanticDigest)) {
|
|
124
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
|
|
125
104
|
}
|
|
126
105
|
if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
|
|
127
106
|
return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
|
|
@@ -149,10 +128,62 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
149
128
|
const detail = inspectDecisionDetailDuplicates(artifactContent);
|
|
150
129
|
if (!detail.ok)
|
|
151
130
|
return reject('REVIEW_DECISION_DETAIL_INVALID', `${detail.code}: ${detail.message}`);
|
|
131
|
+
const schema = getArtifactSchema(spec.family);
|
|
132
|
+
const structure = inspectArtifactContract(artifactContent, schema);
|
|
133
|
+
if (!structure.ok) {
|
|
134
|
+
if (!recovery && !request.dryRun && options.startRecovery) {
|
|
135
|
+
try {
|
|
136
|
+
recovery = beginArtifactRecovery({
|
|
137
|
+
taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
138
|
+
requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
|
|
139
|
+
}, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
|
|
147
|
+
artifactSha256, semanticDigest: structure.semanticDigest,
|
|
148
|
+
...(recovery ? { recovery: {
|
|
149
|
+
recoveryId: recovery.recoveryId,
|
|
150
|
+
candidatePath: recovery.stagingPath,
|
|
151
|
+
baselineSha256: recovery.baselineSha256,
|
|
152
|
+
baselineSemanticDigest: recovery.baselineSemanticDigest
|
|
153
|
+
} } : {})
|
|
154
|
+
}),
|
|
155
|
+
...(recovery ? { recovery } : {})
|
|
156
|
+
};
|
|
157
|
+
}
|
|
152
158
|
const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
|
|
153
159
|
if (!transformed.ok)
|
|
154
160
|
return reject(transformed.code, transformed.message);
|
|
155
161
|
const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
|
|
162
|
+
if (!recovery && (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')) {
|
|
163
|
+
if (transformed.changed)
|
|
164
|
+
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact requires changes after its completed recovery was consumed', finalDigests);
|
|
165
|
+
return {
|
|
166
|
+
content: transformed.content,
|
|
167
|
+
result: {
|
|
168
|
+
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
|
|
169
|
+
status: 'no-op',
|
|
170
|
+
changed: false,
|
|
171
|
+
error: null
|
|
172
|
+
},
|
|
173
|
+
lockAlreadyHeld: options.lockAlreadyHeld
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (!recovery && !request.dryRun && options.startRecovery) {
|
|
177
|
+
try {
|
|
178
|
+
recovery = beginArtifactRecovery({
|
|
179
|
+
taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
180
|
+
requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
|
|
181
|
+
}, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
return reject('REVIEW_PROVENANCE_INVALID', String(error), finalDigests);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
156
187
|
return {
|
|
157
188
|
content: transformed.content,
|
|
158
189
|
result: {
|
|
@@ -162,22 +193,38 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
162
193
|
operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
|
|
163
194
|
error: null
|
|
164
195
|
},
|
|
165
|
-
...(
|
|
166
|
-
|
|
167
|
-
expectedIntent: repairIntent
|
|
168
|
-
} : {})
|
|
196
|
+
...(recovery ? { recovery } : {}),
|
|
197
|
+
lockAlreadyHeld: options.lockAlreadyHeld
|
|
169
198
|
};
|
|
170
199
|
}
|
|
171
200
|
function commitReviewSummaryProvenance(prepared, repoRoot) {
|
|
201
|
+
if (prepared.result.status === 'failed' || !prepared.recovery)
|
|
202
|
+
return prepared.result;
|
|
172
203
|
try {
|
|
173
|
-
|
|
174
|
-
|
|
204
|
+
const intent = readArtifactRecoveryIntent(prepared.recovery.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
|
|
205
|
+
if (!intent)
|
|
206
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
|
|
207
|
+
let committed;
|
|
208
|
+
if (intent.state === 'finalize-ready' || intent.state === 'commit-started') {
|
|
209
|
+
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
175
210
|
}
|
|
176
|
-
|
|
211
|
+
else {
|
|
212
|
+
const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
213
|
+
prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
214
|
+
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
215
|
+
}
|
|
216
|
+
if (committed.state !== 'passed')
|
|
217
|
+
throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed.state}'`);
|
|
218
|
+
return {
|
|
219
|
+
...prepared.result,
|
|
220
|
+
artifactSha256: committed.finalArtifactSha256,
|
|
221
|
+
semanticDigest: committed.finalSemanticDigest,
|
|
222
|
+
error: null
|
|
223
|
+
};
|
|
177
224
|
}
|
|
178
225
|
catch (error) {
|
|
179
226
|
return { ...prepared.result, status: 'failed', error: {
|
|
180
|
-
code: '
|
|
227
|
+
code: 'REVIEW_RECOVERY_COMMIT_FAILED', message: `cannot publish review candidate: ${String(error)}`
|
|
181
228
|
} };
|
|
182
229
|
}
|
|
183
230
|
}
|
|
@@ -203,37 +250,17 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
|
|
|
203
250
|
const prepared = prepareReviewSummaryCandidate(request, content, options);
|
|
204
251
|
if (request.dryRun)
|
|
205
252
|
return prepared.result;
|
|
206
|
-
if (prepared.result.status !== 'applied')
|
|
207
|
-
return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
|
|
208
|
-
const tempPath = path.join(resolved.taskDir, `.${request.artifact}.tmp-${process.pid}-${(options.randomSuffix ?? randomUUID)()}`);
|
|
209
|
-
try {
|
|
210
|
-
fileSystem.writeFileSync(tempPath, prepared.content, fileSystem.statModeSync(validated.artifact.path));
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
cleanupTemp(fileSystem, tempPath);
|
|
214
|
-
return failed(request, 'REVIEW_TEMP_WRITE_FAILED', String(error), resolved.taskId, prepared.result.stageStatus);
|
|
215
|
-
}
|
|
216
|
-
try {
|
|
217
|
-
if (fileSystem.readFileSync(validated.artifact.path) !== content) {
|
|
218
|
-
cleanupTemp(fileSystem, tempPath);
|
|
219
|
-
return failed(request, 'REVIEW_ARTIFACT_CONFLICT', `${request.artifact} changed during finalization`, resolved.taskId, prepared.result.stageStatus);
|
|
220
|
-
}
|
|
221
|
-
fileSystem.renameSync(tempPath, validated.artifact.path);
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
cleanupTemp(fileSystem, tempPath);
|
|
225
|
-
return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, prepared.result.stageStatus);
|
|
226
|
-
}
|
|
227
253
|
return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
|
|
228
254
|
}
|
|
229
255
|
function finalizeReviewSummary(request, options = {}) {
|
|
256
|
+
const effectiveOptions = { ...options, startRecovery: !request.dryRun };
|
|
230
257
|
if (request.dryRun || options.lockAlreadyHeld)
|
|
231
|
-
return finalizeReviewSummaryUnlocked(request,
|
|
258
|
+
return finalizeReviewSummaryUnlocked(request, effectiveOptions);
|
|
232
259
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
233
260
|
if (!resolved.ok)
|
|
234
|
-
return finalizeReviewSummaryUnlocked(request,
|
|
261
|
+
return finalizeReviewSummaryUnlocked(request, effectiveOptions);
|
|
235
262
|
try {
|
|
236
|
-
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', () => finalizeReviewSummaryUnlocked(request,
|
|
263
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', () => finalizeReviewSummaryUnlocked(request, { ...effectiveOptions, lockAlreadyHeld: true }));
|
|
237
264
|
}
|
|
238
265
|
catch (error) {
|
|
239
266
|
if (!(error instanceof TaskExecutionLockError))
|
|
@@ -199,14 +199,16 @@ function mutateShortIdRegistry(repoRoot, taskId, requested) {
|
|
|
199
199
|
const activeDir = path.join(repoRoot, '.agents', 'workspace', 'active');
|
|
200
200
|
return mutateShortIdRegistryAt(activeDir, configuredShortIdLength(repoRoot), taskId, requested);
|
|
201
201
|
}
|
|
202
|
-
function mutateShortIdRegistryAt(activeDir, width, taskId, requested) {
|
|
202
|
+
function mutateShortIdRegistryAt(activeDir, width, taskId, requested, onlyReleaseWhenInactive = false, pruneOrphans = true) {
|
|
203
203
|
const registryPath = path.join(activeDir, REGISTRY_NAME);
|
|
204
204
|
return withRegistryLock(activeDir, () => {
|
|
205
205
|
const registry = readRegistryStrict(registryPath, width);
|
|
206
206
|
const original = JSON.stringify(registry.ids);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
if (pruneOrphans) {
|
|
208
|
+
for (const [key, candidate] of Object.entries(registry.ids)) {
|
|
209
|
+
if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md')))
|
|
210
|
+
delete registry.ids[key];
|
|
211
|
+
}
|
|
210
212
|
}
|
|
211
213
|
const existing = Object.entries(registry.ids).find(([, candidate]) => candidate === taskId);
|
|
212
214
|
let result;
|
|
@@ -232,8 +234,10 @@ function mutateShortIdRegistryAt(activeDir, width, taskId, requested) {
|
|
|
232
234
|
}
|
|
233
235
|
}
|
|
234
236
|
else if (requested === 'release') {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
+
const taskIsActive = fs.existsSync(path.join(activeDir, taskId, 'task.md'));
|
|
238
|
+
if (!existing || (onlyReleaseWhenInactive && taskIsActive)) {
|
|
239
|
+
result = { effect: 'unchanged', shortId: existing?.[0] ?? null, changed: false };
|
|
240
|
+
}
|
|
237
241
|
else {
|
|
238
242
|
delete registry.ids[existing[0]];
|
|
239
243
|
result = { effect: 'released', shortId: existing[0], changed: true };
|
|
@@ -247,6 +251,10 @@ function mutateShortIdRegistryAt(activeDir, width, taskId, requested) {
|
|
|
247
251
|
return { ...result, changed };
|
|
248
252
|
});
|
|
249
253
|
}
|
|
254
|
+
function releaseStaleShortIdRegistry(repoRoot, taskId) {
|
|
255
|
+
const activeDir = path.join(repoRoot, '.agents', 'workspace', 'active');
|
|
256
|
+
return mutateShortIdRegistryAt(activeDir, configuredShortIdLength(repoRoot), taskId, 'release', true, false);
|
|
257
|
+
}
|
|
250
258
|
function executeShortIdCommand(request) {
|
|
251
259
|
try {
|
|
252
260
|
if (!Number.isInteger(request.shortIdLength) || request.shortIdLength < 1) {
|
|
@@ -429,5 +437,5 @@ function lookupShortIdByBranch(branch, repoRoot, _opts) {
|
|
|
429
437
|
}
|
|
430
438
|
return matches[0];
|
|
431
439
|
}
|
|
432
|
-
export { normalizeShortIdInput, isRemovedHashShortIdInput, resolveShortIdReadOnly, lookupShortIdByBranch, loadShortIdByTaskId, inspectShortIdRegistry, configuredShortIdLength, mutateShortIdRegistry, executeShortIdCommand };
|
|
440
|
+
export { normalizeShortIdInput, isRemovedHashShortIdInput, resolveShortIdReadOnly, lookupShortIdByBranch, loadShortIdByTaskId, inspectShortIdRegistry, configuredShortIdLength, mutateShortIdRegistry, releaseStaleShortIdRegistry, executeShortIdCommand };
|
|
433
441
|
//# sourceMappingURL=short-id.js.map
|
|
@@ -231,7 +231,7 @@ function withTaskExecutionLock(repoRoot, taskId, ownerName, callback, options =
|
|
|
231
231
|
catch (error) {
|
|
232
232
|
if (error instanceof TaskExecutionLockError)
|
|
233
233
|
throw error;
|
|
234
|
-
if (error instanceof Error && error.name
|
|
234
|
+
if (error instanceof Error && ['OrchestrationStateError', 'ArtifactRecoveryError'].includes(error.name))
|
|
235
235
|
throw error;
|
|
236
236
|
throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'lifecycle task lock operation failed', { operation: ownsFixed ? 'callback' : 'acquire', errno: errno(error), key });
|
|
237
237
|
}
|
|
@@ -33,6 +33,7 @@ import { manualValidationFinalSummaryProjectionMatches } from "./manual-validati
|
|
|
33
33
|
import { readManualValidationCompletion } from "./manual-validation-completion.js";
|
|
34
34
|
import { sha256File } from "./artifact-receipts.js";
|
|
35
35
|
import { summaryCommentState } from "../platform/pr-summary.js";
|
|
36
|
+
import { taskIssueIdentity } from "../platform/task-identities.js";
|
|
36
37
|
const TASK_ENUMS = {
|
|
37
38
|
type: ["feature", "bugfix", "refactor", "docs", "chore"],
|
|
38
39
|
workflow: ["feature-development", "bug-fix", "refactoring"],
|
|
@@ -78,8 +79,6 @@ const sharedUtils = {
|
|
|
78
79
|
failResult,
|
|
79
80
|
blockedResult,
|
|
80
81
|
safeStat,
|
|
81
|
-
parseIssueNumber,
|
|
82
|
-
parsePrNumber
|
|
83
82
|
};
|
|
84
83
|
async function runCheck(type, context, shared) {
|
|
85
84
|
switch (type) {
|
|
@@ -314,8 +313,8 @@ function checkTaskMeta({ taskDir, config, repositoryRoot }) {
|
|
|
314
313
|
if (expectedStatus && metadata.status !== expectedStatus) {
|
|
315
314
|
return failResult("task-meta", `Expected status '${expectedStatus}', got '${metadata.status || "(empty)"}'`);
|
|
316
315
|
}
|
|
317
|
-
if (config.
|
|
318
|
-
return failResult("task-meta", "Expected a valid
|
|
316
|
+
if (config.require_platform_issue_identity && !taskIssueIdentity(metadata)) {
|
|
317
|
+
return failResult("task-meta", "Expected a valid platform_issue_identity in task metadata");
|
|
319
318
|
}
|
|
320
319
|
if (config.require_completed_at && isBlank(metadata.completed_at)) {
|
|
321
320
|
return failResult("task-meta", "Expected completed_at to be present");
|
|
@@ -1185,16 +1184,6 @@ function getCheckedRequirements(content) {
|
|
|
1185
1184
|
.filter(Boolean)
|
|
1186
1185
|
.map((match) => match[1].trim());
|
|
1187
1186
|
}
|
|
1188
|
-
function parseIssueNumber(value) {
|
|
1189
|
-
if (isBlank(value) || value === "N/A") {
|
|
1190
|
-
return null;
|
|
1191
|
-
}
|
|
1192
|
-
const match = String(value).match(/\d+/);
|
|
1193
|
-
return match ? Number(match[0]) : null;
|
|
1194
|
-
}
|
|
1195
|
-
function parsePrNumber(value) {
|
|
1196
|
-
return parseIssueNumber(value);
|
|
1197
|
-
}
|
|
1198
1187
|
// === Utilities ===
|
|
1199
1188
|
function normalizeContent(text) {
|
|
1200
1189
|
return String(text || "")
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export const TASK_WORKFLOW_COMMANDS = Object.freeze({
|
|
2
2
|
'artifact-inspect': ['task-artifact', 'inspect'],
|
|
3
3
|
'artifact-init': ['task-artifact', 'init'],
|
|
4
|
-
'artifact-repair': ['task-artifact', 'repair'],
|
|
5
4
|
'artifact-finalize-local': ['task-artifact', 'finalize-local'],
|
|
6
5
|
'review-finalize-summary': ['task-review', 'finalize-summary'],
|
|
7
6
|
event: ['task-event', null],
|
package/dist/lib/version.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import semver from 'semver';
|
|
3
2
|
const packageJsonUrl = [
|
|
4
3
|
new URL('../package.json', import.meta.url),
|
|
5
4
|
new URL('../../package.json', import.meta.url),
|
|
@@ -10,17 +9,8 @@ if (!packageJsonUrl) {
|
|
|
10
9
|
const { version } = JSON.parse(readFileSync(packageJsonUrl, 'utf8'));
|
|
11
10
|
const VERSION = `v${version}`;
|
|
12
11
|
const AGENT_INFRA_VERSION_PATTERN = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
13
|
-
const LEGACY_COMPATIBILITY_CUTOFF_VERSION = '1.0.0';
|
|
14
12
|
function isValidAgentInfraVersion(value) {
|
|
15
13
|
return typeof value === 'string' && AGENT_INFRA_VERSION_PATTERN.test(value);
|
|
16
14
|
}
|
|
17
|
-
|
|
18
|
-
const normalized = runtimeVersion.startsWith('v') ? runtimeVersion.slice(1) : runtimeVersion;
|
|
19
|
-
const parsed = semver.valid(normalized);
|
|
20
|
-
return parsed !== null && semver.lt(parsed, LEGACY_COMPATIBILITY_CUTOFF_VERSION);
|
|
21
|
-
}
|
|
22
|
-
function legacyCompatibilityError(resource, runtimeVersion = VERSION) {
|
|
23
|
-
return Object.assign(new Error(`PLATFORM_IDENTITY_LEGACY_UNSUPPORTED: ${resource} requires migration to the current schema before v${LEGACY_COMPATIBILITY_CUTOFF_VERSION} runtime ${runtimeVersion}`), { code: 'PLATFORM_IDENTITY_LEGACY_UNSUPPORTED' });
|
|
24
|
-
}
|
|
25
|
-
export { LEGACY_COMPATIBILITY_CUTOFF_VERSION, VERSION, isLegacyCompatibilityEnabled, isValidAgentInfraVersion, legacyCompatibilityError };
|
|
15
|
+
export { VERSION, isValidAgentInfraVersion };
|
|
26
16
|
//# sourceMappingURL=version.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.10.0",
|
|
4
|
+
"sourceInputHash": "d7af8de4bd6c1996a920335dfc766e23b8d391cd4db2965d7d9dfaa2cb112059",
|
|
5
|
+
"internalExecutableBuildHash": "63a6d2d9113dd353dfce165efccf82a5ea25398c2aad9f1b2c5543dc28e8b833"
|
|
6
6
|
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type ControllerControl = Readonly<{
|
|
27
27
|
token: string;
|
|
28
28
|
generation: string;
|
|
29
|
+
rootId?: string;
|
|
29
30
|
channelDir: string;
|
|
30
31
|
statusDir: string;
|
|
31
32
|
runtimeDir: string;
|
|
@@ -83,13 +84,14 @@ function copyRegular(source: string, destination: string, mode: number): void {
|
|
|
83
84
|
function controlFromEnvironment(): ControllerControl {
|
|
84
85
|
const token = process.env.AGENT_INFRA_CONTROL_TOKEN;
|
|
85
86
|
const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
87
|
+
const rootId = process.env.AGENT_INFRA_CONTROL_ROOT_ID;
|
|
86
88
|
const channelDir = process.env.AGENT_INFRA_CONTROL_DIR;
|
|
87
89
|
const statusDir = process.env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
88
90
|
const runtimeDir = process.env.AGENT_INFRA_RUNTIME_DIR;
|
|
89
|
-
if (!token || !generation || !channelDir || !statusDir || !runtimeDir) {
|
|
91
|
+
if (!token || !generation || !rootId || !channelDir || !statusDir || !runtimeDir) {
|
|
90
92
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTROL_MISSING');
|
|
91
93
|
}
|
|
92
|
-
return { token, generation, channelDir, statusDir, runtimeDir };
|
|
94
|
+
return { token, generation, rootId, channelDir, statusDir, runtimeDir };
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
function verifyRuntime(runtimeDir: string): void {
|
|
@@ -255,6 +257,9 @@ function isolatedEnvironment(
|
|
|
255
257
|
AGENT_INFRA_TASK_ID: taskId,
|
|
256
258
|
AGENT_INFRA_CONTROL_TOKEN: control.token,
|
|
257
259
|
AGENT_INFRA_CONTROL_GENERATION: control.generation,
|
|
260
|
+
...(control.rootId || sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID
|
|
261
|
+
? { AGENT_INFRA_CONTROL_ROOT_ID: control.rootId ?? sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID }
|
|
262
|
+
: {}),
|
|
258
263
|
AGENT_INFRA_CONTROL_DIR: control.channelDir,
|
|
259
264
|
AGENT_INFRA_CONTROL_STATUS_DIR: control.statusDir,
|
|
260
265
|
AGENT_INFRA_RUNTIME_DIR: control.runtimeDir,
|
|
@@ -43,11 +43,23 @@ type ActiveCodexLifecycleEvidenceQuery = Readonly<{
|
|
|
43
43
|
const LOCK_RETRY_MS = 10;
|
|
44
44
|
const LOCK_TIMEOUT_MS = 1_000;
|
|
45
45
|
const LOCK_STALE_MS = 30_000;
|
|
46
|
+
const RECOVERY_CONSUMER_RE = /^lifecycle-recovery:([^:\r\n]+):([^:\r\n]+)$/u;
|
|
46
47
|
|
|
47
48
|
function digest(value: string): string {
|
|
48
49
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
function isRecoveryConsumer(value: string): boolean {
|
|
53
|
+
return RECOVERY_CONSUMER_RE.test(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function recoveryConsumer(taskId: string, receiptId: string): string {
|
|
57
|
+
if (!/^[^:\r\n]+$/u.test(taskId) || !/^[^:\r\n]+$/u.test(receiptId)) {
|
|
58
|
+
throw new Error('Codex lifecycle recovery claim identity is invalid');
|
|
59
|
+
}
|
|
60
|
+
return `lifecycle-recovery:${taskId}:${receiptId}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
function readRecord(file: string): StoredCodexLifecycle {
|
|
52
64
|
const value = JSON.parse(fs.readFileSync(file, 'utf8')) as StoredCodexLifecycle;
|
|
53
65
|
if (
|
|
@@ -249,11 +261,12 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
|
|
|
249
261
|
|
|
250
262
|
function read(childThreadId: string): StoredCodexLifecycle {
|
|
251
263
|
const matches = findByChild(childThreadId);
|
|
252
|
-
if (matches.length
|
|
264
|
+
if (matches.length === 0) throw new Error(`Codex lifecycle child '${childThreadId}' was not found uniquely`);
|
|
265
|
+
if (matches.length > 1) throw new Error(`Codex lifecycle child '${childThreadId}' is ambiguous`);
|
|
253
266
|
return readRecord(matches[0]!);
|
|
254
267
|
}
|
|
255
268
|
|
|
256
|
-
function
|
|
269
|
+
function consumeInternal(
|
|
257
270
|
childThreadId: string,
|
|
258
271
|
consumer: string,
|
|
259
272
|
expectedHookDefinitionHash?: string
|
|
@@ -291,12 +304,51 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
|
|
|
291
304
|
});
|
|
292
305
|
}
|
|
293
306
|
|
|
307
|
+
function consume(
|
|
308
|
+
childThreadId: string,
|
|
309
|
+
consumer: string,
|
|
310
|
+
expectedHookDefinitionHash?: string
|
|
311
|
+
): StoredCodexLifecycle {
|
|
312
|
+
if (!consumer.trim()) throw new Error('Codex lifecycle consumer is required');
|
|
313
|
+
if (consumer.startsWith('lifecycle-recovery:')) {
|
|
314
|
+
throw new Error('Codex lifecycle recovery claims require claimRecovery');
|
|
315
|
+
}
|
|
316
|
+
return consumeInternal(childThreadId, consumer, expectedHookDefinitionHash);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function claimRecovery(
|
|
320
|
+
childThreadId: string,
|
|
321
|
+
taskId: string,
|
|
322
|
+
receiptId: string,
|
|
323
|
+
expectedHookDefinitionHash?: string
|
|
324
|
+
): StoredCodexLifecycle {
|
|
325
|
+
return consumeInternal(
|
|
326
|
+
childThreadId,
|
|
327
|
+
recoveryConsumer(taskId, receiptId),
|
|
328
|
+
expectedHookDefinitionHash
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function releaseRecovery(childThreadId: string, consumer: string): boolean {
|
|
333
|
+
if (!/^lifecycle-recovery:[^:\r\n]+:[^:\r\n]+$/.test(consumer)) return false;
|
|
334
|
+
return withWriteLock(() => {
|
|
335
|
+
const matches = findByChild(childThreadId);
|
|
336
|
+
if (matches.length !== 1) return false;
|
|
337
|
+
const file = matches[0]!;
|
|
338
|
+
const current = readRecord(file);
|
|
339
|
+
if (current.state.status !== 'stop-ready' || current.consumer !== consumer || !current.consumedAt) return false;
|
|
340
|
+
fs.unlinkSync(file);
|
|
341
|
+
return true;
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
294
345
|
function expireBefore(cutoff: string): number {
|
|
295
346
|
return withWriteLock(() => {
|
|
296
347
|
let changed = 0;
|
|
297
348
|
for (const file of recordFiles(root)) {
|
|
298
349
|
const current = readRecord(file);
|
|
299
350
|
if (current.updatedAt >= cutoff) continue;
|
|
351
|
+
if (current.consumer && isRecoveryConsumer(current.consumer)) continue;
|
|
300
352
|
if (current.consumer || ['invalid', 'expired', 'stop-ready'].includes(current.state.status)) {
|
|
301
353
|
fs.unlinkSync(file);
|
|
302
354
|
changed += 1;
|
|
@@ -316,7 +368,7 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
|
|
|
316
368
|
});
|
|
317
369
|
}
|
|
318
370
|
|
|
319
|
-
return Object.freeze({ root, apply, applyToSpawn, consume, expireBefore, findByParent, read });
|
|
371
|
+
return Object.freeze({ root, apply, applyToSpawn, consume, claimRecovery, releaseRecovery, expireBefore, findByParent, read });
|
|
320
372
|
}
|
|
321
373
|
|
|
322
374
|
export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence };
|
package/lib/fs/durable-write.ts
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
export function fsyncDirectory(directory: string): void {
|
|
6
|
+
if (process.platform === 'win32') return;
|
|
6
7
|
const descriptor = fs.openSync(directory, 'r');
|
|
7
8
|
try { fs.fsyncSync(descriptor); } finally { fs.closeSync(descriptor); }
|
|
8
9
|
}
|
|
@@ -57,7 +57,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
57
57
|
'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert'],
|
|
58
58
|
'task-warning': ['list', 'add', 'set-status'],
|
|
59
59
|
'task-activity': ['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate'],
|
|
60
|
-
'task-artifact': ['inspect', 'init', '
|
|
60
|
+
'task-artifact': ['inspect', 'init', 'finalize-local'],
|
|
61
61
|
'task-orchestration': ['status', 'progress'],
|
|
62
62
|
'task-review': ['finalize-summary'],
|
|
63
63
|
'task-event': ['event'],
|
|
@@ -43,15 +43,24 @@ function printFailure(format: OutputFormat, error: { code: string; message: stri
|
|
|
43
43
|
process.exitCode = 1;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
const MANUAL_VALIDATION_STATUS_SECTION = /^###\s+(?:⚠️\s+(?:需人工校验|Manual Validation Required)|✅\s+(?:人工验证已通过|无需人工校验|Manual Validation Passed|No Manual Validation Required)|⏳\s+(?:人工验证待收尾|Manual Validation Pending))\s*$[\s\S]*?(?=^#{1,3}\s|(?![\s\S]))/gmu;
|
|
47
|
+
|
|
46
48
|
function manualSummaryBody(body: string, phase: 'pending' | 'final', transactionId: string, receiptDigest = '', evidenceDigest = '', prHeadSha = ''): string {
|
|
47
|
-
const
|
|
48
|
-
.replace(/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/gmu, '')
|
|
49
|
-
.replace(/^###\s+⏳\s+(?:Manual Validation Pending|人工验证待收尾)\s*$/gmu, '')
|
|
50
|
-
.replace(/Manual validation passed(?:\s+→)?/giu, 'Manual validation pending');
|
|
49
|
+
const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
|
|
51
50
|
const section = phase === 'pending'
|
|
52
|
-
?
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
? chinese
|
|
52
|
+
? `### ⏳ 人工验证待收尾\n\n人工验证证据已暂存;事务 ${transactionId} 正在等待最终完成。`
|
|
53
|
+
: `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
|
|
54
|
+
: chinese
|
|
55
|
+
? `### ✅ 人工验证已通过\n\n人工验证已通过;transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`
|
|
56
|
+
: `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
|
|
57
|
+
let inserted = false;
|
|
58
|
+
const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, () => {
|
|
59
|
+
if (inserted) return '';
|
|
60
|
+
inserted = true;
|
|
61
|
+
return `${section}\n\n`;
|
|
62
|
+
});
|
|
63
|
+
return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
function openManualValidationStarted(taskMdPath: string): { present: boolean; transactionId: string | null } {
|
|
@@ -246,7 +255,7 @@ async function executeManualValidationTransactionLocked(
|
|
|
246
255
|
});
|
|
247
256
|
if (!['applied', 'no-op'].includes(promoted.status)) transactionFailure(promoted.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary promotion failed' });
|
|
248
257
|
const postWrite = await summaryCommentState(taskRef, { cwd, client: options.client });
|
|
249
|
-
if (
|
|
258
|
+
if (!/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.test(postWrite.comment?.body ?? '') || postWrite.pullRequest?.head.sha !== receipt.prHeadSha || !manualValidationFinalSummaryProjectionMatches(postWrite.comment?.body ?? '', receipt)) transactionFailure({ code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary post-write verification failed' });
|
|
250
259
|
transitionAndPersist('committed', { postWriteVerified: true });
|
|
251
260
|
return result('applied', null, { transaction, receipt, idempotent: false });
|
|
252
261
|
} catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseArtifactCommand, executeArtifactCommand } from '../task/artifact-command.ts';
|
|
2
2
|
import { ensureInternalHandlerRoute } from './cli-route-inventory.ts';
|
|
3
3
|
|
|
4
|
-
const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id>
|
|
4
|
+
const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> finalize-local --family <analysis|plan|code> --artifact <artifact> [--recovery-id <id>]\n\nInspect, initialize, or finalize a workflow artifact without changing task state.\n`;
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
function taskArtifact(args: string[] = []): void {
|