@fitlab-ai/agent-infra 0.11.11 → 0.11.13
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 +2 -2
- package/dist/lib/agent-clients/adapter.js +7 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
- package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
- package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
- package/dist/lib/agent-clients/adapters/codex.js +40 -1
- package/dist/lib/agent-clients/adapters/index.js +2 -10
- package/dist/lib/agent-clients/tokens.js +3 -2
- package/dist/lib/internal/agent-client.js +132 -72
- package/dist/lib/internal/cli-route-inventory.js +2 -2
- package/dist/lib/internal/sandbox-control.js +1 -81
- package/dist/lib/internal/task-finalization.js +26 -19
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -1
- package/dist/lib/internal/task-validate.js +1 -1
- package/dist/lib/platform/github-client.js +33 -3
- package/dist/lib/platform/issues.js +18 -6
- package/dist/lib/runtime/agent-runtime.js +6 -29
- package/dist/lib/sandbox/commands/control-status.js +1 -0
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/ls.js +4 -5
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/control/agent-operation.js +34 -0
- package/dist/lib/sandbox/control/client.js +14 -201
- package/dist/lib/sandbox/control/executor.js +12 -125
- package/dist/lib/sandbox/control/lifecycle.js +66 -4
- package/dist/lib/sandbox/control/protocol.js +15 -54
- package/dist/lib/sandbox/control/server.js +216 -281
- package/dist/lib/sandbox/control/task-view.js +0 -13
- package/dist/lib/sandbox/mounts.js +9 -1
- package/dist/lib/sandbox/recovery.js +31 -10
- package/dist/lib/sandbox/removal.js +28 -5
- package/dist/lib/sandbox/workspace-identity.js +18 -6
- package/dist/lib/sandbox/workspace-view.js +56 -19
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/control-authority.js +56 -264
- package/dist/lib/task/control-recovery.js +2 -3
- package/dist/lib/task/create.js +2 -2
- package/dist/lib/task/delegation-receipts.js +74 -161
- package/dist/lib/task/events.js +88 -44
- package/dist/lib/task/finalization.js +35 -6
- package/dist/lib/task/lifecycle.js +274 -115
- package/dist/lib/task/orchestration.js +201 -194
- package/dist/lib/task/runtime-paths.js +10 -0
- package/dist/lib/task/short-id.js +37 -27
- package/dist/lib/task/verification-engine.js +32 -26
- package/dist/lib/task/workspace-snapshot.js +1 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +39 -0
- package/lib/agent-clients/adapters/claude-code.ts +5 -1
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
- package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
- package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
- package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
- package/lib/agent-clients/adapters/codex.ts +44 -1
- package/lib/agent-clients/adapters/index.ts +7 -20
- package/lib/agent-clients/tokens.ts +4 -3
- package/lib/internal/agent-client.ts +142 -73
- package/lib/internal/cli-route-inventory.ts +2 -2
- package/lib/internal/sandbox-control.ts +0 -97
- package/lib/internal/task-finalization.ts +30 -18
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -1
- package/lib/internal/task-validate.ts +1 -1
- package/lib/platform/github-client.ts +34 -3
- package/lib/platform/issues.ts +18 -6
- package/lib/runtime/agent-runtime.ts +6 -41
- package/lib/sandbox/commands/control-status.ts +1 -0
- package/lib/sandbox/commands/create.ts +7 -1
- package/lib/sandbox/commands/ls.ts +4 -5
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/control/agent-operation.ts +49 -0
- package/lib/sandbox/control/client.ts +20 -254
- package/lib/sandbox/control/executor.ts +13 -136
- package/lib/sandbox/control/lifecycle.ts +67 -4
- package/lib/sandbox/control/protocol.ts +22 -66
- package/lib/sandbox/control/server.ts +21 -95
- package/lib/sandbox/control/task-view.ts +0 -19
- package/lib/sandbox/mounts.ts +10 -1
- package/lib/sandbox/recovery.ts +38 -17
- package/lib/sandbox/removal.ts +33 -7
- package/lib/sandbox/workspace-identity.ts +16 -6
- package/lib/sandbox/workspace-view.ts +50 -18
- package/lib/task/commands/ls.ts +3 -3
- package/lib/task/control-authority.ts +66 -378
- package/lib/task/control-recovery.ts +3 -4
- package/lib/task/create.ts +3 -3
- package/lib/task/delegation-receipts.ts +87 -231
- package/lib/task/events.ts +100 -42
- package/lib/task/finalization.ts +37 -7
- package/lib/task/lifecycle.ts +257 -109
- package/lib/task/orchestration.ts +260 -237
- package/lib/task/runtime-paths.ts +12 -0
- package/lib/task/short-id.ts +33 -40
- package/lib/task/verification-engine.ts +30 -25
- package/lib/task/workspace-snapshot.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
- package/templates/.agents/rules/README.en.md +2 -1
- package/templates/.agents/rules/README.zh-CN.md +2 -1
- package/templates/.agents/rules/next-step-output.en.md +6 -19
- package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
- package/templates/.agents/rules/strategy-pattern.en.md +25 -0
- package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
- package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +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/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
- package/dist/lib/internal/codex-lifecycle.js +0 -385
- package/dist/lib/task/codex-orchestration.js +0 -206
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
- package/lib/internal/codex-lifecycle.ts +0 -390
- package/lib/task/codex-orchestration.ts +0 -276
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { resolveTaskRef } from './resolve-ref.js';
|
|
4
|
+
export function resolveTaskRuntimeRoot(taskRef, options = {}) {
|
|
5
|
+
const resolved = resolveTaskRef(taskRef, options);
|
|
6
|
+
if (!resolved.ok)
|
|
7
|
+
throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
8
|
+
return path.join(fs.realpathSync.native(resolved.taskDir), '.runtime');
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=runtime-paths.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
3
4
|
const REGISTRY_NAME = '.short-ids.json';
|
|
4
5
|
const LOCK_NAME = '.short-ids.json.lock';
|
|
5
6
|
function normalizeShortIdInput(input, opts) {
|
|
@@ -90,20 +91,33 @@ function resolveShortIdReadOnly(input, repoRoot, opts) {
|
|
|
90
91
|
const taskId = ids[key];
|
|
91
92
|
if (!taskId) {
|
|
92
93
|
const message = Object.keys(ids).length === 0
|
|
93
|
-
? `short id '${key}' not found;
|
|
94
|
-
: `short id '${key}' not found in
|
|
94
|
+
? `short id '${key}' not found; task short-id registry is empty.`
|
|
95
|
+
: `short id '${key}' not found in task short-id registry.`;
|
|
95
96
|
return shortIdFailure('SHORT_ID_NOT_FOUND', message);
|
|
96
97
|
}
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
const activeTask = path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md');
|
|
99
|
+
const completedTask = path.join(repoRoot, '.agents', 'workspace', 'completed', taskId, 'task.md');
|
|
100
|
+
let isValidCompletedTask = false;
|
|
101
|
+
try {
|
|
102
|
+
const task = parseTypedTaskFrontmatter(fs.readFileSync(completedTask, 'utf8'));
|
|
103
|
+
isValidCompletedTask = task.id === taskId && task.status === 'completed';
|
|
104
|
+
}
|
|
105
|
+
catch { /* a mapping is valid only while its task content is present */ }
|
|
106
|
+
if (!fs.existsSync(activeTask) && !isValidCompletedTask) {
|
|
107
|
+
return shortIdFailure('SHORT_ID_STALE', `short id '${key}' points to no active or completed task content.`, taskId);
|
|
104
108
|
}
|
|
105
109
|
return { ok: true, taskId };
|
|
106
110
|
}
|
|
111
|
+
function hasRetainedCompletedTask(activeDir, taskId) {
|
|
112
|
+
try {
|
|
113
|
+
const taskPath = path.join(path.dirname(activeDir), 'completed', taskId, 'task.md');
|
|
114
|
+
const task = parseTypedTaskFrontmatter(fs.readFileSync(taskPath, 'utf8'));
|
|
115
|
+
return task.id === taskId && task.status === 'completed';
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
107
121
|
function configuredShortIdLength(repoRoot) {
|
|
108
122
|
try {
|
|
109
123
|
const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
|
|
@@ -206,7 +220,8 @@ function mutateShortIdRegistryAt(activeDir, width, taskId, requested, onlyReleas
|
|
|
206
220
|
const original = JSON.stringify(registry.ids);
|
|
207
221
|
if (pruneOrphans) {
|
|
208
222
|
for (const [key, candidate] of Object.entries(registry.ids)) {
|
|
209
|
-
if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md'))
|
|
223
|
+
if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md'))
|
|
224
|
+
&& !hasRetainedCompletedTask(activeDir, candidate))
|
|
210
225
|
delete registry.ids[key];
|
|
211
226
|
}
|
|
212
227
|
}
|
|
@@ -277,34 +292,29 @@ function executeShortIdCommand(request) {
|
|
|
277
292
|
if (normalized.kind !== 'shortId') {
|
|
278
293
|
throw Object.assign(new Error(`invalid short id format '${request.argument}'`), { code: 'SHORT_ID_FORMAT_INVALID' });
|
|
279
294
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (changed)
|
|
290
|
-
writeRegistry(registryPath, registry);
|
|
291
|
-
const taskId = registry.ids[normalized.value];
|
|
292
|
-
if (!taskId)
|
|
293
|
-
throw Object.assign(new Error(`short id '${normalized.value}' not found in active task registry`), { code: 'SHORT_ID_NOT_FOUND' });
|
|
294
|
-
return { status: changed ? 'applied' : 'no-op', changed, output: taskId, error: null };
|
|
295
|
-
});
|
|
295
|
+
const registry = readRegistryStrict(registryPath, request.shortIdLength);
|
|
296
|
+
const taskId = registry.ids[normalized.value];
|
|
297
|
+
if (!taskId)
|
|
298
|
+
throw Object.assign(new Error(`short id '${normalized.value}' not found in task short-id registry`), { code: 'SHORT_ID_NOT_FOUND' });
|
|
299
|
+
if (!fs.existsSync(path.join(request.activeDir, taskId, 'task.md'))
|
|
300
|
+
&& !hasRetainedCompletedTask(request.activeDir, taskId)) {
|
|
301
|
+
throw Object.assign(new Error(`short id '${normalized.value}' points to a task without active or completed task content`), { code: 'SHORT_ID_STALE' });
|
|
302
|
+
}
|
|
303
|
+
return { status: 'no-op', changed: false, output: taskId, error: null };
|
|
296
304
|
}
|
|
297
305
|
const registry = readRegistryStrict(registryPath, request.shortIdLength);
|
|
298
306
|
if (request.verify) {
|
|
299
307
|
const active = fs.existsSync(request.activeDir)
|
|
300
308
|
? fs.readdirSync(request.activeDir).filter((entry) => /^TASK-\d{8}-\d{6}$/.test(entry) && fs.existsSync(path.join(request.activeDir, entry, 'task.md')))
|
|
301
309
|
: [];
|
|
310
|
+
const retained = Object.values(registry.ids).filter((taskId) => hasRetainedCompletedTask(request.activeDir, taskId));
|
|
311
|
+
const validTasks = new Set([...active, ...retained]);
|
|
302
312
|
const registered = new Map();
|
|
303
313
|
for (const [key, taskId] of Object.entries(registry.ids))
|
|
304
314
|
registered.set(taskId, [...(registered.get(taskId) ?? []), key]);
|
|
305
315
|
const diff = {
|
|
306
316
|
missing_in_registry: active.filter((taskId) => !registered.has(taskId)).map((taskId) => ({ taskId })),
|
|
307
|
-
orphans_in_registry: Object.entries(registry.ids).filter(([, taskId]) => !
|
|
317
|
+
orphans_in_registry: Object.entries(registry.ids).filter(([, taskId]) => !validTasks.has(taskId)).map(([key, taskId]) => ({ key, taskId })),
|
|
308
318
|
duplicate_registry_keys: [...registered.entries()].filter(([, keys]) => keys.length > 1).map(([taskId, keys]) => ({ taskId, keys }))
|
|
309
319
|
};
|
|
310
320
|
const clean = Object.values(diff).every((items) => items.length === 0);
|
|
@@ -31,6 +31,7 @@ import { manualValidationFinalSummaryProjectionMatches } from "./manual-validati
|
|
|
31
31
|
import { readManualValidationCompletion } from "./manual-validation-completion.js";
|
|
32
32
|
import { sha256File } from "./artifact-receipts.js";
|
|
33
33
|
import { inspectArtifactDirectory } from "./artifact-lifecycle.js";
|
|
34
|
+
import { getAgentClientAdapter } from "../agent-clients/registry.js";
|
|
34
35
|
import { parseArtifactName } from "./artifact-name.js";
|
|
35
36
|
import { summaryCommentState } from "../platform/pr-summary.js";
|
|
36
37
|
import { taskIssueIdentity } from "../platform/task-identities.js";
|
|
@@ -124,7 +125,7 @@ async function runCheck(type, context, shared) {
|
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
function checkOrchestrationState({ taskDir }) {
|
|
127
|
-
const file = path.join(taskDir, 'orchestration.json');
|
|
128
|
+
const file = path.join(taskDir, '.runtime', 'orchestration.json');
|
|
128
129
|
const stat = safeStat(file);
|
|
129
130
|
if (!stat?.isFile())
|
|
130
131
|
return failResult('orchestration-state', 'orchestration.json is missing');
|
|
@@ -139,18 +140,16 @@ function checkOrchestrationState({ taskDir }) {
|
|
|
139
140
|
if (!run || !['paused', 'completed'].includes(run.status)) {
|
|
140
141
|
return failResult('orchestration-state', `Expected paused or completed run, received '${run?.status ?? 'missing'}'`);
|
|
141
142
|
}
|
|
143
|
+
const adapterEvidenceError = validateAdapterReceiptEvidence(run);
|
|
144
|
+
if (adapterEvidenceError)
|
|
145
|
+
return failResult('orchestration-state', adapterEvidenceError);
|
|
142
146
|
if (run.status === 'paused' && (!run.pause?.code || !run.pause?.message)) {
|
|
143
147
|
return failResult('orchestration-state', 'Paused run requires a stable pause code and message');
|
|
144
148
|
}
|
|
145
149
|
if (run.status === 'completed') {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return failResult('orchestration-state', evidenceError);
|
|
150
|
-
}
|
|
151
|
-
else if (run.pendingDelegation !== null || !run.commitAuthorization?.consumedAt) {
|
|
152
|
-
return failResult('orchestration-state', 'Completed run must consume commit authorization and clear pending delegation');
|
|
153
|
-
}
|
|
150
|
+
const evidenceError = validateCleanCompletionEvidence(run);
|
|
151
|
+
if (evidenceError)
|
|
152
|
+
return failResult('orchestration-state', evidenceError);
|
|
154
153
|
}
|
|
155
154
|
else if (run.completionEvidence != null) {
|
|
156
155
|
return failResult('orchestration-state', 'Clean completion evidence requires a completed run');
|
|
@@ -182,15 +181,23 @@ function validateCleanCompletionEvidence(run) {
|
|
|
182
181
|
}
|
|
183
182
|
if (run.status !== 'completed'
|
|
184
183
|
|| run.pendingDelegation !== null
|
|
185
|
-
|| run.commitAuthorization?.issuedAt !== null
|
|
186
|
-
|| run.commitAuthorization?.consumedAt !== null
|
|
187
184
|
|| run.receipts.some((receipt) => receipt.stage === 'commit')) {
|
|
188
|
-
return 'Clean completion evidence conflicts with
|
|
185
|
+
return 'Clean completion evidence conflicts with pending delegation or commit receipt state';
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
function validateAdapterReceiptEvidence(run) {
|
|
190
|
+
const receipts = [...run.receipts, ...(run.pendingDelegation ? [run.pendingDelegation] : [])];
|
|
191
|
+
for (const receipt of receipts) {
|
|
192
|
+
const validate = getAgentClientAdapter(receipt.client).orchestrationAdapter?.validateReceiptEvidence;
|
|
193
|
+
if (validate && !validate(receipt)) {
|
|
194
|
+
return `Receipt '${receipt.id}' has invalid evidence from its agent adapter`;
|
|
195
|
+
}
|
|
189
196
|
}
|
|
190
197
|
return null;
|
|
191
198
|
}
|
|
192
199
|
function checkOrchestrationEvidence({ taskDir }) {
|
|
193
|
-
const file = path.join(taskDir, 'orchestration.json');
|
|
200
|
+
const file = path.join(taskDir, '.runtime', 'orchestration.json');
|
|
194
201
|
const stat = safeStat(file);
|
|
195
202
|
if (!stat?.isFile())
|
|
196
203
|
return failResult('orchestration-evidence', 'orchestration.json is missing');
|
|
@@ -204,6 +211,9 @@ function checkOrchestrationEvidence({ taskDir }) {
|
|
|
204
211
|
}
|
|
205
212
|
if (!run)
|
|
206
213
|
return failResult('orchestration-evidence', 'orchestration.json is missing');
|
|
214
|
+
const adapterEvidenceError = validateAdapterReceiptEvidence(run);
|
|
215
|
+
if (adapterEvidenceError)
|
|
216
|
+
return failResult('orchestration-evidence', adapterEvidenceError);
|
|
207
217
|
// readRun validates the persisted schema, receipt identities and client provenance.
|
|
208
218
|
const policy = run.modelPolicy;
|
|
209
219
|
if (run.completionEvidence != null) {
|
|
@@ -228,20 +238,22 @@ function checkOrchestrationEvidence({ taskDir }) {
|
|
|
228
238
|
}
|
|
229
239
|
const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
|
|
230
240
|
if (activated) {
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
241
|
+
const evidencePolicy = getAgentClientAdapter(receipt.client).delegationEvidence;
|
|
242
|
+
if (evidencePolicy.actualModelRequired !== false) {
|
|
233
243
|
if (!exactText(receipt.actualModel)) {
|
|
234
244
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual model`);
|
|
235
245
|
}
|
|
246
|
+
}
|
|
247
|
+
if (evidencePolicy.actualReasoningEffortRequired !== false) {
|
|
236
248
|
if (!exactText(receipt.actualReasoningEffort)) {
|
|
237
249
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual reasoning effort`);
|
|
238
250
|
}
|
|
239
251
|
}
|
|
240
|
-
if (
|
|
252
|
+
if (evidencePolicy.spawnModeRequired !== false && receipt.spawnMode !== 'fresh') {
|
|
241
253
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid fresh delegation identity`);
|
|
242
254
|
}
|
|
243
255
|
if (receipt.actualModel != null && receipt.actualModel !== receipt.requestedModel) {
|
|
244
|
-
if (
|
|
256
|
+
if (evidencePolicy.fallbackReasonRequired !== false && !exactText(receipt.modelFallbackReason)) {
|
|
245
257
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' model fallback is not justified`);
|
|
246
258
|
}
|
|
247
259
|
}
|
|
@@ -250,7 +262,7 @@ function checkOrchestrationEvidence({ taskDir }) {
|
|
|
250
262
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated model fallback reason`);
|
|
251
263
|
}
|
|
252
264
|
if (receipt.actualReasoningEffort != null && receipt.actualReasoningEffort !== receipt.requestedReasoningEffort) {
|
|
253
|
-
if (
|
|
265
|
+
if (evidencePolicy.fallbackReasonRequired !== false && !exactText(receipt.reasoningEffortFallbackReason)) {
|
|
254
266
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' reasoning-effort fallback is not justified`);
|
|
255
267
|
}
|
|
256
268
|
}
|
|
@@ -700,7 +712,6 @@ function checkActivityLog({ taskDir, config, skillName, artifactFile }) {
|
|
|
700
712
|
const entries = section.entries;
|
|
701
713
|
if (entries.length === 0)
|
|
702
714
|
return failResult("activity-log", "Activity Log has no entries");
|
|
703
|
-
let previousTimestamp = "";
|
|
704
715
|
let latestAction = "";
|
|
705
716
|
let latestTimestamp = "";
|
|
706
717
|
const doneActions = [];
|
|
@@ -708,13 +719,8 @@ function checkActivityLog({ taskDir, config, skillName, artifactFile }) {
|
|
|
708
719
|
if (!entry.note.trim())
|
|
709
720
|
return failResult("activity-log", "Activity Log entry note is required");
|
|
710
721
|
const { time: timestamp, step: action } = entry;
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
previousTimestamp = timestamp;
|
|
715
|
-
// Ascending order is checked over every entry, but a `[started]` marker is
|
|
716
|
-
// not a terminal action: keep latestAction/latestTimestamp on the most
|
|
717
|
-
// recent done entry so expected_action_pattern sees it.
|
|
722
|
+
// A `[started]` marker is not a terminal action: keep latestAction/latestTimestamp
|
|
723
|
+
// on the most recent done entry so expected_action_pattern sees it.
|
|
718
724
|
if (!ACTIVITY_LOG_STARTED_RE.test(action)) {
|
|
719
725
|
latestTimestamp = timestamp;
|
|
720
726
|
latestAction = action;
|
|
@@ -45,7 +45,7 @@ function taskFileFingerprints(stateRoot, taskId) {
|
|
|
45
45
|
}
|
|
46
46
|
if (!stat.isFile() && !stat.isSymbolicLink())
|
|
47
47
|
return;
|
|
48
|
-
if (
|
|
48
|
+
if (/^\.agents\/workspace\/(?:active|blocked|completed)\/TASK-[0-9]{8}-[0-9]{6}\/\.runtime\/orchestration\.json$/u.test(relative))
|
|
49
49
|
return;
|
|
50
50
|
const content = stat.isSymbolicLink()
|
|
51
51
|
? Buffer.from(fs.readlinkSync(absolute))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.11.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.13",
|
|
4
|
+
"sourceInputHash": "70e89f362895c1f8f976011715a86b47e06c105ca4285eacbb3fa51d0459a282",
|
|
5
|
+
"internalExecutableBuildHash": "f183e6d39ba985dbb2962eb951b4019af9594361ca3c46b88bf65fff325d7ad4"
|
|
6
6
|
}
|
|
@@ -23,6 +23,27 @@ import type {
|
|
|
23
23
|
SandboxTool,
|
|
24
24
|
SandboxToolContext
|
|
25
25
|
} from '../sandbox/tool-types.ts';
|
|
26
|
+
import type { SandboxControlManifest } from '../sandbox/control/protocol.ts';
|
|
27
|
+
|
|
28
|
+
type AgentClientSandboxControlOperation = Readonly<{
|
|
29
|
+
validate: (params: Readonly<{
|
|
30
|
+
operation: string;
|
|
31
|
+
payload: Record<string, unknown>;
|
|
32
|
+
manifest: SandboxControlManifest;
|
|
33
|
+
}>) => void;
|
|
34
|
+
execute: (params: Readonly<{
|
|
35
|
+
operation: string;
|
|
36
|
+
payload: Record<string, unknown>;
|
|
37
|
+
manifest: SandboxControlManifest;
|
|
38
|
+
manifestPath: string;
|
|
39
|
+
}>) => Promise<Readonly<{ exitCode: number; stdout: string; stderr: string }>>;
|
|
40
|
+
recover?: (params: Readonly<{
|
|
41
|
+
operation: string;
|
|
42
|
+
manifest: SandboxControlManifest;
|
|
43
|
+
manifestPath: string;
|
|
44
|
+
stdout: string | null;
|
|
45
|
+
}>) => Readonly<Record<string, unknown>>;
|
|
46
|
+
}>;
|
|
26
47
|
|
|
27
48
|
type AgentClientCapabilities = AgentClientCapabilityMap;
|
|
28
49
|
|
|
@@ -46,6 +67,10 @@ type AgentClientModelSelectionContext =
|
|
|
46
67
|
type AgentClientDelegationEvidence = Readonly<{
|
|
47
68
|
actualModel: 'host-event' | 'app-server' | 'unavailable';
|
|
48
69
|
actualReasoningEffort: 'host-event' | 'app-server' | 'spawn-ack' | 'unavailable';
|
|
70
|
+
spawnModeRequired?: boolean;
|
|
71
|
+
actualModelRequired?: boolean;
|
|
72
|
+
actualReasoningEffortRequired?: boolean;
|
|
73
|
+
fallbackReasonRequired?: boolean;
|
|
49
74
|
}>;
|
|
50
75
|
|
|
51
76
|
type AgentClientLifecycleRecoveryRequest = Readonly<{
|
|
@@ -68,6 +93,12 @@ type AgentClientLifecycleRecoveryResult = Readonly<{
|
|
|
68
93
|
}>;
|
|
69
94
|
|
|
70
95
|
type AgentClientOrchestrationAdapter = Readonly<{
|
|
96
|
+
validateActivationEvidence?: (evidence: unknown, parentId: string) => boolean;
|
|
97
|
+
validateReceiptEvidence?: (receipt: unknown) => boolean;
|
|
98
|
+
createLaunchCarrier?: (
|
|
99
|
+
adapterContext: string,
|
|
100
|
+
identity: Readonly<{ stage: string; round: number; role: string }>
|
|
101
|
+
) => Readonly<Record<string, string>>;
|
|
71
102
|
prepareDelegation?: (
|
|
72
103
|
taskRef: string,
|
|
73
104
|
input: Readonly<{
|
|
@@ -116,6 +147,7 @@ type AgentClientAdapter = Readonly<{
|
|
|
116
147
|
modelSelection: AgentClientModelSelectionContext;
|
|
117
148
|
delegationEvidence: AgentClientDelegationEvidence;
|
|
118
149
|
orchestrationAdapter?: AgentClientOrchestrationAdapter;
|
|
150
|
+
sandboxControlOperation?: AgentClientSandboxControlOperation;
|
|
119
151
|
project: AgentClientProjectDescriptor;
|
|
120
152
|
sandbox: AgentClientSandboxDescriptor;
|
|
121
153
|
}>;
|
|
@@ -395,6 +427,9 @@ function defineAgentClientAdapter(
|
|
|
395
427
|
!evidence
|
|
396
428
|
|| !['host-event', 'app-server', 'unavailable'].includes(evidence.actualModel)
|
|
397
429
|
|| !['host-event', 'app-server', 'spawn-ack', 'unavailable'].includes(evidence.actualReasoningEffort)
|
|
430
|
+
|| ['spawnModeRequired', 'actualModelRequired', 'actualReasoningEffortRequired', 'fallbackReasonRequired']
|
|
431
|
+
.some((key) => evidence[key as keyof AgentClientDelegationEvidence] !== undefined
|
|
432
|
+
&& typeof evidence[key as keyof AgentClientDelegationEvidence] !== 'boolean')
|
|
398
433
|
) {
|
|
399
434
|
throw new Error(`Agent Client '${candidate.id}' has invalid delegation evidence`);
|
|
400
435
|
}
|
|
@@ -693,6 +728,9 @@ function defineAgentClientAdapter(
|
|
|
693
728
|
...(candidate.orchestrationAdapter === undefined
|
|
694
729
|
? {}
|
|
695
730
|
: { orchestrationAdapter: Object.freeze({ ...candidate.orchestrationAdapter }) }),
|
|
731
|
+
...(candidate.sandboxControlOperation === undefined
|
|
732
|
+
? {}
|
|
733
|
+
: { sandboxControlOperation: Object.freeze({ ...candidate.sandboxControlOperation }) }),
|
|
696
734
|
project: Object.freeze({
|
|
697
735
|
ownedPathPrefixes: Object.freeze(paths),
|
|
698
736
|
...projectAssets,
|
|
@@ -715,6 +753,7 @@ export type {
|
|
|
715
753
|
AgentClientCapabilities,
|
|
716
754
|
AgentClientCustomCommandDescriptor,
|
|
717
755
|
AgentClientDelegationEvidence,
|
|
756
|
+
AgentClientSandboxControlOperation,
|
|
718
757
|
AgentClientLifecycleRecoveryRequest,
|
|
719
758
|
AgentClientLifecycleRecoveryResult,
|
|
720
759
|
AgentClientOrchestrationAdapter,
|
|
@@ -27,7 +27,11 @@ const claudeCodeAdapter = defineAgentClientAdapter({
|
|
|
27
27
|
},
|
|
28
28
|
delegationEvidence: {
|
|
29
29
|
actualModel: 'host-event',
|
|
30
|
-
actualReasoningEffort: 'spawn-ack'
|
|
30
|
+
actualReasoningEffort: 'spawn-ack',
|
|
31
|
+
spawnModeRequired: false,
|
|
32
|
+
actualModelRequired: false,
|
|
33
|
+
actualReasoningEffortRequired: false,
|
|
34
|
+
fallbackReasonRequired: false
|
|
31
35
|
},
|
|
32
36
|
project: {
|
|
33
37
|
ownedPathPrefixes: ['.claude/'],
|
|
@@ -7,7 +7,9 @@ import semver from 'semver';
|
|
|
7
7
|
|
|
8
8
|
import type { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
9
9
|
import type { CodexLifecycleEvent } from './evidence.ts';
|
|
10
|
-
import {
|
|
10
|
+
import { parseCodexLifecycleBinding } from './binding.ts';
|
|
11
|
+
import { hasActiveCodexLifecycleEvidence } from './store.ts';
|
|
12
|
+
import { resolveTaskContext, resolveTaskRef } from '../../../task/resolve-ref.ts';
|
|
11
13
|
|
|
12
14
|
type JsonObject = Record<string, unknown>;
|
|
13
15
|
type ThreadResolution = Readonly<{
|
|
@@ -71,26 +73,14 @@ function nonEmpty(value: unknown): string | null {
|
|
|
71
73
|
return typeof value === 'string' && value.trim() ? value : null;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
function
|
|
75
|
-
transcriptPath: string,
|
|
76
|
-
expected: Readonly<{
|
|
77
|
-
sessionId: string;
|
|
78
|
-
turnId: string;
|
|
79
|
-
toolUseId: string;
|
|
80
|
-
nativeAgent: string;
|
|
81
|
-
taskName: string;
|
|
82
|
-
requestedModel?: string;
|
|
83
|
-
requestedReasoningEffort?: string;
|
|
84
|
-
}>
|
|
85
|
-
): string {
|
|
76
|
+
function readRolloutRecords(transcriptPath: string, sessionId: string): JsonObject[] {
|
|
86
77
|
const name = path.basename(transcriptPath);
|
|
87
|
-
if (!transcriptPath || (!name.endsWith(`-${
|
|
78
|
+
if (!transcriptPath || (!name.endsWith(`-${sessionId}.jsonl`) && name !== `${sessionId}.jsonl`)) {
|
|
88
79
|
throw new Error('Codex parent rollout path is invalid');
|
|
89
80
|
}
|
|
90
81
|
const stat = fs.lstatSync(transcriptPath);
|
|
91
82
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('Codex parent rollout is not a regular file');
|
|
92
|
-
const
|
|
93
|
-
const offset = Math.max(0, stat.size - maxBytes);
|
|
83
|
+
const offset = Math.max(0, stat.size - 8 * 1024 * 1024);
|
|
94
84
|
const buffer = Buffer.alloc(stat.size - offset);
|
|
95
85
|
const descriptor = fs.openSync(transcriptPath, 'r');
|
|
96
86
|
try {
|
|
@@ -100,37 +90,61 @@ function resolveCodexSpawnedChild(
|
|
|
100
90
|
}
|
|
101
91
|
let text = buffer.toString('utf8');
|
|
102
92
|
if (offset) text = text.slice(text.indexOf('\n') + 1);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
93
|
+
return text.split(/\r?\n/u).filter(Boolean).map((line) => object(JSON.parse(line))).filter((record): record is JsonObject => Boolean(record));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isExpectedSpawnCall(record: JsonObject, expected: Parameters<typeof resolveCodexSpawnedChild>[1]): boolean {
|
|
97
|
+
const payload = object(record.payload);
|
|
98
|
+
return record.type === 'response_item'
|
|
99
|
+
&& payload?.type === 'function_call'
|
|
100
|
+
&& payload.call_id === expected.toolUseId
|
|
101
|
+
&& payload.namespace === 'collaboration'
|
|
102
|
+
&& payload.name === 'spawn_agent';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function spawnArgsMatch(args: JsonObject | null, expected: Parameters<typeof resolveCodexSpawnedChild>[1]): boolean {
|
|
106
|
+
return args?.agent_type === expected.nativeAgent
|
|
107
|
+
&& args.task_name === expected.taskName
|
|
108
|
+
&& (!expected.requestedModel || args.model === expected.requestedModel)
|
|
109
|
+
&& (!expected.requestedReasoningEffort || args.reasoning_effort === expected.requestedReasoningEffort);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isExpectedChildActivity(record: JsonObject, expected: Parameters<typeof resolveCodexSpawnedChild>[1]): boolean {
|
|
113
|
+
const payload = object(record.payload);
|
|
114
|
+
const item = object(payload?.item);
|
|
115
|
+
return record.type === 'event_msg'
|
|
116
|
+
&& payload?.type === 'item_completed'
|
|
117
|
+
&& payload.thread_id === expected.sessionId
|
|
118
|
+
&& payload.turn_id === expected.turnId
|
|
119
|
+
&& item?.type === 'SubAgentActivity'
|
|
120
|
+
&& item.id === expected.toolUseId
|
|
121
|
+
&& item.kind === 'started'
|
|
122
|
+
&& item.agent_path === `/root/${expected.taskName}`
|
|
123
|
+
&& Boolean(nonEmpty(item.agent_thread_id));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function resolveCodexSpawnedChild(
|
|
127
|
+
transcriptPath: string,
|
|
128
|
+
expected: Readonly<{
|
|
129
|
+
sessionId: string;
|
|
130
|
+
turnId: string;
|
|
131
|
+
toolUseId: string;
|
|
132
|
+
nativeAgent: string;
|
|
133
|
+
taskName: string;
|
|
134
|
+
requestedModel?: string;
|
|
135
|
+
requestedReasoningEffort?: string;
|
|
136
|
+
}>
|
|
137
|
+
): string {
|
|
138
|
+
if (!parseCodexLifecycleBinding(expected.taskName)) {
|
|
139
|
+
throw new Error('Codex spawn task_name does not carry a valid lifecycle binding');
|
|
140
|
+
}
|
|
141
|
+
const records = readRolloutRecords(transcriptPath, expected.sessionId);
|
|
142
|
+
const calls = records.filter((record) => isExpectedSpawnCall(record, expected));
|
|
112
143
|
if (calls.length !== 1) throw new Error('Codex parent rollout spawn call was not found uniquely');
|
|
113
144
|
const call = object(calls[0]?.payload);
|
|
114
145
|
const args = object(JSON.parse(nonEmpty(call?.arguments) ?? '{}'));
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|| args.task_name !== expected.taskName
|
|
118
|
-
|| (expected.requestedModel && args.model !== expected.requestedModel)
|
|
119
|
-
|| (expected.requestedReasoningEffort && args.reasoning_effort !== expected.requestedReasoningEffort)
|
|
120
|
-
) throw new Error('Codex parent rollout spawn identity does not match the hook event');
|
|
121
|
-
const activities = records.filter((record) => {
|
|
122
|
-
const payload = object(record?.payload);
|
|
123
|
-
const item = object(payload?.item);
|
|
124
|
-
return record?.type === 'event_msg'
|
|
125
|
-
&& payload?.type === 'item_completed'
|
|
126
|
-
&& payload.thread_id === expected.sessionId
|
|
127
|
-
&& payload.turn_id === expected.turnId
|
|
128
|
-
&& item?.type === 'SubAgentActivity'
|
|
129
|
-
&& item.id === expected.toolUseId
|
|
130
|
-
&& item.kind === 'started'
|
|
131
|
-
&& item.agent_path === `/root/${expected.taskName}`
|
|
132
|
-
&& nonEmpty(item.agent_thread_id);
|
|
133
|
-
});
|
|
146
|
+
if (!spawnArgsMatch(args, expected)) throw new Error('Codex parent rollout spawn identity does not match the hook event');
|
|
147
|
+
const activities = records.filter((record) => isExpectedChildActivity(record, expected));
|
|
134
148
|
if (activities.length !== 1) throw new Error('Codex parent rollout child activity was not found uniquely');
|
|
135
149
|
return nonEmpty(object(object(activities[0]?.payload)?.item)?.agent_thread_id)!;
|
|
136
150
|
}
|
|
@@ -198,30 +212,6 @@ function parseCodexHooksList(
|
|
|
198
212
|
return Object.freeze(hooks);
|
|
199
213
|
}
|
|
200
214
|
|
|
201
|
-
function hasCodexRuntimeLiveness(
|
|
202
|
-
runtimeRoot: string,
|
|
203
|
-
hookDefinitionHash: string,
|
|
204
|
-
identity?: CodexRuntimeIdentity
|
|
205
|
-
): boolean {
|
|
206
|
-
if (!identity || !fs.existsSync(runtimeRoot)) return false;
|
|
207
|
-
return fs.readdirSync(runtimeRoot)
|
|
208
|
-
.filter((name) => /^[a-f0-9]{64}\.json$/.test(name))
|
|
209
|
-
.some((name) => {
|
|
210
|
-
try {
|
|
211
|
-
const record = object(JSON.parse(fs.readFileSync(path.join(runtimeRoot, name), 'utf8')));
|
|
212
|
-
const state = object(record?.state);
|
|
213
|
-
const spawnEvent = object(state?.spawn);
|
|
214
|
-
return !['invalid', 'expired'].includes(String(state?.status))
|
|
215
|
-
&& spawnEvent?.hookDefinitionHash === hookDefinitionHash
|
|
216
|
-
&& spawnEvent.sessionId === identity.sessionId
|
|
217
|
-
&& spawnEvent.turnId === identity.turnId
|
|
218
|
-
&& spawnEvent.toolUseId === identity.toolUseId;
|
|
219
|
-
} catch {
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
215
|
function parseCodexThreadResolution(readValue: unknown, rolloutRecords: readonly unknown[]): ThreadResolution {
|
|
226
216
|
const readRoot = object(readValue);
|
|
227
217
|
const thread = object(readRoot?.thread);
|
|
@@ -278,6 +268,20 @@ function parseCodexThreadResolution(readValue: unknown, rolloutRecords: readonly
|
|
|
278
268
|
});
|
|
279
269
|
}
|
|
280
270
|
|
|
271
|
+
function resolvePreflightTaskDir(
|
|
272
|
+
repoRoot: string,
|
|
273
|
+
taskId?: string
|
|
274
|
+
): string | undefined {
|
|
275
|
+
try {
|
|
276
|
+
const context = taskId
|
|
277
|
+
? resolveTaskRef(taskId, { repoRoot })
|
|
278
|
+
: resolveTaskContext(undefined, { repoRoot });
|
|
279
|
+
return context.ok ? context.taskDir : undefined;
|
|
280
|
+
} catch {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
281
285
|
function readCodexRolloutRecords(readValue: unknown, childThreadId: string): readonly unknown[] {
|
|
282
286
|
const thread = object(object(readValue)?.thread);
|
|
283
287
|
const rolloutPath = nonEmpty(thread?.path);
|
|
@@ -570,8 +574,27 @@ async function resolveCodexTerminal(
|
|
|
570
574
|
|
|
571
575
|
async function preflightCodexLifecycleEvidence(
|
|
572
576
|
repoRoot: string = process.cwd(),
|
|
573
|
-
runtimeIdentity?: CodexRuntimeIdentity
|
|
577
|
+
runtimeIdentity?: CodexRuntimeIdentity,
|
|
578
|
+
taskId?: string
|
|
574
579
|
) {
|
|
580
|
+
const { cliVersion, hooksRaw } = validateStaticCodexPreflight(repoRoot);
|
|
581
|
+
const hookDefinitionHash = crypto.createHash('sha256').update(hooksRaw).digest('hex');
|
|
582
|
+
const taskDir = resolvePreflightTaskDir(repoRoot, taskId);
|
|
583
|
+
const runtimeLiveness = Boolean(taskDir && runtimeIdentity
|
|
584
|
+
&& hasActiveCodexLifecycleEvidence(taskDir, { hookDefinitionHash, identity: runtimeIdentity }));
|
|
585
|
+
const host = await discoverLifecycleHooks(repoRoot);
|
|
586
|
+
return Object.freeze({
|
|
587
|
+
cliVersion,
|
|
588
|
+
hookDefinitionHash,
|
|
589
|
+
staticReady: true,
|
|
590
|
+
discoveredHooks: host.discoveredHooks,
|
|
591
|
+
hookProvenance: host.hookProvenance,
|
|
592
|
+
runtimeLiveness,
|
|
593
|
+
diagnostics: host.diagnostics
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function validateStaticCodexPreflight(repoRoot: string): Readonly<{ cliVersion: string; hooksRaw: string }> {
|
|
575
598
|
const versionRun = spawnSync('codex', ['--version'], { encoding: 'utf8' });
|
|
576
599
|
const match = /codex-cli\s+(\d+\.\d+\.\d+)/.exec(versionRun.stdout ?? '');
|
|
577
600
|
if (versionRun.status !== 0 || !match?.[1] || !semver.gte(match[1], '0.147.0')) {
|
|
@@ -599,11 +622,18 @@ async function preflightCodexLifecycleEvidence(
|
|
|
599
622
|
} finally {
|
|
600
623
|
fs.rmSync(schemaDir, { recursive: true, force: true });
|
|
601
624
|
}
|
|
625
|
+
return { cliVersion: match[1]!, hooksRaw };
|
|
626
|
+
}
|
|
602
627
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
628
|
+
async function discoverLifecycleHooks(repoRoot: string): Promise<Readonly<{
|
|
629
|
+
discoveredHooks: readonly DiscoveredHook[];
|
|
630
|
+
hookProvenance: Readonly<{
|
|
631
|
+
hookSource: 'isolated-user' | 'managed' | 'project';
|
|
632
|
+
hookSourcePathDigest: string;
|
|
633
|
+
hookSourceHash: string;
|
|
634
|
+
}>;
|
|
635
|
+
diagnostics: readonly unknown[];
|
|
636
|
+
}>> {
|
|
607
637
|
const transport = new CodexAppServerTransport();
|
|
608
638
|
transport.start();
|
|
609
639
|
let discoveredHooks: readonly DiscoveredHook[];
|
|
@@ -633,10 +663,7 @@ async function preflightCodexLifecycleEvidence(
|
|
|
633
663
|
const hookSource = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT
|
|
634
664
|
? 'isolated-user'
|
|
635
665
|
: source === 'managed' ? 'managed' : 'project';
|
|
636
|
-
return
|
|
637
|
-
cliVersion: match[1],
|
|
638
|
-
hookDefinitionHash,
|
|
639
|
-
staticReady: true,
|
|
666
|
+
return {
|
|
640
667
|
discoveredHooks,
|
|
641
668
|
hookProvenance: Object.freeze({
|
|
642
669
|
hookSource,
|
|
@@ -648,14 +675,12 @@ async function preflightCodexLifecycleEvidence(
|
|
|
648
675
|
currentHash: hook.currentHash
|
|
649
676
|
})))).digest('hex')
|
|
650
677
|
}),
|
|
651
|
-
runtimeLiveness,
|
|
652
678
|
diagnostics: transport.diagnostics
|
|
653
|
-
}
|
|
679
|
+
};
|
|
654
680
|
}
|
|
655
681
|
|
|
656
682
|
export {
|
|
657
683
|
CodexAppServerTransport,
|
|
658
|
-
hasCodexRuntimeLiveness,
|
|
659
684
|
parseCodexModelReroute,
|
|
660
685
|
parseCodexHooksList,
|
|
661
686
|
parseCodexThreadResolution,
|