@fitlab-ai/agent-infra 0.11.1 → 0.11.2
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/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/platform-comment.js +59 -0
- package/dist/lib/internal/sandbox-control.js +4 -131
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -0
- 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 +120 -13
- 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/executor.js +0 -38
- package/dist/lib/sandbox/control/finalization-response.js +10 -2
- package/dist/lib/sandbox/control/task-view.js +3 -1
- package/dist/lib/sandbox/control/workflow-executor.js +28 -43
- 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 +1 -8
- package/dist/lib/task/delegation-receipts.js +14 -76
- package/dist/lib/task/events.js +89 -237
- package/dist/lib/task/finalization-state.js +2 -1
- package/dist/lib/task/finalization.js +273 -49
- package/dist/lib/task/lifecycle-execution.js +2 -11
- package/dist/lib/task/local-artifact-finalization.js +25 -242
- package/dist/lib/task/orchestration.js +0 -18
- package/dist/lib/task/review-command.js +0 -1
- package/dist/lib/task/review-finalization.js +46 -211
- package/dist/lib/task/workflow-command.js +4 -1
- package/dist/lib/task/workflow-dispatch.js +55 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/platform-comment.ts +38 -0
- package/lib/internal/sandbox-control.ts +3 -145
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -0
- 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 +132 -13
- 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/executor.ts +0 -44
- package/lib/sandbox/control/finalization-response.ts +10 -2
- package/lib/sandbox/control/task-view.ts +3 -1
- package/lib/sandbox/control/workflow-executor.ts +23 -50
- 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 +1 -8
- package/lib/task/delegation-receipts.ts +12 -79
- package/lib/task/events.ts +106 -255
- package/lib/task/finalization-state.ts +2 -1
- package/lib/task/finalization.ts +277 -57
- package/lib/task/lifecycle-execution.ts +10 -21
- package/lib/task/local-artifact-finalization.ts +23 -281
- package/lib/task/orchestration.ts +0 -34
- package/lib/task/review-command.ts +0 -1
- package/lib/task/review-finalization.ts +45 -193
- package/lib/task/workflow-command.ts +4 -1
- package/lib/task/workflow-dispatch.ts +46 -3
- package/package.json +2 -2
- package/templates/.agents/rules/lifecycle-orchestration.en.md +7 -7
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +7 -7
- 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-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- 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-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
- 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
|
@@ -357,43 +357,6 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
357
357
|
? lifecycleFailure('LIFECYCLE_PAYLOAD_INVALID', message)
|
|
358
358
|
: orchestrationFailure(code, message);
|
|
359
359
|
}
|
|
360
|
-
let controllerBinding = null;
|
|
361
|
-
if (request.family === 'task-orchestration') {
|
|
362
|
-
const codexPrepare = operation.family === 'task-orchestration'
|
|
363
|
-
&& operation.intent === 'prepare'
|
|
364
|
-
&& operation.input.client === 'codex';
|
|
365
|
-
if (codexPrepare) {
|
|
366
|
-
if (!request.controllerProof) {
|
|
367
|
-
const authority = options.lifecycleRecoveryAttestation;
|
|
368
|
-
if (!authority || authority.phase !== 'orchestration.prepare') {
|
|
369
|
-
return orchestrationFailure('CODEX_SANDBOX_CONTROLLER_PROOF_REQUIRED', 'Codex prepare requires a current controller lease proof');
|
|
370
|
-
}
|
|
371
|
-
controllerBinding = {
|
|
372
|
-
instanceDigest: authority.controllerInstanceDigest,
|
|
373
|
-
controlGeneration: authority.controlGeneration
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
try {
|
|
378
|
-
controllerBinding = (options.resolveControllerBinding ?? resolveCodexControllerBinding)({
|
|
379
|
-
manifest,
|
|
380
|
-
manifestPath,
|
|
381
|
-
proof: request.controllerProof,
|
|
382
|
-
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
catch (error) {
|
|
386
|
-
const code = error instanceof CodexControllerRegistrationError
|
|
387
|
-
? error.code
|
|
388
|
-
: 'CODEX_SANDBOX_CONTROLLER_PROOF_INVALID';
|
|
389
|
-
return orchestrationFailure(code, `${code}: Codex controller proof was rejected`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
else if (request.controllerProof !== null) {
|
|
394
|
-
return orchestrationFailure('CODEX_SANDBOX_CONTROLLER_PROOF_INVALID', 'Controller proof is only accepted for canonical Codex prepare');
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
360
|
const diagnosticLog = (event, fields) => {
|
|
398
361
|
appendExecutorAudit(manifest, event.startsWith('orchestration-') ? event : `orchestration-${event}`, {
|
|
399
362
|
requestId: request.id,
|
|
@@ -411,7 +374,6 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
411
374
|
manifestPath,
|
|
412
375
|
requestId: request.id,
|
|
413
376
|
diagnosticLog,
|
|
414
|
-
...(controllerBinding ? { controllerBinding } : {}),
|
|
415
377
|
lifecycleRecoveryAttestation: options.lifecycleRecoveryAttestation ?? null
|
|
416
378
|
});
|
|
417
379
|
const format = (value) => {
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
export function finalizationTerminalResponse(taskId, requestId, receipt) {
|
|
2
2
|
const pendingSteps = [
|
|
3
3
|
receipt.taskComment === 'pending' ? 'task-comment' : null,
|
|
4
|
-
receipt.verification === 'pending' ? 'verification' : null
|
|
4
|
+
receipt.verification === 'pending' ? 'verification' : null,
|
|
5
|
+
receipt.summary === 'pending' ? 'summary' : null,
|
|
6
|
+
receipt.postSummaryVerification === 'pending' ? 'post-summary-verification' : null
|
|
5
7
|
].filter((step) => step !== null);
|
|
6
|
-
const completedSteps = [
|
|
8
|
+
const completedSteps = [
|
|
9
|
+
'lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment',
|
|
10
|
+
receipt.verification === 'pending' ? null : 'verification', receipt.summary === 'pending' ? null : 'summary',
|
|
11
|
+
receipt.postSummaryVerification === 'pending' ? null : 'post-summary-verification'
|
|
12
|
+
]
|
|
7
13
|
.filter((step) => step !== null);
|
|
8
14
|
const warnings = receipt.warnings
|
|
9
15
|
.filter((warning) => warning.status === 'open')
|
|
@@ -13,6 +19,8 @@ export function finalizationTerminalResponse(taskId, requestId, receipt) {
|
|
|
13
19
|
lifecycle: { status: 'no-op', changed: false, error: null },
|
|
14
20
|
taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
15
21
|
verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
22
|
+
summary: receipt.summary === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
23
|
+
postSummaryVerification: receipt.postSummaryVerification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
16
24
|
completedSteps, pendingSteps,
|
|
17
25
|
result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
|
|
18
26
|
? 'completed_with_warnings' : 'completed',
|
|
@@ -13,9 +13,11 @@ function receiptIdentity(value, generation, requestId) {
|
|
|
13
13
|
if (!binding || typeof binding !== 'object' || Array.isArray(binding))
|
|
14
14
|
return null;
|
|
15
15
|
const controlBinding = binding;
|
|
16
|
-
if (receipt.version !==
|
|
16
|
+
if (receipt.version !== 3 || typeof receipt.receiptId !== 'string' || receipt.receiptId.length === 0
|
|
17
17
|
|| !Number.isSafeInteger(receipt.revision) || receipt.revision < 0
|
|
18
18
|
|| receipt.lifecycle !== 'done'
|
|
19
|
+
|| !['pending', 'done', 'skipped'].includes(String(receipt.summary))
|
|
20
|
+
|| !['pending', 'done', 'skipped'].includes(String(receipt.postSummaryVerification))
|
|
19
21
|
|| controlBinding.generation !== generation
|
|
20
22
|
|| typeof controlBinding.requestId !== 'string'
|
|
21
23
|
|| !/^[a-f0-9-]{16,64}$/u.test(controlBinding.requestId)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
1
4
|
import { TASK_WORKFLOW_COMMANDS } from '../../task/workflow-command.js';
|
|
2
5
|
import { parseArtifactCommand, executeArtifactCommand } from '../../task/artifact-command.js';
|
|
3
6
|
import { parseReviewCommand } from '../../task/review-command.js';
|
|
4
7
|
import { prepareLocalArtifact, preflightLocalArtifact, commitLocalArtifactProvenance } from '../../task/local-artifact-finalization.js';
|
|
5
|
-
import { prepareReviewSummaryCandidate,
|
|
6
|
-
import { readArtifactRecoveryIntent } from '../../task/artifact-repair-intent.js';
|
|
8
|
+
import { prepareReviewSummaryCandidate, preflightReviewSummary } from '../../task/review-finalization.js';
|
|
7
9
|
import { withTaskExecutionLock } from '../../task/task-execution-lock.js';
|
|
8
10
|
import { dispatchWorkflowCommand } from '../../task/workflow-dispatch.js';
|
|
9
11
|
import { assertSandboxTaskSource } from '../workspace-view.js';
|
|
@@ -14,20 +16,25 @@ import { parseTaskEventRequest } from '../../internal/task-event.js';
|
|
|
14
16
|
function executionResult(result) {
|
|
15
17
|
return { exitCode: result.status === 'failed' || result.status === 'refused' ? 1 : 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
|
|
16
18
|
}
|
|
19
|
+
function replaceCurrentArtifactAtomically(file, content) {
|
|
20
|
+
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
|
|
21
|
+
try {
|
|
22
|
+
fs.writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx' });
|
|
23
|
+
fs.renameSync(temporary, file);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
try {
|
|
27
|
+
fs.unlinkSync(temporary);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.code !== 'ENOENT')
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
17
35
|
/** The broker already authorized this host executor; domain operations own validation. */
|
|
18
36
|
export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAttestation = null, options = {}) {
|
|
19
37
|
let publicationCommitted = false;
|
|
20
|
-
let recoveryObservation = null;
|
|
21
|
-
const observeRecoveryPublication = () => {
|
|
22
|
-
if (!recoveryObservation || recoveryObservation.wasPassed)
|
|
23
|
-
return;
|
|
24
|
-
const intent = readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, recoveryObservation.family, recoveryObservation.artifact);
|
|
25
|
-
publicationCommitted ||= intent?.state === 'preflight-ready'
|
|
26
|
-
|| intent?.state === 'preflight-commit-started'
|
|
27
|
-
|| intent?.state === 'preflight-passed'
|
|
28
|
-
|| intent?.state === 'passed'
|
|
29
|
-
|| intent?.state === 'commit-started';
|
|
30
|
-
};
|
|
31
38
|
let faultTriggered = false;
|
|
32
39
|
const fault = (window) => {
|
|
33
40
|
if (faultTriggered || options.faultWindow !== window)
|
|
@@ -91,26 +98,13 @@ export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAt
|
|
|
91
98
|
const { family } = input;
|
|
92
99
|
if (family !== 'analysis' && family !== 'plan' && family !== 'code')
|
|
93
100
|
throw new Error('ARTIFACT_IDENTITY_INVALID');
|
|
94
|
-
const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot,
|
|
101
|
+
const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot, lockAlreadyHeld: true };
|
|
95
102
|
if (input.operation === 'preflight') {
|
|
96
|
-
// Preflight only seals a verified generation. It must not publish the
|
|
97
|
-
// formal artifact, set publicationStarted, or append finalizer audit.
|
|
98
103
|
const result = preflightLocalArtifact(local);
|
|
99
|
-
recoveryObservation = { family, artifact: input.artifact, wasPassed: false };
|
|
100
|
-
observeRecoveryPublication();
|
|
101
104
|
return response(result);
|
|
102
105
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
artifact: input.artifact,
|
|
106
|
-
wasPassed: readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, family, input.artifact)?.state === 'passed'
|
|
107
|
-
};
|
|
108
|
-
const prepared = prepareLocalArtifact(local, content, lifecycleRecoveryAttestation ?? undefined);
|
|
109
|
-
observeRecoveryPublication();
|
|
110
|
-
const commitOptions = { afterPublish: () => fault('after-atomic-rename') };
|
|
111
|
-
if (prepared.result.status === 'failed')
|
|
112
|
-
return executionResult(commitLocalArtifactProvenance(prepared, commitOptions));
|
|
113
|
-
result = commitLocalArtifactProvenance(prepared, commitOptions);
|
|
106
|
+
const prepared = prepareLocalArtifact(local, content);
|
|
107
|
+
result = commitLocalArtifactProvenance(prepared);
|
|
114
108
|
}
|
|
115
109
|
else {
|
|
116
110
|
if (input.overrideTicket)
|
|
@@ -118,25 +112,17 @@ export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAt
|
|
|
118
112
|
const family = `review-${input.stage}`;
|
|
119
113
|
if (request.operation === 'review-preflight') {
|
|
120
114
|
const result = preflightReviewSummary(input, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true });
|
|
121
|
-
recoveryObservation = { family, artifact: input.artifact, wasPassed: false };
|
|
122
|
-
observeRecoveryPublication();
|
|
123
115
|
return response(result);
|
|
124
116
|
}
|
|
125
|
-
|
|
126
|
-
family,
|
|
127
|
-
artifact: input.artifact,
|
|
128
|
-
wasPassed: readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, family, input.artifact)?.state === 'passed'
|
|
129
|
-
};
|
|
130
|
-
const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true, startRecovery: true });
|
|
131
|
-
observeRecoveryPublication();
|
|
117
|
+
const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true });
|
|
132
118
|
if (input.dryRun || prepared.result.status === 'planned')
|
|
133
119
|
return executionResult(prepared.result);
|
|
134
|
-
const commitOptions = { afterPublish: () => fault('after-atomic-rename') };
|
|
135
120
|
if (prepared.result.status === 'failed')
|
|
136
|
-
return executionResult(
|
|
137
|
-
|
|
121
|
+
return executionResult(prepared.result);
|
|
122
|
+
if (prepared.result.changed)
|
|
123
|
+
replaceCurrentArtifactAtomically(path.join(taskDir, input.artifact), prepared.content);
|
|
124
|
+
result = prepared.result;
|
|
138
125
|
}
|
|
139
|
-
observeRecoveryPublication();
|
|
140
126
|
publicationCommitted ||= result.changed === true;
|
|
141
127
|
if (result.status !== 'failed')
|
|
142
128
|
appendDiagnosticAudit(manifest, 'task-workflow-artifact-finalized', {
|
|
@@ -148,7 +134,6 @@ export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAt
|
|
|
148
134
|
});
|
|
149
135
|
}
|
|
150
136
|
catch (error) {
|
|
151
|
-
observeRecoveryPublication();
|
|
152
137
|
const message = error instanceof Error ? error.message : String(error);
|
|
153
138
|
const code = error?.code ?? /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'TASK_WORKFLOW_REQUEST_INVALID';
|
|
154
139
|
return executionResult({ status: 'failed', changed: publicationCommitted ? null : false, error: { code, message } });
|
|
@@ -15,8 +15,8 @@ export function parseArtifactCommand(args) {
|
|
|
15
15
|
const allowed = {
|
|
16
16
|
inspect: ['--family'],
|
|
17
17
|
init: ['--family', '--artifact', '--locale'],
|
|
18
|
-
'preflight': ['--family', '--artifact'
|
|
19
|
-
'finalize-local': ['--family', '--artifact'
|
|
18
|
+
'preflight': ['--family', '--artifact'],
|
|
19
|
+
'finalize-local': ['--family', '--artifact']
|
|
20
20
|
}[operation];
|
|
21
21
|
for (let index = 2; index < args.length; index += 1) {
|
|
22
22
|
const flag = args[index];
|
|
@@ -38,12 +38,12 @@ export function parseArtifactCommand(args) {
|
|
|
38
38
|
throw new Error("option '--locale' must be 'zh-CN' or 'en'");
|
|
39
39
|
return {
|
|
40
40
|
taskRef, operation: operation, family: fields['--family'],
|
|
41
|
-
artifact: fields['--artifact'] ?? '', locale
|
|
41
|
+
artifact: fields['--artifact'] ?? '', locale
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
/** Resolve authoritative task metadata separately from a trusted candidate directory. */
|
|
45
45
|
export function executeArtifactCommand(command, options = {}) {
|
|
46
|
-
const { taskRef, operation, family, artifact, locale
|
|
46
|
+
const { taskRef, operation, family, artifact, locale } = command;
|
|
47
47
|
const fail = (code, message) => ({ status: 'failed', changed: false, error: { code, message } });
|
|
48
48
|
if (operation === 'inspect') {
|
|
49
49
|
const result = resolveArtifactContext(taskRef, family, { repoRoot: options.repoRoot });
|
|
@@ -76,7 +76,7 @@ export function executeArtifactCommand(command, options = {}) {
|
|
|
76
76
|
return fail('ARTIFACT_PAYLOAD_INVALID', "preflight and finalize-local only support 'analysis', 'plan', and 'code'");
|
|
77
77
|
}
|
|
78
78
|
if (operation === 'preflight')
|
|
79
|
-
return { ...preflightLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot
|
|
80
|
-
return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot
|
|
79
|
+
return { ...preflightLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot }) };
|
|
80
|
+
return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot }) };
|
|
81
81
|
}
|
|
82
82
|
//# sourceMappingURL=artifact-command.js.map
|
|
@@ -1,39 +1,15 @@
|
|
|
1
1
|
import { preflightCodexLifecycleEvidence, resolveCodexSpawnedChild, resolveCodexTerminal, resolveCodexThread } from '../agent-clients/adapters/codex-lifecycle/app-server.js';
|
|
2
2
|
import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.js';
|
|
3
|
-
import {
|
|
4
|
-
import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
5
|
-
import { verifyCodexSandboxControllerContextWithWarnings as verifySandboxControllerWithWarnings } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
|
|
6
|
-
import { verifyCodexSandboxControllerContextWithWarnings as verifyControllerContextFileWithWarnings } from '../agent-clients/adapters/codex-lifecycle/controller-context.js';
|
|
3
|
+
import { computeLifecycleBuildIdentity, } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
7
4
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
8
|
-
import { activateMatchingOrchestrationDelegation, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, pauseMatchingOrchestrationDelegation, prepareOrchestrationDelegation,
|
|
9
|
-
import { randomUUID, createHash } from 'node:crypto';
|
|
10
|
-
import { consumeLifecycleRecoveryAttestation, issueLifecycleRecoveryAttestation, validateLifecycleRecoveryAttestation } from './control-authority.js';
|
|
11
|
-
function buildIdentityDigest(value) {
|
|
12
|
-
return createHash('sha256').update(JSON.stringify(value), 'utf8').digest('hex');
|
|
13
|
-
}
|
|
5
|
+
import { activateMatchingOrchestrationDelegation, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, pauseMatchingOrchestrationDelegation, prepareOrchestrationDelegation, reconcileMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence } from './orchestration.js';
|
|
14
6
|
function coreOptions(options) {
|
|
15
7
|
return { ...options.orchestrationOptions, repoRoot: options.repoRoot ?? options.orchestrationOptions?.repoRoot };
|
|
16
8
|
}
|
|
17
|
-
function bridgeFailure(code, message
|
|
9
|
+
function bridgeFailure(code, message) {
|
|
18
10
|
return {
|
|
19
11
|
status: 'failed', changed: false, taskId: null, run: null, next: null,
|
|
20
|
-
error: { code, message
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
function capabilityFailure(error) {
|
|
24
|
-
const detailValue = error instanceof Error
|
|
25
|
-
? error.detail
|
|
26
|
-
: undefined;
|
|
27
|
-
return {
|
|
28
|
-
code: error instanceof Error && error.name.startsWith('CODEX_CAPABILITY_')
|
|
29
|
-
? error.name
|
|
30
|
-
: 'ORCHESTRATION_CLIENT_PREFLIGHT_FAILED',
|
|
31
|
-
message: error instanceof Error ? error.message : String(error),
|
|
32
|
-
...(error instanceof Error
|
|
33
|
-
&& error.name === 'CODEX_CAPABILITY_PROVENANCE_MISMATCH'
|
|
34
|
-
&& isCodexCapabilityProvenanceDetail(detailValue)
|
|
35
|
-
? { detail: detailValue }
|
|
36
|
-
: {})
|
|
12
|
+
error: { code, message }
|
|
37
13
|
};
|
|
38
14
|
}
|
|
39
15
|
function pauseBridge(code, message, options) {
|
|
@@ -47,28 +23,6 @@ function requiredStore(options) {
|
|
|
47
23
|
throw new Error('Codex lifecycle store is required for orchestration bridge events');
|
|
48
24
|
return options.store;
|
|
49
25
|
}
|
|
50
|
-
function brokerControllerBinding() {
|
|
51
|
-
const raw = process.env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING;
|
|
52
|
-
if (!raw)
|
|
53
|
-
return null;
|
|
54
|
-
let value;
|
|
55
|
-
try {
|
|
56
|
-
value = JSON.parse(raw);
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
|
|
60
|
-
}
|
|
61
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
62
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
|
|
63
|
-
}
|
|
64
|
-
const binding = value;
|
|
65
|
-
if (Object.keys(binding).sort().join(',') !== 'controlGeneration,instanceDigest'
|
|
66
|
-
|| typeof binding.instanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(binding.instanceDigest)
|
|
67
|
-
|| typeof binding.controlGeneration !== 'string' || binding.controlGeneration.length === 0) {
|
|
68
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
|
|
69
|
-
}
|
|
70
|
-
return binding;
|
|
71
|
-
}
|
|
72
26
|
async function prepareCodexOrchestrationDelegation(taskRef, input, options = {}) {
|
|
73
27
|
if (input.client !== 'codex')
|
|
74
28
|
return prepareOrchestrationDelegation(taskRef, input, coreOptions(options));
|
|
@@ -77,152 +31,13 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
|
|
|
77
31
|
const resolved = resolveTaskRef(taskRef, { repoRoot });
|
|
78
32
|
if (!resolved.ok)
|
|
79
33
|
return bridgeFailure(resolved.code, resolved.message);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!capabilityRef) {
|
|
83
|
-
return bridgeFailure('ORCHESTRATION_CODEX_CAPABILITY_REQUIRED', 'Codex prepare requires a current-session capability reference');
|
|
84
|
-
}
|
|
85
|
-
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
86
|
-
const brokerController = options.controllerBinding ?? brokerControllerBinding();
|
|
87
|
-
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
88
|
-
const contextVerification = contextPath
|
|
89
|
-
? (brokerController
|
|
90
|
-
? verifyControllerContextFileWithWarnings(contextPath, {
|
|
91
|
-
repoRoot,
|
|
92
|
-
generation: process.env.AGENT_INFRA_CONTROL_GENERATION ?? brokerController.controlGeneration
|
|
93
|
-
})
|
|
94
|
-
: verifySandboxControllerWithWarnings(contextPath, { repoRoot }))
|
|
95
|
-
: null;
|
|
96
|
-
const identityWarnings = [...(contextVerification?.warnings ?? [])];
|
|
97
|
-
const localController = contextVerification?.context ?? null;
|
|
98
|
-
if (localController && brokerController
|
|
99
|
-
&& (localController.controllerInstanceDigest !== brokerController.instanceDigest
|
|
100
|
-
|| localController.controlGeneration !== brokerController.controlGeneration)) {
|
|
101
|
-
return bridgeFailure('CODEX_SANDBOX_CONTROLLER_BINDING_MISMATCH', 'broker and local controller bindings do not match');
|
|
102
|
-
}
|
|
103
|
-
const controller = brokerController ?? (localController ? {
|
|
104
|
-
instanceDigest: localController.controllerInstanceDigest,
|
|
105
|
-
controlGeneration: localController.controlGeneration
|
|
106
|
-
} : null);
|
|
107
|
-
const capabilityStore = options.capabilityStore ?? createCodexCapabilityStore();
|
|
108
|
-
const attested = capabilityStore.inspectReference(capabilityRef);
|
|
109
|
-
const capabilityIdentity = verifyLifecycleBuildIdentity(attested.buildIdentity, buildIdentity);
|
|
110
|
-
if (!capabilityIdentity.ok) {
|
|
111
|
-
return bridgeFailure(capabilityIdentity.code, capabilityIdentity.message);
|
|
112
|
-
}
|
|
113
|
-
identityWarnings.push(...capabilityIdentity.warnings);
|
|
114
|
-
const lifecycleProvenance = {
|
|
115
|
-
...attested.buildIdentity,
|
|
116
|
-
hookDefinitionHash: preflight.hookDefinitionHash,
|
|
117
|
-
...preflight.hookProvenance,
|
|
118
|
-
capabilitySessionId: attested.sessionId,
|
|
119
|
-
capabilityTurnId: attested.turnId,
|
|
120
|
-
capabilityToolUseId: attested.toolUseId,
|
|
121
|
-
controllerInstanceDigest: controller?.instanceDigest ?? null,
|
|
122
|
-
controlGeneration: controller?.controlGeneration ?? null
|
|
123
|
-
};
|
|
124
|
-
const capabilityExpected = {
|
|
125
|
-
taskId: resolved.taskId,
|
|
126
|
-
hookDefinitionHash: preflight.hookDefinitionHash,
|
|
127
|
-
buildIdentity,
|
|
128
|
-
...(controller ? { controller: {
|
|
129
|
-
instanceDigest: controller.instanceDigest,
|
|
130
|
-
controlGeneration: controller.controlGeneration
|
|
131
|
-
} } : {})
|
|
132
|
-
};
|
|
133
|
-
if (input.lifecycleRecoveryAttestation) {
|
|
134
|
-
try {
|
|
135
|
-
validateLifecycleRecoveryAttestation(input.lifecycleRecoveryAttestation);
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
return bridgeFailure('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID', error instanceof Error ? error.message : String(error));
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
let lifecycleRecoveryAttestation = input.lifecycleRecoveryAttestation ?? null;
|
|
142
|
-
const routed = input.capabilityRef && !lifecycleRecoveryAttestation
|
|
143
|
-
? routeOrchestration(taskRef, coreOptions(options))
|
|
144
|
-
: null;
|
|
145
|
-
if (routed && !routed.next)
|
|
146
|
-
return routed;
|
|
147
|
-
if (routed?.next && ['analysis', 'plan', 'code'].includes(routed.next.stage)) {
|
|
148
|
-
const recoveryRequest = {
|
|
149
|
-
version: 1,
|
|
150
|
-
requestId: randomUUID(),
|
|
151
|
-
operationId: randomUUID(),
|
|
152
|
-
phase: 'orchestration.prepare',
|
|
153
|
-
taskId: resolved.taskId,
|
|
154
|
-
family: routed.next.stage === 'analysis' ? 'analysis'
|
|
155
|
-
: routed.next.stage === 'plan' ? 'plan' : 'code',
|
|
156
|
-
artifact: routed.next.artifact,
|
|
157
|
-
round: routed.next.round,
|
|
158
|
-
lifecycleRequestId: randomUUID(),
|
|
159
|
-
authorityRef: capabilityRef,
|
|
160
|
-
expectedControlGeneration: controller?.controlGeneration ?? '',
|
|
161
|
-
expectedControllerInstanceDigest: controller?.instanceDigest ?? '0'.repeat(64),
|
|
162
|
-
expectedBuildIdentityDigest: buildIdentityDigest(buildIdentity),
|
|
163
|
-
expectedHookDefinitionHash: preflight.hookDefinitionHash
|
|
164
|
-
};
|
|
165
|
-
const authority = issueLifecycleRecoveryAttestation(recoveryRequest, {
|
|
166
|
-
capabilityStore,
|
|
167
|
-
...(controller ? { controllerBinding: controller } : {}),
|
|
168
|
-
buildIdentity
|
|
169
|
-
});
|
|
170
|
-
if (authority.status === 'rejected' || !authority.attestation) {
|
|
171
|
-
return bridgeFailure(authority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED', authority.error?.message ?? 'lifecycle authority was rejected');
|
|
172
|
-
}
|
|
173
|
-
lifecycleRecoveryAttestation = authority.attestation;
|
|
174
|
-
}
|
|
175
|
-
const prepared = prepareOrchestrationDelegation(taskRef, {
|
|
176
|
-
...input,
|
|
177
|
-
lifecycleProvenance,
|
|
178
|
-
lifecycleRecoveryAttestation
|
|
179
|
-
}, {
|
|
180
|
-
...coreOptions(options),
|
|
181
|
-
validateLifecycleCapability: () => {
|
|
182
|
-
try {
|
|
183
|
-
const validated = capabilityStore.validateReference(capabilityRef, capabilityExpected);
|
|
184
|
-
if (validated.sessionId !== attested.sessionId
|
|
185
|
-
|| validated.turnId !== attested.turnId
|
|
186
|
-
|| validated.toolUseId !== attested.toolUseId) {
|
|
187
|
-
return {
|
|
188
|
-
code: 'CODEX_CAPABILITY_IDENTITY_CHANGED',
|
|
189
|
-
message: 'capability identity changed during validation'
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
catch (error) {
|
|
195
|
-
return capabilityFailure(error);
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
consumeLifecycleCapability: () => null
|
|
199
|
-
});
|
|
200
|
-
if (lifecycleRecoveryAttestation && !options.deferLifecycleRecoveryConsumption && prepared.status !== 'failed') {
|
|
201
|
-
try {
|
|
202
|
-
consumeLifecycleRecoveryAttestation(lifecycleRecoveryAttestation);
|
|
203
|
-
}
|
|
204
|
-
catch (error) {
|
|
205
|
-
return bridgeFailure(error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED', error instanceof Error ? error.message : String(error));
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return identityWarnings.length > 0 && prepared.status !== 'failed'
|
|
209
|
-
? { ...prepared, warnings: Object.freeze(identityWarnings) }
|
|
210
|
-
: prepared;
|
|
34
|
+
await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot);
|
|
35
|
+
return prepareOrchestrationDelegation(taskRef, input, coreOptions(options));
|
|
211
36
|
}
|
|
212
37
|
catch (error) {
|
|
213
38
|
if (error instanceof OrchestrationStateError)
|
|
214
39
|
return bridgeFailure(error.code, error.message);
|
|
215
|
-
|
|
216
|
-
? error.detail
|
|
217
|
-
: undefined;
|
|
218
|
-
const detail = error instanceof Error
|
|
219
|
-
&& error.name === 'CODEX_CAPABILITY_PROVENANCE_MISMATCH'
|
|
220
|
-
&& isCodexCapabilityProvenanceDetail(detailValue)
|
|
221
|
-
? detailValue
|
|
222
|
-
: undefined;
|
|
223
|
-
return bridgeFailure(error instanceof Error && error.name.startsWith('CODEX_CAPABILITY_')
|
|
224
|
-
? error.name
|
|
225
|
-
: 'ORCHESTRATION_CLIENT_PREFLIGHT_FAILED', error instanceof Error ? error.message : String(error), detail);
|
|
40
|
+
return bridgeFailure('ORCHESTRATION_CLIENT_PREFLIGHT_FAILED', error instanceof Error ? error.message : String(error));
|
|
226
41
|
}
|
|
227
42
|
}
|
|
228
43
|
async function activateCodexOrchestrationDelegation(childThreadId, options = {}) {
|
|
@@ -248,16 +63,6 @@ async function activateCodexOrchestrationDelegation(childThreadId, options = {})
|
|
|
248
63
|
toolUseId: evidence.spawnToolUseId
|
|
249
64
|
});
|
|
250
65
|
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
251
|
-
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
252
|
-
const contextVerification = contextPath
|
|
253
|
-
? verifySandboxControllerWithWarnings(contextPath, { repoRoot })
|
|
254
|
-
: null;
|
|
255
|
-
const controller = options.controllerBinding
|
|
256
|
-
?? brokerControllerBinding()
|
|
257
|
-
?? (contextVerification?.context ? {
|
|
258
|
-
instanceDigest: contextVerification.context.controllerInstanceDigest,
|
|
259
|
-
controlGeneration: contextVerification.context.controlGeneration
|
|
260
|
-
} : null);
|
|
261
66
|
const activated = activateMatchingOrchestrationDelegation('codex', {
|
|
262
67
|
nativeAgent: evidence.nativeAgent,
|
|
263
68
|
childId: evidence.childThreadId,
|
|
@@ -279,14 +84,11 @@ async function activateCodexOrchestrationDelegation(childThreadId, options = {})
|
|
|
279
84
|
capabilityTurnId: evidence.parentTurnId,
|
|
280
85
|
spawnToolUseId: evidence.spawnToolUseId,
|
|
281
86
|
spawnObservedAt: record.spawnObservedAt ?? undefined,
|
|
282
|
-
controllerInstanceDigest:
|
|
283
|
-
controlGeneration:
|
|
87
|
+
controllerInstanceDigest: null,
|
|
88
|
+
controlGeneration: null
|
|
284
89
|
}
|
|
285
90
|
}, coreOptions(options));
|
|
286
|
-
|
|
287
|
-
return warnings.length > 0 && activated.status !== 'failed'
|
|
288
|
-
? { ...activated, warnings: Object.freeze(warnings) }
|
|
289
|
-
: activated;
|
|
91
|
+
return activated;
|
|
290
92
|
}
|
|
291
93
|
catch (error) {
|
|
292
94
|
if (error instanceof OrchestrationStateError)
|
|
@@ -181,16 +181,6 @@ function checkpointIdentity(input, task, delivery) {
|
|
|
181
181
|
function checkpointTimestamp() {
|
|
182
182
|
return new Date().toISOString();
|
|
183
183
|
}
|
|
184
|
-
function samePreparedCheckpointIdentity(pending, identity) {
|
|
185
|
-
return pending.state === 'prepared'
|
|
186
|
-
&& pending.taskId === identity.taskId
|
|
187
|
-
&& pending.branch === identity.branch
|
|
188
|
-
&& pending.mode === identity.mode
|
|
189
|
-
&& pending.expectedHead === identity.expectedHead
|
|
190
|
-
&& pending.message === identity.message
|
|
191
|
-
&& pending.round === identity.round
|
|
192
|
-
&& JSON.stringify(pending.paths) === JSON.stringify(identity.paths);
|
|
193
|
-
}
|
|
194
184
|
function executeUnlocked(input, task, mode) {
|
|
195
185
|
const inspected = inspectGitWorkflow(input.cwd);
|
|
196
186
|
if (!inspected.snapshot)
|
|
@@ -225,16 +215,11 @@ function executeUnlocked(input, task, mode) {
|
|
|
225
215
|
};
|
|
226
216
|
}
|
|
227
217
|
if (pendingIntent && !sameCheckpointIntent(pendingIntent, identity)) {
|
|
228
|
-
|
|
229
|
-
return {
|
|
230
|
-
status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
|
|
231
|
-
error: { code: 'COMMIT_INTENT_CONFLICT', message: 'A different checkpoint intent is still pending for this task' }
|
|
232
|
-
};
|
|
218
|
+
const timestamp = checkpointTimestamp();
|
|
233
219
|
pendingIntent = {
|
|
234
|
-
...
|
|
235
|
-
expectedTree: identity.expectedTree,
|
|
220
|
+
version: 1, ...identity,
|
|
236
221
|
digest: checkpointIntentDigest(identity),
|
|
237
|
-
updatedAt:
|
|
222
|
+
state: 'prepared', committedHead: null, createdAt: timestamp, updatedAt: timestamp
|
|
238
223
|
};
|
|
239
224
|
try {
|
|
240
225
|
writeCheckpointIntent(task.repoRoot, pendingIntent);
|
|
@@ -535,17 +535,11 @@ function orchestration(context, operation) {
|
|
|
535
535
|
client: input.client,
|
|
536
536
|
requestedModel: input.requestedModel,
|
|
537
537
|
requestedReasoningEffort: input.requestedReasoningEffort,
|
|
538
|
-
...(input.capabilityRef === undefined ? {} : { capabilityRef: input.capabilityRef }),
|
|
539
|
-
...(context.lifecycleRecoveryAttestation === undefined
|
|
540
|
-
? {}
|
|
541
|
-
: { lifecycleRecoveryAttestation: context.lifecycleRecoveryAttestation })
|
|
542
538
|
};
|
|
543
539
|
if (prepareInput.client === 'codex') {
|
|
544
540
|
return prepareCodexOrchestrationDelegation(operation.taskRef, prepareInput, {
|
|
545
541
|
repoRoot: context.repoRoot,
|
|
546
542
|
orchestrationOptions: options,
|
|
547
|
-
deferLifecycleRecoveryConsumption: context.source === 'sandbox-executor',
|
|
548
|
-
...(context.controllerBinding ? { controllerBinding: context.controllerBinding } : {})
|
|
549
543
|
});
|
|
550
544
|
}
|
|
551
545
|
return prepareOrchestrationDelegation(operation.taskRef, prepareInput, options);
|
|
@@ -629,7 +623,7 @@ const FINALIZATION_FLAGS = new Set(['--agent']);
|
|
|
629
623
|
const ORCHESTRATION_FLAGS = new Set([
|
|
630
624
|
'--agent', '--max-steps', '--executor-model', '--executor-reasoning-effort', '--reviewer-model',
|
|
631
625
|
'--reviewer-reasoning-effort', '--client', '--requested-model', '--requested-reasoning-effort',
|
|
632
|
-
'--
|
|
626
|
+
'--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
|
|
633
627
|
'--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
|
|
634
628
|
'--model-fallback-reason', '--reasoning-effort-fallback-reason', '--exit-code', '--after-fingerprint',
|
|
635
629
|
'--changed-paths', '--code', '--message', '--recoverable', '--git-worktree-root'
|
|
@@ -769,7 +763,6 @@ export function parseTaskControlOperation(family, args) {
|
|
|
769
763
|
if (parsedIntent === 'prepare') {
|
|
770
764
|
input.requestedModel = value(values, '--requested-model');
|
|
771
765
|
input.requestedReasoningEffort = value(values, '--requested-reasoning-effort');
|
|
772
|
-
input.capabilityRef = value(values, '--capability-ref');
|
|
773
766
|
}
|
|
774
767
|
if (parsedIntent === 'await-activation') {
|
|
775
768
|
required(values, ['--stage', '--round', '--artifact', '--role']);
|