@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,6 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
2
4
|
import { parseArtifactName } from './artifact-name.js';
|
|
3
|
-
import { validateCompletedArtifact,
|
|
5
|
+
import { validateCompletedArtifact, validateArtifactPublication } from './artifact-lifecycle.js';
|
|
4
6
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
5
7
|
import { finalizeReviewSummaryContent } from './review-artifacts.js';
|
|
6
8
|
import { inspectDecisionDetailDuplicates } from './decision-details.js';
|
|
@@ -11,8 +13,6 @@ import { allowsManualOverride } from './guard-override.js';
|
|
|
11
13
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
12
14
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
13
15
|
import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
|
|
14
|
-
import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
15
|
-
import { beginArtifactRecovery, commitArtifactRecovery, prepareArtifactRecoveryFinal, prepareArtifactRecoveryCommit, reconcileArtifactRecovery, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
|
|
16
16
|
const STAGES = {
|
|
17
17
|
analysis: { family: 'review-analysis' },
|
|
18
18
|
plan: { family: 'review-plan' },
|
|
@@ -47,7 +47,7 @@ function preflightFailed(request, code, message, taskId = null, extra = {}) {
|
|
|
47
47
|
stageStatus: null
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
/** Validate
|
|
50
|
+
/** Validate the current review draft before any finding-ledger write. */
|
|
51
51
|
function preflightReviewSummaryUnlocked(request, options = {}) {
|
|
52
52
|
const stage = request.stage;
|
|
53
53
|
const spec = STAGES[stage];
|
|
@@ -68,37 +68,16 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
|
|
|
68
68
|
return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message, resolved.taskId);
|
|
69
69
|
let taskContent;
|
|
70
70
|
let content;
|
|
71
|
-
let recovery;
|
|
72
71
|
try {
|
|
73
72
|
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!['awaiting-preflight-recovery', 'preflight-ready'].includes(intent.state))
|
|
79
|
-
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`, resolved.taskId);
|
|
80
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
81
|
-
content = fs.existsSync(recovery.stagingPath)
|
|
82
|
-
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
83
|
-
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
|
|
87
|
-
if (!validated.ok)
|
|
88
|
-
return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
|
|
89
|
-
content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
90
|
-
const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
|
|
91
|
-
if (intent?.state === 'preflight-ready')
|
|
92
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
93
|
-
if (intent?.state === 'consumed')
|
|
94
|
-
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', 'review artifact was already finalized', resolved.taskId);
|
|
95
|
-
}
|
|
73
|
+
const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
|
|
74
|
+
if (!validated.ok)
|
|
75
|
+
return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
|
|
76
|
+
content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
96
77
|
}
|
|
97
78
|
catch (error) {
|
|
98
79
|
return preflightFailed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
|
|
99
80
|
}
|
|
100
|
-
if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round))
|
|
101
|
-
return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`, resolved.taskId);
|
|
102
81
|
const execution = validateLifecycleExecution(request.taskRef, {
|
|
103
82
|
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
104
83
|
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
@@ -120,17 +99,6 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
|
|
|
120
99
|
});
|
|
121
100
|
const qualificationError = qualification.ok ? null : qualification;
|
|
122
101
|
if (!detail.ok || !structure.ok || qualificationError) {
|
|
123
|
-
if (!recovery && !request.dryRun) {
|
|
124
|
-
try {
|
|
125
|
-
recovery = beginArtifactRecovery({
|
|
126
|
-
taskId: resolved.taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
127
|
-
requestId: `review-preflight:${resolved.taskId}:${stage}:${parsed.round}`
|
|
128
|
-
}, Buffer.from(content, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
102
|
const diagnostics = [
|
|
135
103
|
...(!detail.ok ? [`${detail.code}: ${detail.message}`] : []),
|
|
136
104
|
...structure.diagnostics.map((item) => `${item.code}: ${item.message}`),
|
|
@@ -138,30 +106,18 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
|
|
|
138
106
|
].join('; ');
|
|
139
107
|
return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : !structure.ok ? 'REVIEW_ARTIFACT_STRUCTURE_INVALID' : 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', diagnostics, resolved.taskId, {
|
|
140
108
|
artifactSha256, semanticDigest,
|
|
141
|
-
...(recovery ? { recovery: recoveryInfo(recovery) } : {})
|
|
142
109
|
});
|
|
143
110
|
}
|
|
144
|
-
if (request.dryRun
|
|
111
|
+
if (request.dryRun) {
|
|
145
112
|
return {
|
|
146
|
-
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest
|
|
113
|
+
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
|
|
147
114
|
status: 'passed', changed: false, error: null
|
|
148
115
|
};
|
|
149
116
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}, Buffer.from(content, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
155
|
-
const staged = stageArtifactCandidate(recovery, Buffer.from(content, 'utf8'), { lockAlreadyHeld: options.lockAlreadyHeld });
|
|
156
|
-
prepareArtifactRecoveryCommit(recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: options.lockAlreadyHeld });
|
|
157
|
-
return {
|
|
158
|
-
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest, recovery: recoveryInfo(recovery) }),
|
|
159
|
-
status: 'passed', changed: false, error: null
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
catch (error) {
|
|
163
|
-
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, { artifactSha256, semanticDigest });
|
|
164
|
-
}
|
|
117
|
+
return {
|
|
118
|
+
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
|
|
119
|
+
status: 'passed', changed: false, error: null
|
|
120
|
+
};
|
|
165
121
|
}
|
|
166
122
|
function preflightReviewSummary(request, options = {}) {
|
|
167
123
|
if (request.dryRun || options.lockAlreadyHeld)
|
|
@@ -178,14 +134,6 @@ function preflightReviewSummary(request, options = {}) {
|
|
|
178
134
|
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `${error.code}: ${error.message}`, resolved.taskId);
|
|
179
135
|
}
|
|
180
136
|
}
|
|
181
|
-
function recoveryInfo(recovery) {
|
|
182
|
-
return {
|
|
183
|
-
recoveryId: recovery.recoveryId,
|
|
184
|
-
candidatePath: recovery.stagingPath,
|
|
185
|
-
baselineSha256: recovery.baselineSha256,
|
|
186
|
-
baselineSemanticDigest: recovery.baselineSemanticDigest
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
137
|
/** Prepare domain validation, summary bytes and provenance before any publication. */
|
|
190
138
|
function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
191
139
|
let taskId = null;
|
|
@@ -219,45 +167,9 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
219
167
|
catch (error) {
|
|
220
168
|
return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error));
|
|
221
169
|
}
|
|
222
|
-
let recovery;
|
|
223
|
-
if (request.recoveryId) {
|
|
224
|
-
try {
|
|
225
|
-
const intent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
|
|
226
|
-
if (!intent || intent.recoveryOperationId !== request.recoveryId)
|
|
227
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal');
|
|
228
|
-
if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state))
|
|
229
|
-
return reject('REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`);
|
|
230
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
231
|
-
artifactContent = fs.existsSync(recovery.stagingPath)
|
|
232
|
-
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
233
|
-
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
return reject('REVIEW_PROVENANCE_INVALID', String(error));
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
170
|
const artifactSha256 = sha256Content(artifactContent);
|
|
240
171
|
const semanticDigest = canonicalSemanticDigest(artifactContent);
|
|
241
172
|
const digests = { artifactSha256, semanticDigest };
|
|
242
|
-
let repairIntent;
|
|
243
|
-
try {
|
|
244
|
-
repairIntent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
|
|
248
|
-
}
|
|
249
|
-
if (repairIntent?.state === 'consumed') {
|
|
250
|
-
if (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest) {
|
|
251
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if (repairIntent?.state === 'passed'
|
|
255
|
-
&& (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest)) {
|
|
256
|
-
repairIntent = undefined;
|
|
257
|
-
}
|
|
258
|
-
if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
|
|
259
|
-
return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
|
|
260
|
-
}
|
|
261
173
|
const execution = validateLifecycleExecution(request.taskRef, {
|
|
262
174
|
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
263
175
|
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
@@ -284,67 +196,16 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
284
196
|
const schema = getArtifactSchema(spec.family);
|
|
285
197
|
const structure = inspectArtifactContract(artifactContent, schema);
|
|
286
198
|
if (!structure.ok) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
291
|
-
requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
|
|
292
|
-
}, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
293
|
-
}
|
|
294
|
-
catch (error) {
|
|
295
|
-
return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return {
|
|
299
|
-
...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
|
|
300
|
-
artifactSha256, semanticDigest: structure.semanticDigest,
|
|
301
|
-
...(recovery ? { recovery: {
|
|
302
|
-
recoveryId: recovery.recoveryId,
|
|
303
|
-
candidatePath: recovery.stagingPath,
|
|
304
|
-
baselineSha256: recovery.baselineSha256,
|
|
305
|
-
baselineSemanticDigest: recovery.baselineSemanticDigest
|
|
306
|
-
} } : {})
|
|
307
|
-
}),
|
|
308
|
-
...(recovery ? { recovery } : {})
|
|
309
|
-
};
|
|
199
|
+
return reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
|
|
200
|
+
artifactSha256, semanticDigest: structure.semanticDigest
|
|
201
|
+
});
|
|
310
202
|
}
|
|
311
203
|
const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts, {
|
|
312
|
-
refreshNumericCounts:
|
|
204
|
+
refreshNumericCounts: true
|
|
313
205
|
});
|
|
314
206
|
if (!transformed.ok)
|
|
315
207
|
return reject(transformed.code, transformed.message);
|
|
316
208
|
const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
|
|
317
|
-
if (!recovery && repairIntent?.state === 'commit-started') {
|
|
318
|
-
if (repairIntent.finalArtifactSha256 !== finalDigests.artifactSha256 || repairIntent.finalSemanticDigest !== finalDigests.semanticDigest) {
|
|
319
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'formal review artifact does not match the interrupted recovery journal', finalDigests);
|
|
320
|
-
}
|
|
321
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, repairIntent);
|
|
322
|
-
}
|
|
323
|
-
if (!recovery && (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')) {
|
|
324
|
-
if (transformed.changed)
|
|
325
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact requires changes after its completed recovery was consumed', finalDigests);
|
|
326
|
-
return {
|
|
327
|
-
content: transformed.content,
|
|
328
|
-
result: {
|
|
329
|
-
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
|
|
330
|
-
status: 'no-op',
|
|
331
|
-
changed: false,
|
|
332
|
-
error: null
|
|
333
|
-
},
|
|
334
|
-
lockAlreadyHeld: options.lockAlreadyHeld
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
if (!recovery && !request.dryRun && options.startRecovery) {
|
|
338
|
-
try {
|
|
339
|
-
recovery = beginArtifactRecovery({
|
|
340
|
-
taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
341
|
-
requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
|
|
342
|
-
}, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
343
|
-
}
|
|
344
|
-
catch (error) {
|
|
345
|
-
return reject('REVIEW_PROVENANCE_INVALID', String(error), finalDigests);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
209
|
return {
|
|
349
210
|
content: transformed.content,
|
|
350
211
|
result: {
|
|
@@ -354,65 +215,26 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
354
215
|
operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
|
|
355
216
|
error: null
|
|
356
217
|
},
|
|
357
|
-
...(recovery ? { recovery } : {}),
|
|
358
218
|
lockAlreadyHeld: options.lockAlreadyHeld
|
|
359
219
|
};
|
|
360
220
|
}
|
|
361
221
|
function commitReviewSummaryProvenance(prepared, repoRoot, options = {}) {
|
|
362
|
-
|
|
363
|
-
|
|
222
|
+
return prepared.result;
|
|
223
|
+
}
|
|
224
|
+
function replaceCurrentArtifactAtomically(file, content) {
|
|
225
|
+
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
|
|
364
226
|
try {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
...prepared.result,
|
|
372
|
-
status: 'no-op',
|
|
373
|
-
changed: false,
|
|
374
|
-
artifactSha256: intent.finalArtifactSha256,
|
|
375
|
-
semanticDigest: intent.finalSemanticDigest,
|
|
376
|
-
error: null
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
else if (intent.state === 'commit-started') {
|
|
380
|
-
const reconciled = reconcileArtifactRecovery(prepared.recovery, {
|
|
381
|
-
lockAlreadyHeld: prepared.lockAlreadyHeld,
|
|
382
|
-
afterPublish: options.afterPublish
|
|
383
|
-
});
|
|
384
|
-
committed = reconciled.intent;
|
|
385
|
-
}
|
|
386
|
-
else if (intent.state === 'awaiting-preflight-recovery') {
|
|
387
|
-
const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
388
|
-
prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
389
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
390
|
-
}
|
|
391
|
-
else if (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started') {
|
|
392
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
393
|
-
}
|
|
394
|
-
if (committed?.state === 'preflight-passed' || intent.state === 'preflight-passed') {
|
|
395
|
-
prepareArtifactRecoveryFinal(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
396
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
227
|
+
fs.writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx' });
|
|
228
|
+
fs.renameSync(temporary, file);
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
try {
|
|
232
|
+
fs.unlinkSync(temporary);
|
|
397
233
|
}
|
|
398
|
-
|
|
399
|
-
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (error.code !== 'ENOENT')
|
|
236
|
+
throw error;
|
|
400
237
|
}
|
|
401
|
-
if (!committed || committed.state !== 'passed')
|
|
402
|
-
throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
|
|
403
|
-
return {
|
|
404
|
-
...prepared.result,
|
|
405
|
-
artifactSha256: committed.finalArtifactSha256,
|
|
406
|
-
semanticDigest: committed.finalSemanticDigest,
|
|
407
|
-
error: null
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
catch (error) {
|
|
411
|
-
if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:'))
|
|
412
|
-
throw error;
|
|
413
|
-
return { ...prepared.result, status: 'failed', error: {
|
|
414
|
-
code: 'REVIEW_RECOVERY_COMMIT_FAILED', message: `cannot publish review candidate: ${String(error)}`
|
|
415
|
-
} };
|
|
416
238
|
}
|
|
417
239
|
}
|
|
418
240
|
function finalizeReviewSummaryUnlocked(request, options = {}) {
|
|
@@ -437,10 +259,23 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
|
|
|
437
259
|
const prepared = prepareReviewSummaryCandidate(request, content, options);
|
|
438
260
|
if (request.dryRun)
|
|
439
261
|
return prepared.result;
|
|
440
|
-
|
|
262
|
+
if (prepared.result.status === 'failed' || !prepared.result.changed)
|
|
263
|
+
return prepared.result;
|
|
264
|
+
try {
|
|
265
|
+
replaceCurrentArtifactAtomically(validated.artifact.path, prepared.content);
|
|
266
|
+
return {
|
|
267
|
+
...prepared.result,
|
|
268
|
+
status: 'applied',
|
|
269
|
+
artifactSha256: sha256Content(prepared.content),
|
|
270
|
+
semanticDigest: canonicalSemanticDigest(prepared.content)
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
return failed(request, 'REVIEW_RECOVERY_COMMIT_FAILED', `cannot update current review artifact: ${error instanceof Error ? error.message : String(error)}`, resolved.taskId);
|
|
275
|
+
}
|
|
441
276
|
}
|
|
442
277
|
function finalizeReviewSummary(request, options = {}) {
|
|
443
|
-
const effectiveOptions =
|
|
278
|
+
const effectiveOptions = options;
|
|
444
279
|
if (request.dryRun || options.lockAlreadyHeld)
|
|
445
280
|
return finalizeReviewSummaryUnlocked(request, effectiveOptions);
|
|
446
281
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.11.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.3",
|
|
4
|
+
"sourceInputHash": "99efbc575d475879773bf1676cc2de5ea628b9c193f69a9812a723313ba5ae7f",
|
|
5
|
+
"internalExecutableBuildHash": "6912a66863d013c476699b88cc1d4154082971164cbae3f98ea79dda39a46e7a"
|
|
6
6
|
}
|
|
@@ -7,6 +7,10 @@ import type {
|
|
|
7
7
|
AgentClientCapabilityMap,
|
|
8
8
|
AgentClientId
|
|
9
9
|
} from './types.ts';
|
|
10
|
+
import type {
|
|
11
|
+
OrchestrationOptions,
|
|
12
|
+
OrchestrationResult
|
|
13
|
+
} from '../task/orchestration.ts';
|
|
10
14
|
import {
|
|
11
15
|
SANDBOX_HOOK_PHASES
|
|
12
16
|
} from '../sandbox/tool-types.ts';
|
|
@@ -43,6 +47,41 @@ type AgentClientDelegationEvidence = Readonly<{
|
|
|
43
47
|
actualReasoningEffort: 'host-event' | 'app-server' | 'spawn-ack' | 'unavailable';
|
|
44
48
|
}>;
|
|
45
49
|
|
|
50
|
+
type AgentClientLifecycleRecoveryRequest = Readonly<{
|
|
51
|
+
taskRef: string;
|
|
52
|
+
intent: 'recover-started';
|
|
53
|
+
agent: string;
|
|
54
|
+
auto: true;
|
|
55
|
+
}>;
|
|
56
|
+
|
|
57
|
+
type AgentClientLifecycleRecoveryResult = Readonly<{
|
|
58
|
+
status: 'applied' | 'no-op' | 'owner-unknown' | 'conflict';
|
|
59
|
+
changed: boolean;
|
|
60
|
+
targetState: 'active';
|
|
61
|
+
requestRef: string;
|
|
62
|
+
intent: 'recover-started';
|
|
63
|
+
taskId: string | null;
|
|
64
|
+
receiptId: string | null;
|
|
65
|
+
childId: string | null;
|
|
66
|
+
error: Readonly<{ code: string; message: string }> | null;
|
|
67
|
+
}>;
|
|
68
|
+
|
|
69
|
+
type AgentClientOrchestrationAdapter = Readonly<{
|
|
70
|
+
prepareDelegation?: (
|
|
71
|
+
taskRef: string,
|
|
72
|
+
input: Readonly<{
|
|
73
|
+
client: AgentClientId;
|
|
74
|
+
requestedModel?: string;
|
|
75
|
+
requestedReasoningEffort?: string;
|
|
76
|
+
}>,
|
|
77
|
+
options?: Readonly<{ repoRoot?: string; orchestrationOptions?: OrchestrationOptions }>
|
|
78
|
+
) => Promise<OrchestrationResult>;
|
|
79
|
+
recoverStarted?: (
|
|
80
|
+
request: AgentClientLifecycleRecoveryRequest,
|
|
81
|
+
options: Readonly<{ repoRoot: string }>
|
|
82
|
+
) => AgentClientLifecycleRecoveryResult | Promise<AgentClientLifecycleRecoveryResult>;
|
|
83
|
+
}>;
|
|
84
|
+
|
|
46
85
|
type AgentClientSeedCommand = Readonly<{
|
|
47
86
|
templates: Readonly<{
|
|
48
87
|
en: string;
|
|
@@ -75,6 +114,7 @@ type AgentClientAdapter = Readonly<{
|
|
|
75
114
|
capabilities: AgentClientCapabilities;
|
|
76
115
|
modelSelection: AgentClientModelSelectionContext;
|
|
77
116
|
delegationEvidence: AgentClientDelegationEvidence;
|
|
117
|
+
orchestrationAdapter?: AgentClientOrchestrationAdapter;
|
|
78
118
|
project: AgentClientProjectDescriptor;
|
|
79
119
|
sandbox: AgentClientSandboxDescriptor;
|
|
80
120
|
}>;
|
|
@@ -628,6 +668,9 @@ function defineAgentClientAdapter(
|
|
|
628
668
|
capabilities: Object.freeze(capabilities),
|
|
629
669
|
modelSelection: frozenModelSelection,
|
|
630
670
|
delegationEvidence: Object.freeze({ ...evidence }),
|
|
671
|
+
...(candidate.orchestrationAdapter === undefined
|
|
672
|
+
? {}
|
|
673
|
+
: { orchestrationAdapter: Object.freeze({ ...candidate.orchestrationAdapter }) }),
|
|
631
674
|
project: Object.freeze({
|
|
632
675
|
ownedPathPrefixes: Object.freeze(paths),
|
|
633
676
|
...projectAssets,
|
|
@@ -650,6 +693,9 @@ export type {
|
|
|
650
693
|
AgentClientCapabilities,
|
|
651
694
|
AgentClientCustomCommandDescriptor,
|
|
652
695
|
AgentClientDelegationEvidence,
|
|
696
|
+
AgentClientLifecycleRecoveryRequest,
|
|
697
|
+
AgentClientLifecycleRecoveryResult,
|
|
698
|
+
AgentClientOrchestrationAdapter,
|
|
653
699
|
AgentClientManifestEntry,
|
|
654
700
|
AgentClientModelSelectionContext,
|
|
655
701
|
AgentClientProjectDescriptor,
|