@fitlab-ai/agent-infra 0.9.8 → 0.9.9
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +6 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +58 -21
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +173 -10
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +56 -67
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/internal/codex-lifecycle.js +19 -5
- package/dist/lib/internal/codex-sandbox-controller.js +6 -13
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +2 -1
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-finalization.js +54 -0
- package/dist/lib/internal/task-orchestration.js +156 -291
- package/dist/lib/platform/pr-summary.js +48 -10
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/controller-registration.js +273 -0
- package/dist/lib/sandbox/control/executor.js +137 -5
- package/dist/lib/sandbox/control/lifecycle.js +3 -1
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/task/codex-orchestration.js +158 -33
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/delegation-receipts.js +243 -2
- package/dist/lib/task/finalization.js +567 -0
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +187 -761
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +113 -60
- package/dist/lib/task/verification.js +2 -1
- package/dist/lib/task/workflow-warning-intents.js +50 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +61 -19
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +276 -12
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +83 -91
- package/lib/git/workflow.ts +11 -2
- package/lib/internal/codex-lifecycle.ts +18 -5
- package/lib/internal/codex-sandbox-controller.ts +6 -12
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +2 -1
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-orchestration.ts +20 -103
- package/lib/platform/pr-summary.ts +55 -11
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/controller-registration.ts +347 -0
- package/lib/sandbox/control/executor.ts +163 -5
- package/lib/sandbox/control/lifecycle.ts +2 -1
- package/lib/sandbox/control/protocol.ts +80 -7
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +3 -3
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/task/codex-orchestration.ts +167 -33
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/delegation-receipts.ts +258 -1
- package/lib/task/finalization.ts +719 -0
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +217 -982
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +118 -63
- package/lib/task/verification.ts +3 -2
- package/lib/task/workflow-warning-intents.ts +57 -2
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +2 -2
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -21
- package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
|
@@ -6,11 +6,22 @@ import path from 'node:path';
|
|
|
6
6
|
import semver from 'semver';
|
|
7
7
|
import * as toml from 'smol-toml';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
requestCodexControllerClose,
|
|
11
|
+
requestCodexControllerOpen,
|
|
12
|
+
requestCodexControllerVerify,
|
|
13
|
+
requestSandboxControl
|
|
14
|
+
} from '../../../sandbox/control/client.ts';
|
|
10
15
|
import { readSandboxControlStatus } from '../../../sandbox/control/state.ts';
|
|
11
|
-
import { getProcessStartTime,
|
|
16
|
+
import { getProcessStartTime, type ProcessIdentity } from '../../../server/process-state.ts';
|
|
12
17
|
import { computeLifecycleBuildIdentity } from './build-identity.ts';
|
|
13
|
-
import
|
|
18
|
+
import {
|
|
19
|
+
contextFromControllerLease,
|
|
20
|
+
controllerProofFromContext,
|
|
21
|
+
verifyCodexSandboxControllerContext as verifyContextFile,
|
|
22
|
+
writeCodexSandboxControllerContext,
|
|
23
|
+
type CodexSandboxControllerContextV2
|
|
24
|
+
} from './controller-context.ts';
|
|
14
25
|
|
|
15
26
|
type ControllerControl = Readonly<{
|
|
16
27
|
token: string;
|
|
@@ -20,23 +31,7 @@ type ControllerControl = Readonly<{
|
|
|
20
31
|
runtimeDir: string;
|
|
21
32
|
}>;
|
|
22
33
|
|
|
23
|
-
type CodexSandboxControllerContext = Readonly<{
|
|
24
|
-
version: 1;
|
|
25
|
-
taskId: string;
|
|
26
|
-
controlGeneration: string;
|
|
27
|
-
controllerInstanceDigest: string;
|
|
28
|
-
parentPid: number;
|
|
29
|
-
parentStartTime: number;
|
|
30
|
-
issuedAt: number;
|
|
31
|
-
expiresAt: number;
|
|
32
|
-
buildIdentity: LifecycleBuildIdentity;
|
|
33
|
-
hookDefinitionHash: string;
|
|
34
|
-
lifecycleProfilesHash: string;
|
|
35
|
-
}>;
|
|
36
|
-
|
|
37
34
|
type ControllerInput = Readonly<{
|
|
38
|
-
taskId: string;
|
|
39
|
-
taskRef: string;
|
|
40
35
|
executorModel?: string;
|
|
41
36
|
executorReasoningEffort?: string;
|
|
42
37
|
reviewerModel?: string;
|
|
@@ -51,6 +46,8 @@ type ControllerOptions = Readonly<{
|
|
|
51
46
|
now?: () => number;
|
|
52
47
|
codexVersion?: () => string;
|
|
53
48
|
verifyTaskBinding?: (taskId: string, control: ControllerControl) => void;
|
|
49
|
+
openController?: typeof requestCodexControllerOpen;
|
|
50
|
+
closeController?: typeof requestCodexControllerClose;
|
|
54
51
|
environment?: NodeJS.ProcessEnv;
|
|
55
52
|
}>;
|
|
56
53
|
|
|
@@ -60,11 +57,18 @@ type PreparedCodexSandboxController = Readonly<{
|
|
|
60
57
|
env: NodeJS.ProcessEnv;
|
|
61
58
|
home: string;
|
|
62
59
|
contextPath: string;
|
|
63
|
-
context:
|
|
60
|
+
context: CodexSandboxControllerContextV2;
|
|
64
61
|
cleanup: () => void;
|
|
65
62
|
}>;
|
|
66
63
|
|
|
67
|
-
|
|
64
|
+
type VerifyContextOptions = Readonly<{
|
|
65
|
+
repoRoot?: string;
|
|
66
|
+
now?: number;
|
|
67
|
+
generation?: string;
|
|
68
|
+
probeProcess?: (identity: ProcessIdentity) => 'alive' | 'dead' | 'unknown';
|
|
69
|
+
control?: ControllerControl;
|
|
70
|
+
requestControllerVerify?: typeof requestCodexControllerVerify;
|
|
71
|
+
}>;
|
|
68
72
|
|
|
69
73
|
function digestFiles(files: readonly string[]): string {
|
|
70
74
|
const hash = crypto.createHash('sha256');
|
|
@@ -162,6 +166,30 @@ function verifyControl(taskId: string, control: ControllerControl): void {
|
|
|
162
166
|
if (payload.taskId !== taskId) throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
|
|
163
167
|
}
|
|
164
168
|
|
|
169
|
+
function verifyCodexSandboxControllerContext(
|
|
170
|
+
contextPath: string,
|
|
171
|
+
options: VerifyContextOptions = {}
|
|
172
|
+
): CodexSandboxControllerContextV2 {
|
|
173
|
+
const control = options.control ?? controlFromEnvironment();
|
|
174
|
+
const context = verifyContextFile(contextPath, {
|
|
175
|
+
repoRoot: options.repoRoot,
|
|
176
|
+
now: options.now,
|
|
177
|
+
generation: control.generation,
|
|
178
|
+
probeProcess: options.probeProcess
|
|
179
|
+
});
|
|
180
|
+
const verified = (options.requestControllerVerify ?? requestCodexControllerVerify)({
|
|
181
|
+
controllerProof: controllerProofFromContext(context),
|
|
182
|
+
...control,
|
|
183
|
+
timeoutMs: 30_000
|
|
184
|
+
});
|
|
185
|
+
if (verified.binding.taskId !== context.taskId
|
|
186
|
+
|| verified.binding.controlGeneration !== context.controlGeneration
|
|
187
|
+
|| verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
|
|
188
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
189
|
+
}
|
|
190
|
+
return context;
|
|
191
|
+
}
|
|
192
|
+
|
|
165
193
|
function detectedCodexVersion(): string {
|
|
166
194
|
const result = spawnSync('codex', ['--version'], { encoding: 'utf8' });
|
|
167
195
|
const version = /codex-cli\s+(\d+\.\d+\.\d+)/u.exec(result.stdout ?? '')?.[1];
|
|
@@ -256,7 +284,6 @@ function prepareCodexSandboxController(
|
|
|
256
284
|
const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
257
285
|
const sourceHome = path.resolve(options.codexHome ?? process.env.CODEX_HOME ?? path.join(os.homedir(), '.codex'));
|
|
258
286
|
const control = options.control ?? controlFromEnvironment();
|
|
259
|
-
(options.verifyTaskBinding ?? verifyControl)(input.taskId, control);
|
|
260
287
|
const version = (options.codexVersion ?? detectedCodexVersion)();
|
|
261
288
|
if (!semver.gte(version, '0.147.0')) throw new Error('CODEX_SANDBOX_CONTROLLER_CODEX_UNSUPPORTED');
|
|
262
289
|
|
|
@@ -265,46 +292,33 @@ function prepareCodexSandboxController(
|
|
|
265
292
|
fs.mkdirSync(runtimeRoot, { recursive: true, mode: 0o700 });
|
|
266
293
|
fs.chmodSync(runtimeRoot, 0o700);
|
|
267
294
|
const key = crypto.createHash('sha256')
|
|
268
|
-
.update(`${
|
|
295
|
+
.update(`${process.pid}\0${control.generation}`)
|
|
269
296
|
.digest('hex');
|
|
270
|
-
const leasePath = path.join(runtimeRoot, `${key}.lease.json`);
|
|
271
|
-
if (fs.existsSync(leasePath)) {
|
|
272
|
-
const lease = JSON.parse(fs.readFileSync(leasePath, 'utf8')) as { pid?: number; startTime?: number };
|
|
273
|
-
if (Number.isSafeInteger(lease.pid) && typeof lease.startTime === 'number'
|
|
274
|
-
&& getProcessStartTime(lease.pid!) === lease.startTime) {
|
|
275
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_BUSY');
|
|
276
|
-
}
|
|
277
|
-
fs.unlinkSync(leasePath);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
297
|
const parentStartTime = getProcessStartTime(process.pid);
|
|
281
298
|
if (!parentStartTime) throw new Error('CODEX_SANDBOX_CONTROLLER_PROCESS_IDENTITY_INVALID');
|
|
282
|
-
const instanceDigest = crypto.randomBytes(32).toString('hex');
|
|
283
|
-
fs.writeFileSync(leasePath, `${JSON.stringify({
|
|
284
|
-
version: 1,
|
|
285
|
-
pid: process.pid,
|
|
286
|
-
startTime: parentStartTime,
|
|
287
|
-
taskId: input.taskId,
|
|
288
|
-
generation: control.generation,
|
|
289
|
-
instanceDigest
|
|
290
|
-
})}\n`, { mode: 0o600, flag: 'wx' });
|
|
291
|
-
|
|
292
299
|
const home = fs.mkdtempSync(path.join(runtimeRoot, `${key}-`));
|
|
293
300
|
fs.chmodSync(home, 0o700);
|
|
294
301
|
let cleaned = false;
|
|
302
|
+
let context: CodexSandboxControllerContextV2 | null = null;
|
|
295
303
|
const cleanup = () => {
|
|
296
304
|
if (cleaned) return;
|
|
297
305
|
cleaned = true;
|
|
306
|
+
if (context) {
|
|
307
|
+
try {
|
|
308
|
+
(options.closeController ?? requestCodexControllerClose)({
|
|
309
|
+
controllerProcess: context.controllerProcess,
|
|
310
|
+
controllerProof: controllerProofFromContext(context),
|
|
311
|
+
...control,
|
|
312
|
+
timeoutMs: 30_000
|
|
313
|
+
});
|
|
314
|
+
} catch {
|
|
315
|
+
// The wrapper is exiting; dead-process recovery safely handles an unknown close result.
|
|
316
|
+
}
|
|
317
|
+
}
|
|
298
318
|
const relative = path.relative(runtimeRoot, home);
|
|
299
319
|
if (!relative.startsWith('..') && !path.isAbsolute(relative)) {
|
|
300
320
|
fs.rmSync(home, { recursive: true, force: true });
|
|
301
321
|
}
|
|
302
|
-
try {
|
|
303
|
-
const lease = JSON.parse(fs.readFileSync(leasePath, 'utf8')) as { instanceDigest?: string };
|
|
304
|
-
if (lease.instanceDigest === instanceDigest) fs.unlinkSync(leasePath);
|
|
305
|
-
} catch {
|
|
306
|
-
// A missing or replaced lease is owned by another cleanup path.
|
|
307
|
-
}
|
|
308
322
|
};
|
|
309
323
|
|
|
310
324
|
try {
|
|
@@ -333,22 +347,25 @@ function prepareCodexSandboxController(
|
|
|
333
347
|
}
|
|
334
348
|
|
|
335
349
|
const buildIdentity = computeLifecycleBuildIdentity(repoRoot);
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
parentStartTime,
|
|
344
|
-
issuedAt: now,
|
|
345
|
-
expiresAt: now + CONTEXT_TTL_MS,
|
|
346
|
-
buildIdentity,
|
|
350
|
+
const opened = (options.openController ?? requestCodexControllerOpen)({
|
|
351
|
+
controllerProcess: { pid: process.pid, startTime: parentStartTime },
|
|
352
|
+
...control,
|
|
353
|
+
timeoutMs: 30_000
|
|
354
|
+
});
|
|
355
|
+
const taskId = opened.lease.taskId;
|
|
356
|
+
context = contextFromControllerLease(opened.lease, {
|
|
347
357
|
hookDefinitionHash: crypto.createHash('sha256').update(fs.readFileSync(hooks)).digest('hex'),
|
|
348
358
|
lifecycleProfilesHash: digestFiles([executor, reviewer])
|
|
349
359
|
});
|
|
360
|
+
if (opened.lease.controlGeneration !== control.generation
|
|
361
|
+
|| opened.lease.controllerProcess.pid !== process.pid
|
|
362
|
+
|| opened.lease.controllerProcess.startTime !== parentStartTime
|
|
363
|
+
|| JSON.stringify(opened.lease.buildIdentity) !== JSON.stringify(buildIdentity)) {
|
|
364
|
+
throw new Error('SANDBOX_CONTROL_RESULT_INVALID');
|
|
365
|
+
}
|
|
366
|
+
(options.verifyTaskBinding ?? verifyControl)(taskId, control);
|
|
350
367
|
const contextPath = path.join(home, 'controller-context.json');
|
|
351
|
-
|
|
368
|
+
writeCodexSandboxControllerContext(contextPath, context);
|
|
352
369
|
|
|
353
370
|
const shimDir = path.join(home, 'bin');
|
|
354
371
|
fs.mkdirSync(shimDir, { mode: 0o700 });
|
|
@@ -364,7 +381,7 @@ function prepareCodexSandboxController(
|
|
|
364
381
|
input.reviewerModel ? `--reviewer-model ${input.reviewerModel}` : '',
|
|
365
382
|
input.reviewerReasoningEffort ? `--reviewer-reasoning-effort ${input.reviewerReasoningEffort}` : ''
|
|
366
383
|
].filter(Boolean).join(' ');
|
|
367
|
-
const prompt = `$run-task ${
|
|
384
|
+
const prompt = `$run-task ${taskId}${policy ? ` ${policy}` : ''}`;
|
|
368
385
|
const args = Object.freeze([
|
|
369
386
|
'exec',
|
|
370
387
|
'--enable', 'hooks',
|
|
@@ -379,7 +396,7 @@ function prepareCodexSandboxController(
|
|
|
379
396
|
home,
|
|
380
397
|
shimDir,
|
|
381
398
|
contextPath,
|
|
382
|
-
|
|
399
|
+
taskId,
|
|
383
400
|
control,
|
|
384
401
|
providerEnvironment,
|
|
385
402
|
options.environment ?? process.env
|
|
@@ -399,32 +416,6 @@ function prepareCodexSandboxController(
|
|
|
399
416
|
}
|
|
400
417
|
}
|
|
401
418
|
|
|
402
|
-
function verifyCodexSandboxControllerContext(
|
|
403
|
-
contextPath: string,
|
|
404
|
-
taskId: string,
|
|
405
|
-
options: Readonly<{ repoRoot?: string; now?: number; generation?: string }> = {}
|
|
406
|
-
): CodexSandboxControllerContext {
|
|
407
|
-
const stat = fs.lstatSync(contextPath);
|
|
408
|
-
if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 0o077) !== 0) {
|
|
409
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
410
|
-
}
|
|
411
|
-
const context = JSON.parse(fs.readFileSync(contextPath, 'utf8')) as CodexSandboxControllerContext;
|
|
412
|
-
const generation = options.generation ?? process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
413
|
-
if (context.version !== 1
|
|
414
|
-
|| context.taskId !== taskId
|
|
415
|
-
|| context.controlGeneration !== generation
|
|
416
|
-
|| context.expiresAt < (options.now ?? Date.now())
|
|
417
|
-
|| getProcessStartTime(context.parentPid) !== context.parentStartTime
|
|
418
|
-
|| !isProcessAlive(context.parentPid)) {
|
|
419
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
420
|
-
}
|
|
421
|
-
const current = computeLifecycleBuildIdentity(options.repoRoot ?? process.cwd());
|
|
422
|
-
if (JSON.stringify(current) !== JSON.stringify(context.buildIdentity)) {
|
|
423
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
|
|
424
|
-
}
|
|
425
|
-
return Object.freeze(context);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
419
|
async function runCodexSandboxController(
|
|
429
420
|
input: ControllerInput,
|
|
430
421
|
options: ControllerOptions = {}
|
|
@@ -469,10 +460,11 @@ async function runCodexSandboxController(
|
|
|
469
460
|
export {
|
|
470
461
|
prepareCodexSandboxController,
|
|
471
462
|
runCodexSandboxController,
|
|
472
|
-
verifyCodexSandboxControllerContext
|
|
463
|
+
verifyCodexSandboxControllerContext,
|
|
464
|
+
controllerProofFromContext
|
|
473
465
|
};
|
|
474
466
|
export type {
|
|
475
|
-
CodexSandboxControllerContext,
|
|
467
|
+
CodexSandboxControllerContextV2 as CodexSandboxControllerContext,
|
|
476
468
|
ControllerControl,
|
|
477
469
|
ControllerInput,
|
|
478
470
|
ControllerOptions,
|
package/lib/git/workflow.ts
CHANGED
|
@@ -105,7 +105,7 @@ function commitExplicitPaths(input: { cwd: string; paths: readonly string[]; mes
|
|
|
105
105
|
return { status: 'applied' as const, changed: true, snapshot: after.snapshot, operations: [{ name: 'stage', status: 'applied' as const }, { name: 'commit', status: 'applied' as const }], error: null };
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function pushGitRefs(input: { cwd: string; remote: string; refs: readonly string[] }, runner: GitRunner = defaultRunner) {
|
|
108
|
+
function pushGitRefs(input: { cwd: string; remote: string; refs: readonly string[]; expectedSha?: string }, runner: GitRunner = defaultRunner) {
|
|
109
109
|
if (!input.remote || input.refs.length === 0) return { status: 'failed' as const, changed: false, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations: [], error: { code: 'GIT_PUSH_INPUT_INVALID', message: 'Remote and refs are required' } };
|
|
110
110
|
const operations: GitOperation[] = [];
|
|
111
111
|
for (const ref of input.refs) {
|
|
@@ -120,7 +120,16 @@ function pushGitRefs(input: { cwd: string; remote: string; refs: readonly string
|
|
|
120
120
|
}
|
|
121
121
|
const canonical = ref.startsWith('refs/') ? ref : `refs/heads/${ref}`;
|
|
122
122
|
const remoteFact = run(runner, input.cwd, ['ls-remote', '--exit-code', input.remote, canonical]);
|
|
123
|
-
|
|
123
|
+
const remoteSha = remoteFact.status === 0 ? remoteFact.stdout.trim().split(/\s+/)[0] ?? null : null;
|
|
124
|
+
const verified = remoteFact.status === 0 && (!input.expectedSha || remoteSha === input.expectedSha);
|
|
125
|
+
operations.push({
|
|
126
|
+
name: 'push',
|
|
127
|
+
ref,
|
|
128
|
+
status: verified ? 'applied' : 'failed',
|
|
129
|
+
message: verified ? undefined : input.expectedSha
|
|
130
|
+
? `Remote ref verification failed: expected ${input.expectedSha}, received ${remoteSha ?? 'unavailable'}`
|
|
131
|
+
: 'Remote ref verification failed'
|
|
132
|
+
});
|
|
124
133
|
}
|
|
125
134
|
const failures = operations.filter((item) => item.status === 'failed').length;
|
|
126
135
|
return { status: failures === 0 ? 'applied' as const : failures === operations.length ? 'failed' as const : 'degraded' as const, changed: failures < operations.length, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations, error: failures ? { code: 'GIT_PUSH_PARTIAL', message: `${failures} ref push operation(s) failed` } : null };
|
|
@@ -14,6 +14,7 @@ import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-l
|
|
|
14
14
|
import { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
|
|
15
15
|
import type { CodexLifecycleEvent } from '../agent-clients/adapters/codex-lifecycle/evidence.ts';
|
|
16
16
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
17
|
+
import { hasSealableOrchestrationDelegation } from '../task/orchestration.ts';
|
|
17
18
|
import {
|
|
18
19
|
activateCodexOrchestrationDelegation,
|
|
19
20
|
activateCodexSpawnDelegation,
|
|
@@ -100,7 +101,7 @@ function hookDefinitionHash(): string {
|
|
|
100
101
|
function controllerBinding(taskId: string) {
|
|
101
102
|
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
102
103
|
if (!contextPath) return undefined;
|
|
103
|
-
const context = verifyCodexSandboxControllerContext(contextPath,
|
|
104
|
+
const context = verifyCodexSandboxControllerContext(contextPath, { repoRoot: process.cwd() });
|
|
104
105
|
return {
|
|
105
106
|
instanceDigest: context.controllerInstanceDigest,
|
|
106
107
|
controlGeneration: context.controlGeneration
|
|
@@ -139,7 +140,8 @@ function recordFromPayload(phase: string, payload: unknown): CodexLifecycleEvent
|
|
|
139
140
|
sessionId: text('sessionId'),
|
|
140
141
|
turnId: text('turnId'),
|
|
141
142
|
childThreadId: text('childThreadId'),
|
|
142
|
-
nativeAgent: text('nativeAgent')
|
|
143
|
+
nativeAgent: text('nativeAgent'),
|
|
144
|
+
source: 'hook'
|
|
143
145
|
};
|
|
144
146
|
if (phase === 'subagent-stop') return {
|
|
145
147
|
type: 'hook-stop',
|
|
@@ -326,8 +328,17 @@ async function codexLifecycle(args: string[] = []): Promise<void> {
|
|
|
326
328
|
}
|
|
327
329
|
const result = store.apply(event);
|
|
328
330
|
if (parsed.values['--bridge'] === 'true' && event.type === 'hook-stop') {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
+
if (!hasSealableOrchestrationDelegation('codex', event.childThreadId, { repoRoot: process.cwd() })) {
|
|
332
|
+
output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
output({
|
|
336
|
+
status: result.state.status,
|
|
337
|
+
changed: true,
|
|
338
|
+
evidence: result.state,
|
|
339
|
+
diagnostics: [],
|
|
340
|
+
error: result.state.error
|
|
341
|
+
});
|
|
331
342
|
return;
|
|
332
343
|
}
|
|
333
344
|
output({ status: result.state.status, changed: true, evidence: result.state, diagnostics: [], error: result.state.error });
|
|
@@ -350,7 +361,9 @@ async function codexLifecycle(args: string[] = []): Promise<void> {
|
|
|
350
361
|
return;
|
|
351
362
|
}
|
|
352
363
|
if (parsed.operation === 'resolve-stop') {
|
|
353
|
-
const
|
|
364
|
+
const stopTurnId = store.read(childThreadId).state.stop?.turnId;
|
|
365
|
+
if (!stopTurnId) throw new Error('Codex lifecycle stop hook is not available');
|
|
366
|
+
const terminal = await resolveCodexTerminal(childThreadId, stopTurnId);
|
|
354
367
|
const latest = store.apply(terminal);
|
|
355
368
|
if (latest.state.status !== 'stop-ready') {
|
|
356
369
|
throw new Error(`Codex lifecycle stop evidence is not ready (status=${latest.state.status})`);
|
|
@@ -26,8 +26,6 @@ function parse(args: string[]): Readonly<{ operation: string; values: Readonly<R
|
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
28
|
const allowed = new Set([
|
|
29
|
-
'--task-id',
|
|
30
|
-
'--task-ref',
|
|
31
29
|
'--context',
|
|
32
30
|
'--executor-model',
|
|
33
31
|
'--executor-reasoning-effort',
|
|
@@ -37,6 +35,11 @@ function parse(args: string[]): Readonly<{ operation: string; values: Readonly<R
|
|
|
37
35
|
const values: Record<string, string> = {};
|
|
38
36
|
for (let index = 1; index < args.length; index += 1) {
|
|
39
37
|
const flag = args[index]!;
|
|
38
|
+
if (flag === '--task-id' || flag.startsWith('--task-id=')
|
|
39
|
+
|| flag === '--task-ref' || flag.startsWith('--task-ref=')) {
|
|
40
|
+
fail('CODEX_SANDBOX_CONTROLLER_IDENTITY_OPTION_UNSUPPORTED', 'controller task identity options are not supported', 2);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
40
43
|
const value = args[++index];
|
|
41
44
|
if (!allowed.has(flag) || Object.hasOwn(values, flag) || !value || value.startsWith('--')) {
|
|
42
45
|
fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', `invalid option '${flag}'`, 2);
|
|
@@ -50,17 +53,12 @@ function parse(args: string[]): Readonly<{ operation: string; values: Readonly<R
|
|
|
50
53
|
async function codexSandboxController(args: string[] = []): Promise<void> {
|
|
51
54
|
const parsed = parse(args);
|
|
52
55
|
if (!parsed || process.exitCode) return;
|
|
53
|
-
const taskId = parsed.values['--task-id'];
|
|
54
|
-
if (!taskId) {
|
|
55
|
-
fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', '--task-id is required', 2);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
56
|
try {
|
|
59
57
|
if (parsed.operation === 'verify-context') {
|
|
60
58
|
const contextPath = parsed.values['--context']
|
|
61
59
|
?? process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
62
60
|
if (!contextPath) throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_MISSING');
|
|
63
|
-
const context = verifyCodexSandboxControllerContext(contextPath
|
|
61
|
+
const context = verifyCodexSandboxControllerContext(contextPath);
|
|
64
62
|
process.stdout.write(`${JSON.stringify({
|
|
65
63
|
status: 'ready',
|
|
66
64
|
changed: false,
|
|
@@ -77,8 +75,6 @@ async function codexSandboxController(args: string[] = []): Promise<void> {
|
|
|
77
75
|
})}\n`);
|
|
78
76
|
return;
|
|
79
77
|
}
|
|
80
|
-
const taskRef = parsed.values['--task-ref'];
|
|
81
|
-
if (!taskRef) throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_REF_REQUIRED');
|
|
82
78
|
const policy = [
|
|
83
79
|
'--executor-model',
|
|
84
80
|
'--executor-reasoning-effort',
|
|
@@ -90,8 +86,6 @@ async function codexSandboxController(args: string[] = []): Promise<void> {
|
|
|
90
86
|
throw new Error('CODEX_SANDBOX_CONTROLLER_MODEL_POLICY_INVALID');
|
|
91
87
|
}
|
|
92
88
|
const exitCode = await runCodexSandboxController({
|
|
93
|
-
taskId,
|
|
94
|
-
taskRef,
|
|
95
89
|
executorModel: parsed.values['--executor-model'],
|
|
96
90
|
executorReasoningEffort: parsed.values['--executor-reasoning-effort'],
|
|
97
91
|
reviewerModel: parsed.values['--reviewer-model'],
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { inspectGitWorkflow, pushRebasedBranch } from '../git/workflow.ts';
|
|
3
3
|
import { compareReviewTrees, snapshotReview } from '../git/review-snapshot.ts';
|
|
4
4
|
import { resolvePostReviewGlobs } from '../task/review-fingerprint.ts';
|
|
5
|
+
import { executeCommitOperation } from '../task/commit-operation.ts';
|
|
5
6
|
|
|
6
7
|
function output(value: unknown): void { process.stdout.write(`${JSON.stringify(value)}\n`); }
|
|
7
8
|
|
|
@@ -18,11 +19,9 @@ function gitWorkflow(args: string[] = []): void {
|
|
|
18
19
|
else if (action === 'commit') {
|
|
19
20
|
const inputIndex = rest.indexOf('--input');
|
|
20
21
|
const input = inputIndex >= 0 && rest[inputIndex + 1] ? JSON.parse(fs.readFileSync(rest[inputIndex + 1]!, 'utf8')) : null;
|
|
21
|
-
result = input
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const input = inputIndex >= 0 && rest[inputIndex + 1] ? JSON.parse(fs.readFileSync(rest[inputIndex + 1]!, 'utf8')) : null;
|
|
25
|
-
result = input ? pushGitRefs({ cwd: cwd ?? process.cwd(), ...input }) : { status: 'failed', changed: false, error: { code: 'GIT_INPUT_REQUIRED', message: '--input JSON file is required' } };
|
|
22
|
+
result = input
|
|
23
|
+
? executeCommitOperation({ cwd: cwd ?? process.cwd(), ...input })
|
|
24
|
+
: { status: 'failed', changed: false, error: { code: 'GIT_INPUT_REQUIRED', message: '--input JSON file is required' } };
|
|
26
25
|
} else if (action === 'push-rebased') {
|
|
27
26
|
const inputIndex = rest.indexOf('--input');
|
|
28
27
|
const input = inputIndex >= 0 && rest[inputIndex + 1] ? JSON.parse(fs.readFileSync(rest[inputIndex + 1]!, 'utf8')) : null;
|
|
@@ -17,13 +17,13 @@ const USAGE = `Usage: agent-infra-internal platform-pr inspect <task-ref> [--cwd
|
|
|
17
17
|
agent-infra-internal platform-pr resolve-external <task-ref> --agent <agent> [--pr <N>] [--dry-run] [--cwd <path>]
|
|
18
18
|
agent-infra-internal platform-pr create <task-ref> --agent <agent> --base <branch> --head <branch> --title-file <path|-> --body-file <path|-> [--draft] [--dry-run] [--cwd <path>]
|
|
19
19
|
agent-infra-internal platform-pr bind <task-ref> --pr <N> --agent <agent> [--dry-run] [--cwd <path>]
|
|
20
|
-
agent-infra-internal platform-pr sync <task-ref> --agent <agent> [--metadata] [--closing-issue] [--dry-run] [--cwd <path>]
|
|
20
|
+
agent-infra-internal platform-pr sync <task-ref> --agent <agent> [--metadata] [--closing-issue] --result <pr_created|pr_reused|no_op> [--dry-run] [--cwd <path>]
|
|
21
21
|
agent-infra-internal platform-pr summary-context <task-ref> [--cwd <path>]
|
|
22
|
-
agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> [--dry-run] [--cwd <path>]
|
|
22
|
+
agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --result <pr_created|pr_reused|no_op> [--dry-run] [--cwd <path>]
|
|
23
23
|
`;
|
|
24
24
|
|
|
25
25
|
const BOOLEAN_FLAGS = new Set(['--draft', '--dry-run', '--metadata', '--closing-issue']);
|
|
26
|
-
const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--pr']);
|
|
26
|
+
const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--pr', '--result']);
|
|
27
27
|
|
|
28
28
|
function key(flag: string): string {
|
|
29
29
|
return flag.slice(2).replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
|
|
@@ -76,9 +76,9 @@ function platformPr(args: string[] = []): void {
|
|
|
76
76
|
'resolve-external': ['cwd', 'agent', 'pr', 'dryRun'],
|
|
77
77
|
create: ['cwd', 'agent', 'base', 'head', 'titleFile', 'bodyFile', 'draft', 'dryRun'],
|
|
78
78
|
bind: ['cwd', 'agent', 'pr', 'dryRun'],
|
|
79
|
-
sync: ['cwd', 'agent', 'metadata', 'closingIssue', 'dryRun'],
|
|
79
|
+
sync: ['cwd', 'agent', 'metadata', 'closingIssue', 'result', 'dryRun'],
|
|
80
80
|
'summary-context': ['cwd'],
|
|
81
|
-
'summary-sync': ['cwd', 'agent', 'bodyFile', 'dryRun']
|
|
81
|
+
'summary-sync': ['cwd', 'agent', 'bodyFile', 'result', 'dryRun']
|
|
82
82
|
};
|
|
83
83
|
const unexpected = Object.keys(values).find((name) => !allowed[operation]!.includes(name));
|
|
84
84
|
if (unexpected) { fail(`${operation} does not accept --${unexpected}`); return; }
|
|
@@ -88,6 +88,11 @@ function platformPr(args: string[] = []): void {
|
|
|
88
88
|
const agent = normalizeAgentToken(values.agent);
|
|
89
89
|
if (!agent) { fail(`invalid --agent '${values.agent}': ${AGENT_USAGE_HINT}`); return; }
|
|
90
90
|
values.agent = agent;
|
|
91
|
+
const primaryResult = values.result === undefined ? undefined : values.result;
|
|
92
|
+
if (primaryResult !== undefined && !['pr_created', 'pr_reused', 'no_op'].includes(primaryResult as string)) {
|
|
93
|
+
fail('--result must be pr_created, pr_reused, or no_op');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
91
96
|
if (operation === 'resolve-external') {
|
|
92
97
|
const pr = values.pr === undefined ? undefined : Number(values.pr);
|
|
93
98
|
if (pr !== undefined && (!Number.isInteger(pr) || pr <= 0)) { fail('resolve-external requires a positive --pr'); return; }
|
|
@@ -102,13 +107,15 @@ function platformPr(args: string[] = []): void {
|
|
|
102
107
|
}
|
|
103
108
|
if (operation === 'sync') {
|
|
104
109
|
if (values.metadata !== true && values.closingIssue !== true) { fail('sync requires --metadata or --closing-issue'); return; }
|
|
105
|
-
|
|
110
|
+
if (!primaryResult) { fail('sync requires --result pr_created, pr_reused, or no_op'); return; }
|
|
111
|
+
finish(syncPlatformPullRequest(taskRef, { cwd, agent: values.agent, metadata: values.metadata === true, closingIssue: values.closingIssue === true, primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', dryRun: values.dryRun === true }));
|
|
106
112
|
return;
|
|
107
113
|
}
|
|
108
114
|
if (operation === 'summary-sync') {
|
|
109
115
|
if (typeof values.bodyFile !== 'string') { fail('summary-sync requires --body-file'); return; }
|
|
116
|
+
if (!primaryResult) { fail('summary-sync requires --result pr_created, pr_reused, or no_op'); return; }
|
|
110
117
|
try {
|
|
111
|
-
finish(syncPullRequestSummary(taskRef, { cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), dryRun: values.dryRun === true }));
|
|
118
|
+
finish(syncPullRequestSummary(taskRef, { cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', dryRun: values.dryRun === true }));
|
|
112
119
|
} catch (error) {
|
|
113
120
|
fail(`unable to read body file: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
121
|
}
|
|
@@ -25,12 +25,13 @@ type DemoResult = {
|
|
|
25
25
|
outputPath: string | null;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
// The tape is the canonical init interaction contract. Do not hash all of
|
|
29
|
+
// lib/init.ts: non-visual config serialization changes must not re-record it.
|
|
28
30
|
const DEMO_INPUT_PATHS = [
|
|
29
31
|
'assets/demo-init.tape',
|
|
30
32
|
'scripts/demo-regen.sh',
|
|
31
33
|
'scripts/normalize-gif-duration.py',
|
|
32
34
|
'bin/cli.ts',
|
|
33
|
-
'lib/init.ts',
|
|
34
35
|
'lib/log.ts',
|
|
35
36
|
'lib/prompt.ts',
|
|
36
37
|
'lib/paths.ts',
|
|
@@ -1,6 +1,84 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
requestSandboxControl,
|
|
3
|
+
requestSandboxTaskFinalization,
|
|
4
|
+
requestSandboxTaskControl,
|
|
5
|
+
SandboxControlClientError
|
|
6
|
+
} from '../sandbox/control/client.ts';
|
|
7
|
+
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
|
|
2
8
|
import { serveSandboxControl } from '../sandbox/control/server.ts';
|
|
3
9
|
import { runSandboxControlExecutor } from '../sandbox/control/executor.ts';
|
|
10
|
+
import {
|
|
11
|
+
controllerProofFromContext,
|
|
12
|
+
verifyCodexSandboxControllerContext
|
|
13
|
+
} from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
|
|
14
|
+
|
|
15
|
+
function isCanonicalCodexPrepare(args: readonly string[]): boolean {
|
|
16
|
+
if (args[1] !== 'prepare') return false;
|
|
17
|
+
const clients: string[] = [];
|
|
18
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
19
|
+
if (args[index] === '--client') clients.push(args[index + 1] ?? '');
|
|
20
|
+
else if (args[index]?.startsWith('--client=')) clients.push(args[index]!.slice('--client='.length));
|
|
21
|
+
}
|
|
22
|
+
return clients.length === 1 && clients[0] === 'codex';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type FinalizationStatus = 'completed' | 'failed' | 'blocked' | 'unknown';
|
|
26
|
+
|
|
27
|
+
function writeFinalizationEnvelope(
|
|
28
|
+
status: FinalizationStatus,
|
|
29
|
+
accepted: boolean,
|
|
30
|
+
error: { code: string; message: string; retryable: boolean },
|
|
31
|
+
changed = false,
|
|
32
|
+
result: unknown = null
|
|
33
|
+
): void {
|
|
34
|
+
process.stdout.write(`${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function finalizationErrorStatus(error: { retryable: boolean; code: string }): FinalizationStatus {
|
|
38
|
+
if (error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN') return 'unknown';
|
|
39
|
+
return error.retryable ? 'blocked' : 'failed';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sandboxFinalizationClient(args: string[]): void {
|
|
43
|
+
if (args.length !== 4 || !args[0] || args[1] !== 'complete' || args[2] !== '--agent' || !args[3]) {
|
|
44
|
+
const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'task ref, complete intent, and --agent are required', retryable: false };
|
|
45
|
+
writeFinalizationEnvelope('failed', false, error);
|
|
46
|
+
process.stderr.write('Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n');
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const agent = normalizeAgentToken(args[3]);
|
|
51
|
+
if (!agent) {
|
|
52
|
+
const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: `invalid --agent: ${AGENT_USAGE_HINT}`, retryable: false };
|
|
53
|
+
writeFinalizationEnvelope('failed', false, error);
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let response;
|
|
58
|
+
try {
|
|
59
|
+
response = requestSandboxTaskFinalization({ agent });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!(error instanceof SandboxControlClientError)) throw error;
|
|
62
|
+
writeFinalizationEnvelope(finalizationErrorStatus(error.detail), error.accepted, error.detail);
|
|
63
|
+
process.stderr.write(`${error.detail.message}\n`);
|
|
64
|
+
process.exitCode = error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.detail.retryable ? 2 : 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (response.phase === 'rejected') {
|
|
68
|
+
const error = response.error ?? {
|
|
69
|
+
code: 'SANDBOX_CONTROL_REJECTED',
|
|
70
|
+
message: 'sandbox control rejected the finalization request',
|
|
71
|
+
retryable: false
|
|
72
|
+
};
|
|
73
|
+
writeFinalizationEnvelope(finalizationErrorStatus(error), error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN', error);
|
|
74
|
+
process.stderr.write(`${error.message}\n`);
|
|
75
|
+
process.exitCode = error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.retryable ? 2 : 1;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
process.stdout.write(response.stdout);
|
|
79
|
+
process.stderr.write(response.stderr);
|
|
80
|
+
process.exitCode = response.exitCode ?? 1;
|
|
81
|
+
}
|
|
4
82
|
|
|
5
83
|
async function sandboxControl(args: string[]): Promise<void> {
|
|
6
84
|
const [operation, ...rest] = args;
|
|
@@ -31,9 +109,21 @@ async function sandboxControl(args: string[]): Promise<void> {
|
|
|
31
109
|
}
|
|
32
110
|
if (operation === 'client') {
|
|
33
111
|
const [family = '', ...commandArgs] = rest;
|
|
112
|
+
if (family === 'task-finalization') {
|
|
113
|
+
sandboxFinalizationClient(commandArgs);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
34
116
|
let response;
|
|
35
117
|
try {
|
|
36
|
-
|
|
118
|
+
if (family === 'task-orchestration' && isCanonicalCodexPrepare(commandArgs)) {
|
|
119
|
+
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
120
|
+
const proof = contextPath
|
|
121
|
+
? controllerProofFromContext(verifyCodexSandboxControllerContext(contextPath))
|
|
122
|
+
: null;
|
|
123
|
+
response = requestSandboxTaskControl({ family, args: commandArgs, controllerProof: proof });
|
|
124
|
+
} else {
|
|
125
|
+
response = requestSandboxControl({ family, args: commandArgs });
|
|
126
|
+
}
|
|
37
127
|
} catch (error) {
|
|
38
128
|
if (!(error instanceof SandboxControlClientError)) throw error;
|
|
39
129
|
process.stderr.write(`${error.detail.message}\n`);
|