@fitlab-ai/agent-infra 0.11.13 → 0.11.14
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 -7
- package/dist/lib/agent-clients/adapter.js +0 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +5 -9
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +1 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +1 -5
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -3
- package/dist/lib/agent-clients/adapters/codex.js +0 -2
- package/dist/lib/internal/cli-route-inventory.js +0 -1
- package/dist/lib/internal/task-finalization.js +6 -4
- package/dist/lib/internal/task-lifecycle.js +3 -1
- package/dist/lib/internal/task-operation-registry.js +1 -3
- package/dist/lib/internal/task-validate.js +14 -124
- package/dist/lib/platform/release-notes.js +2 -2
- package/dist/lib/sandbox/commands/control-status.js +0 -1
- package/dist/lib/sandbox/commands/create.js +5 -4
- package/dist/lib/sandbox/config.js +12 -0
- package/dist/lib/sandbox/control/audit.js +1 -2
- package/dist/lib/sandbox/control/client.js +12 -47
- package/dist/lib/sandbox/control/executor.js +6 -10
- package/dist/lib/sandbox/control/host-recovery.js +89 -27
- package/dist/lib/sandbox/control/lifecycle.js +20 -16
- package/dist/lib/sandbox/control/protocol.js +8 -36
- package/dist/lib/sandbox/control/server.js +2 -21
- package/dist/lib/sandbox/environment.js +7 -8
- package/dist/lib/sandbox/mounts.js +71 -59
- package/dist/lib/sandbox/recovery.js +2 -3
- package/dist/lib/sandbox/removal.js +19 -4
- package/dist/lib/sandbox/workspace-view.js +41 -4
- package/dist/lib/task/control-authority.js +39 -17
- package/dist/lib/task/control-recovery.js +1 -2
- package/dist/lib/task/finalization.js +13 -2
- package/dist/lib/task/lifecycle.js +92 -33
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +0 -27
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +6 -12
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +1 -5
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -7
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +1 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -4
- package/lib/agent-clients/adapters/codex-orchestration.ts +1 -3
- package/lib/agent-clients/adapters/codex.ts +0 -2
- package/lib/internal/cli-route-inventory.ts +0 -1
- package/lib/internal/task-finalization.ts +6 -4
- package/lib/internal/task-lifecycle.ts +3 -1
- package/lib/internal/task-operation-registry.ts +1 -3
- package/lib/internal/task-validate.ts +13 -127
- package/lib/platform/release-notes.ts +2 -2
- package/lib/sandbox/commands/control-status.ts +0 -1
- package/lib/sandbox/commands/create.ts +5 -4
- package/lib/sandbox/config.ts +13 -0
- package/lib/sandbox/control/audit.ts +1 -2
- package/lib/sandbox/control/client.ts +13 -61
- package/lib/sandbox/control/executor.ts +7 -13
- package/lib/sandbox/control/host-recovery.ts +94 -21
- package/lib/sandbox/control/lifecycle.ts +18 -16
- package/lib/sandbox/control/protocol.ts +9 -45
- package/lib/sandbox/control/server.ts +2 -17
- package/lib/sandbox/environment.ts +7 -8
- package/lib/sandbox/mounts.ts +92 -75
- package/lib/sandbox/recovery.ts +2 -3
- package/lib/sandbox/removal.ts +16 -4
- package/lib/sandbox/workspace-view.ts +40 -5
- package/lib/task/control-authority.ts +40 -26
- package/lib/task/control-recovery.ts +2 -3
- package/lib/task/finalization.ts +15 -2
- package/lib/task/lifecycle.ts +103 -31
- package/package.json +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +3 -4
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +3 -4
- package/templates/.agents/rules/task-short-id.en.md +18 -12
- package/templates/.agents/rules/task-short-id.zh-CN.md +12 -7
- package/templates/.agents/skills/create-release-note/SKILL.en.md +3 -1
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +3 -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/controller-client.js +0 -130
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +0 -127
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +0 -131
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-registration.js +0 -275
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js +0 -106
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +0 -345
- package/dist/lib/sandbox/control/agent-operation.js +0 -34
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +0 -158
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +0 -163
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +0 -151
- package/lib/agent-clients/adapters/codex-lifecycle/controller-registration.ts +0 -352
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts +0 -109
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +0 -437
- package/lib/sandbox/control/agent-operation.ts +0 -49
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
runCodexSandboxController,
|
|
3
|
-
verifyCodexSandboxControllerContextWithWarnings
|
|
4
|
-
} from './sandbox-controller.ts';
|
|
5
|
-
import { ensureInternalHandlerRoute, internalHandlerRoute } from '../../../internal/cli-route-inventory.ts';
|
|
6
|
-
|
|
7
|
-
const USAGE = 'Usage: agent-infra-internal codex-sandbox-controller <run|verify-context> [options]\n';
|
|
8
|
-
|
|
9
|
-
function fail(code: string, message: string, exitCode = 1): void {
|
|
10
|
-
process.stdout.write(`${JSON.stringify({
|
|
11
|
-
status: 'failed',
|
|
12
|
-
changed: false,
|
|
13
|
-
error: { code, message }
|
|
14
|
-
})}\n`);
|
|
15
|
-
process.exitCode = exitCode;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function parse(args: string[]): Readonly<{ operation: string; values: Readonly<Record<string, string>> }> | null {
|
|
19
|
-
if (args[0] === '--help' || args[0] === '-h') {
|
|
20
|
-
process.stdout.write(USAGE);
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
const operation = args[0];
|
|
24
|
-
if (!operation || ![
|
|
25
|
-
internalHandlerRoute('codex-sandbox-controller', 'run', operation),
|
|
26
|
-
internalHandlerRoute('codex-sandbox-controller', 'verify-context', operation)
|
|
27
|
-
].some(Boolean)) {
|
|
28
|
-
process.stderr.write(USAGE);
|
|
29
|
-
fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', 'operation must be run or verify-context', 2);
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
const allowed = new Set([
|
|
33
|
-
'--context',
|
|
34
|
-
'--executor-model',
|
|
35
|
-
'--executor-reasoning-effort',
|
|
36
|
-
'--reviewer-model',
|
|
37
|
-
'--reviewer-reasoning-effort'
|
|
38
|
-
]);
|
|
39
|
-
const values: Record<string, string> = {};
|
|
40
|
-
for (let index = 1; index < args.length; index += 1) {
|
|
41
|
-
const flag = args[index]!;
|
|
42
|
-
if (flag === '--task-id' || flag.startsWith('--task-id=')
|
|
43
|
-
|| flag === '--task-ref' || flag.startsWith('--task-ref=')) {
|
|
44
|
-
fail('CODEX_SANDBOX_CONTROLLER_IDENTITY_OPTION_UNSUPPORTED', 'controller task identity options are not supported', 2);
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
const value = args[++index];
|
|
48
|
-
if (!allowed.has(flag) || Object.hasOwn(values, flag) || !value || value.startsWith('--')) {
|
|
49
|
-
fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', `invalid option '${flag}'`, 2);
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
values[flag] = value;
|
|
53
|
-
}
|
|
54
|
-
return { operation, values };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function codexSandboxController(args: string[] = []): Promise<void> {
|
|
58
|
-
if (!ensureInternalHandlerRoute('codex-sandbox-controller', args)) return;
|
|
59
|
-
const parsed = parse(args);
|
|
60
|
-
if (!parsed || process.exitCode) return;
|
|
61
|
-
try {
|
|
62
|
-
if (internalHandlerRoute('codex-sandbox-controller', 'verify-context', parsed.operation)) {
|
|
63
|
-
const contextPath = parsed.values['--context']
|
|
64
|
-
?? process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
65
|
-
if (!contextPath) throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_MISSING');
|
|
66
|
-
const verified = verifyCodexSandboxControllerContextWithWarnings(contextPath);
|
|
67
|
-
const context = verified.context;
|
|
68
|
-
process.stdout.write(`${JSON.stringify({
|
|
69
|
-
status: 'ready',
|
|
70
|
-
changed: false,
|
|
71
|
-
context: {
|
|
72
|
-
taskId: context.taskId,
|
|
73
|
-
controlGeneration: context.controlGeneration,
|
|
74
|
-
controllerInstanceDigest: context.controllerInstanceDigest,
|
|
75
|
-
expiresAt: context.expiresAt,
|
|
76
|
-
buildIdentity: context.buildIdentity,
|
|
77
|
-
hookDefinitionHash: context.hookDefinitionHash,
|
|
78
|
-
},
|
|
79
|
-
...(verified.warnings.length > 0 ? { warnings: verified.warnings } : {}),
|
|
80
|
-
error: null
|
|
81
|
-
})}\n`);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const policy = [
|
|
85
|
-
'--executor-model',
|
|
86
|
-
'--executor-reasoning-effort',
|
|
87
|
-
'--reviewer-model',
|
|
88
|
-
'--reviewer-reasoning-effort'
|
|
89
|
-
];
|
|
90
|
-
if (policy.some((flag) => parsed.values[flag] !== undefined)
|
|
91
|
-
&& policy.some((flag) => parsed.values[flag] === undefined)) {
|
|
92
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_MODEL_POLICY_INVALID');
|
|
93
|
-
}
|
|
94
|
-
const exitCode = await runCodexSandboxController({
|
|
95
|
-
executorModel: parsed.values['--executor-model'],
|
|
96
|
-
executorReasoningEffort: parsed.values['--executor-reasoning-effort'],
|
|
97
|
-
reviewerModel: parsed.values['--reviewer-model'],
|
|
98
|
-
reviewerReasoningEffort: parsed.values['--reviewer-reasoning-effort']
|
|
99
|
-
});
|
|
100
|
-
process.exitCode = exitCode;
|
|
101
|
-
} catch (error) {
|
|
102
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
103
|
-
const code = /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1]
|
|
104
|
-
?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
|
|
105
|
-
fail(code, message);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export { codexSandboxController };
|
|
@@ -1,437 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import semver from 'semver';
|
|
7
|
-
import * as toml from 'smol-toml';
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
requestCodexControllerClose,
|
|
11
|
-
requestCodexControllerOpen,
|
|
12
|
-
requestCodexControllerVerify
|
|
13
|
-
} from './controller-client.ts';
|
|
14
|
-
import { getProcessStartTime, type ProcessIdentity } from '../../../server/process-state.ts';
|
|
15
|
-
import { LIFECYCLE_PROTOCOL_VERSION, type LifecycleIdentityWarning } from './build-identity.ts';
|
|
16
|
-
import {
|
|
17
|
-
contextFromControllerLease,
|
|
18
|
-
controllerProofFromContext,
|
|
19
|
-
verifyCodexSandboxControllerContextWithWarnings as verifyContextFileWithWarnings,
|
|
20
|
-
writeCodexSandboxControllerContext,
|
|
21
|
-
type CodexSandboxControllerContextV2
|
|
22
|
-
} from './controller-context.ts';
|
|
23
|
-
|
|
24
|
-
type ControllerControl = Readonly<{
|
|
25
|
-
token: string;
|
|
26
|
-
generation: string;
|
|
27
|
-
rootId?: string;
|
|
28
|
-
channelDir: string;
|
|
29
|
-
statusDir: string;
|
|
30
|
-
runtimeDir: string;
|
|
31
|
-
}>;
|
|
32
|
-
|
|
33
|
-
type ControllerInput = Readonly<{
|
|
34
|
-
executorModel?: string;
|
|
35
|
-
executorReasoningEffort?: string;
|
|
36
|
-
reviewerModel?: string;
|
|
37
|
-
reviewerReasoningEffort?: string;
|
|
38
|
-
}>;
|
|
39
|
-
|
|
40
|
-
type ControllerOptions = Readonly<{
|
|
41
|
-
repoRoot?: string;
|
|
42
|
-
codexHome?: string;
|
|
43
|
-
temporaryRoot?: string;
|
|
44
|
-
control?: ControllerControl;
|
|
45
|
-
now?: () => number;
|
|
46
|
-
codexVersion?: () => string;
|
|
47
|
-
verifyController?: typeof requestCodexControllerVerify;
|
|
48
|
-
openController?: typeof requestCodexControllerOpen;
|
|
49
|
-
closeController?: typeof requestCodexControllerClose;
|
|
50
|
-
environment?: NodeJS.ProcessEnv;
|
|
51
|
-
}>;
|
|
52
|
-
|
|
53
|
-
type PreparedCodexSandboxController = Readonly<{
|
|
54
|
-
command: 'codex';
|
|
55
|
-
args: readonly string[];
|
|
56
|
-
env: NodeJS.ProcessEnv;
|
|
57
|
-
home: string;
|
|
58
|
-
contextPath: string;
|
|
59
|
-
context: CodexSandboxControllerContextV2;
|
|
60
|
-
cleanup: () => void;
|
|
61
|
-
}>;
|
|
62
|
-
|
|
63
|
-
type VerifyContextOptions = Readonly<{
|
|
64
|
-
repoRoot?: string;
|
|
65
|
-
now?: number;
|
|
66
|
-
generation?: string;
|
|
67
|
-
probeProcess?: (identity: ProcessIdentity) => 'alive' | 'dead' | 'unknown';
|
|
68
|
-
control?: ControllerControl;
|
|
69
|
-
requestControllerVerify?: typeof requestCodexControllerVerify;
|
|
70
|
-
}>;
|
|
71
|
-
|
|
72
|
-
function copyRegular(source: string, destination: string, mode: number): void {
|
|
73
|
-
const stat = fs.lstatSync(source);
|
|
74
|
-
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
75
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_INPUT_INVALID');
|
|
76
|
-
}
|
|
77
|
-
fs.mkdirSync(path.dirname(destination), { recursive: true, mode: 0o700 });
|
|
78
|
-
fs.copyFileSync(source, destination);
|
|
79
|
-
fs.chmodSync(destination, mode);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function controlFromEnvironment(): ControllerControl {
|
|
83
|
-
const token = process.env.AGENT_INFRA_CONTROL_TOKEN;
|
|
84
|
-
const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
85
|
-
const rootId = process.env.AGENT_INFRA_CONTROL_ROOT_ID;
|
|
86
|
-
const channelDir = process.env.AGENT_INFRA_CONTROL_DIR;
|
|
87
|
-
const statusDir = process.env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
88
|
-
const runtimeDir = process.env.AGENT_INFRA_RUNTIME_DIR;
|
|
89
|
-
if (!token || !generation || !rootId || !channelDir || !statusDir || !runtimeDir) {
|
|
90
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTROL_MISSING');
|
|
91
|
-
}
|
|
92
|
-
return { token, generation, rootId, channelDir, statusDir, runtimeDir };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function verifyRuntime(runtimeDir: string): void {
|
|
96
|
-
let stat: fs.Stats;
|
|
97
|
-
try {
|
|
98
|
-
stat = fs.lstatSync(runtimeDir);
|
|
99
|
-
} catch {
|
|
100
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_RUNTIME_UNAVAILABLE');
|
|
101
|
-
}
|
|
102
|
-
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
103
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_RUNTIME_UNAVAILABLE');
|
|
104
|
-
}
|
|
105
|
-
const probe = path.join(runtimeDir, `.controller-${process.pid}-${crypto.randomUUID()}.probe`);
|
|
106
|
-
try {
|
|
107
|
-
fs.writeFileSync(probe, 'runtime-probe\n', { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
108
|
-
if (fs.readFileSync(probe, 'utf8') !== 'runtime-probe\n') {
|
|
109
|
-
throw new Error('probe mismatch');
|
|
110
|
-
}
|
|
111
|
-
} catch {
|
|
112
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_RUNTIME_UNAVAILABLE');
|
|
113
|
-
} finally {
|
|
114
|
-
fs.rmSync(probe, { force: true });
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function verifyControllerBinding(
|
|
119
|
-
context: CodexSandboxControllerContextV2,
|
|
120
|
-
control: ControllerControl,
|
|
121
|
-
requestControllerVerify: typeof requestCodexControllerVerify
|
|
122
|
-
): void {
|
|
123
|
-
verifyRuntime(control.runtimeDir);
|
|
124
|
-
const verified = requestControllerVerify({
|
|
125
|
-
controllerProof: controllerProofFromContext(context),
|
|
126
|
-
...control,
|
|
127
|
-
timeoutMs: 30_000
|
|
128
|
-
});
|
|
129
|
-
if (verified.binding.controlGeneration !== context.controlGeneration
|
|
130
|
-
|| verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
|
|
131
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function verifyCodexSandboxControllerContextWithWarnings(
|
|
136
|
-
contextPath: string,
|
|
137
|
-
options: VerifyContextOptions = {}
|
|
138
|
-
): Readonly<{ context: CodexSandboxControllerContextV2; warnings: readonly LifecycleIdentityWarning[] }> {
|
|
139
|
-
const control = options.control ?? controlFromEnvironment();
|
|
140
|
-
const fileVerification = verifyContextFileWithWarnings(contextPath, {
|
|
141
|
-
repoRoot: options.repoRoot,
|
|
142
|
-
now: options.now,
|
|
143
|
-
generation: control.generation,
|
|
144
|
-
probeProcess: options.probeProcess
|
|
145
|
-
});
|
|
146
|
-
const context = fileVerification.context;
|
|
147
|
-
const verified = (options.requestControllerVerify ?? requestCodexControllerVerify)({
|
|
148
|
-
controllerProof: controllerProofFromContext(context),
|
|
149
|
-
...control,
|
|
150
|
-
timeoutMs: 30_000
|
|
151
|
-
});
|
|
152
|
-
if (verified.binding.controlGeneration !== context.controlGeneration
|
|
153
|
-
|| verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
|
|
154
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
155
|
-
}
|
|
156
|
-
return Object.freeze({
|
|
157
|
-
context,
|
|
158
|
-
warnings: fileVerification.warnings
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function detectedCodexVersion(): string {
|
|
163
|
-
const result = spawnSync('codex', ['--version'], { encoding: 'utf8' });
|
|
164
|
-
const version = /codex-cli\s+(\d+\.\d+\.\d+)/u.exec(result.stdout ?? '')?.[1];
|
|
165
|
-
if (result.status !== 0 || !version) throw new Error('CODEX_SANDBOX_CONTROLLER_CODEX_UNAVAILABLE');
|
|
166
|
-
return version;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function sanitizedConfig(source: string, destination: string, sourceHome: string, targetHome: string): readonly string[] {
|
|
170
|
-
if (!fs.existsSync(source)) return [];
|
|
171
|
-
const parsed = toml.parse(fs.readFileSync(source, 'utf8')) as Record<string, unknown>;
|
|
172
|
-
const allowed = new Set([
|
|
173
|
-
'model',
|
|
174
|
-
'model_provider',
|
|
175
|
-
'model_providers',
|
|
176
|
-
'model_reasoning_effort',
|
|
177
|
-
'model_auto_compact_token_limit',
|
|
178
|
-
'preferred_auth_method'
|
|
179
|
-
]);
|
|
180
|
-
const output = Object.fromEntries(Object.entries(parsed).filter(([key]) => allowed.has(key)));
|
|
181
|
-
const catalog = parsed.model_catalog_json;
|
|
182
|
-
if (typeof catalog === 'string' && catalog.trim()) {
|
|
183
|
-
const resolved = path.resolve(sourceHome, catalog.replace(/^~[/\\]/u, ''));
|
|
184
|
-
const relative = path.relative(sourceHome, resolved);
|
|
185
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
186
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_CONFIG_PATH_INVALID');
|
|
187
|
-
}
|
|
188
|
-
const target = path.join(targetHome, 'model-catalogs', path.basename(resolved));
|
|
189
|
-
copyRegular(resolved, target, 0o600);
|
|
190
|
-
output.model_catalog_json = target;
|
|
191
|
-
}
|
|
192
|
-
fs.writeFileSync(destination, `${toml.stringify(output)}\n`, { mode: 0o600 });
|
|
193
|
-
const providerEnvironment = new Set<string>();
|
|
194
|
-
const providers = output.model_providers;
|
|
195
|
-
if (providers && typeof providers === 'object' && !Array.isArray(providers)) {
|
|
196
|
-
for (const provider of Object.values(providers)) {
|
|
197
|
-
if (!provider || typeof provider !== 'object' || Array.isArray(provider)) continue;
|
|
198
|
-
for (const key of ['env_key', 'envKey']) {
|
|
199
|
-
const name = (provider as Record<string, unknown>)[key];
|
|
200
|
-
if (typeof name === 'string' && /^[A-Z][A-Z0-9_]{1,127}$/u.test(name)) {
|
|
201
|
-
providerEnvironment.add(name);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return Object.freeze([...providerEnvironment]);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function isolatedEnvironment(
|
|
210
|
-
home: string,
|
|
211
|
-
shimDir: string,
|
|
212
|
-
contextPath: string,
|
|
213
|
-
taskId: string,
|
|
214
|
-
control: ControllerControl,
|
|
215
|
-
providerEnvironment: readonly string[],
|
|
216
|
-
sourceEnvironment: NodeJS.ProcessEnv
|
|
217
|
-
): NodeJS.ProcessEnv {
|
|
218
|
-
const inherited = new Set([
|
|
219
|
-
'LANG', 'LC_ALL', 'TERM', 'COLORTERM',
|
|
220
|
-
'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY',
|
|
221
|
-
'http_proxy', 'https_proxy', 'no_proxy',
|
|
222
|
-
'SSL_CERT_FILE', 'SSL_CERT_DIR', 'NODE_EXTRA_CA_CERTS',
|
|
223
|
-
'CODEX_CI',
|
|
224
|
-
...providerEnvironment
|
|
225
|
-
]);
|
|
226
|
-
const env: NodeJS.ProcessEnv = {};
|
|
227
|
-
for (const key of inherited) {
|
|
228
|
-
if (sourceEnvironment[key] !== undefined) env[key] = sourceEnvironment[key];
|
|
229
|
-
}
|
|
230
|
-
return {
|
|
231
|
-
...env,
|
|
232
|
-
HOME: home,
|
|
233
|
-
CODEX_HOME: home,
|
|
234
|
-
PATH: `${shimDir}${path.delimiter}${sourceEnvironment.PATH ?? ''}`,
|
|
235
|
-
AGENT_INFRA_TASK_ID: taskId,
|
|
236
|
-
AGENT_INFRA_CONTROL_TOKEN: control.token,
|
|
237
|
-
AGENT_INFRA_CONTROL_GENERATION: control.generation,
|
|
238
|
-
...(control.rootId || sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID
|
|
239
|
-
? { AGENT_INFRA_CONTROL_ROOT_ID: control.rootId ?? sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID }
|
|
240
|
-
: {}),
|
|
241
|
-
AGENT_INFRA_CONTROL_DIR: control.channelDir,
|
|
242
|
-
AGENT_INFRA_CONTROL_STATUS_DIR: control.statusDir,
|
|
243
|
-
AGENT_INFRA_RUNTIME_DIR: control.runtimeDir,
|
|
244
|
-
AGENT_INFRA_CODEX_CONTROLLER_CONTEXT: contextPath
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function shellQuote(value: string): string {
|
|
249
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function prepareCodexSandboxController(
|
|
253
|
-
input: ControllerInput,
|
|
254
|
-
options: ControllerOptions = {}
|
|
255
|
-
): PreparedCodexSandboxController {
|
|
256
|
-
const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
257
|
-
const sourceHome = path.resolve(options.codexHome ?? process.env.CODEX_HOME ?? path.join(os.homedir(), '.codex'));
|
|
258
|
-
const control = options.control ?? controlFromEnvironment();
|
|
259
|
-
const version = (options.codexVersion ?? detectedCodexVersion)();
|
|
260
|
-
if (!semver.gte(version, '0.147.0')) throw new Error('CODEX_SANDBOX_CONTROLLER_CODEX_UNSUPPORTED');
|
|
261
|
-
|
|
262
|
-
const runtimeRoot = path.resolve(options.temporaryRoot
|
|
263
|
-
?? path.join(os.tmpdir(), 'agent-infra-codex-controllers'));
|
|
264
|
-
fs.mkdirSync(runtimeRoot, { recursive: true, mode: 0o700 });
|
|
265
|
-
fs.chmodSync(runtimeRoot, 0o700);
|
|
266
|
-
const key = crypto.createHash('sha256')
|
|
267
|
-
.update(`${process.pid}\0${control.generation}`)
|
|
268
|
-
.digest('hex');
|
|
269
|
-
const parentStartTime = getProcessStartTime(process.pid);
|
|
270
|
-
if (!parentStartTime) throw new Error('CODEX_SANDBOX_CONTROLLER_PROCESS_IDENTITY_INVALID');
|
|
271
|
-
const home = fs.mkdtempSync(path.join(runtimeRoot, `${key}-`));
|
|
272
|
-
fs.chmodSync(home, 0o700);
|
|
273
|
-
let cleaned = false;
|
|
274
|
-
let context: CodexSandboxControllerContextV2 | null = null;
|
|
275
|
-
const cleanup = () => {
|
|
276
|
-
if (cleaned) return;
|
|
277
|
-
cleaned = true;
|
|
278
|
-
if (context) {
|
|
279
|
-
try {
|
|
280
|
-
(options.closeController ?? requestCodexControllerClose)({
|
|
281
|
-
controllerProcess: context.controllerProcess,
|
|
282
|
-
controllerProof: controllerProofFromContext(context),
|
|
283
|
-
...control,
|
|
284
|
-
timeoutMs: 30_000
|
|
285
|
-
});
|
|
286
|
-
} catch {
|
|
287
|
-
// The wrapper is exiting; dead-process recovery safely handles an unknown close result.
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
const relative = path.relative(runtimeRoot, home);
|
|
291
|
-
if (!relative.startsWith('..') && !path.isAbsolute(relative)) {
|
|
292
|
-
fs.rmSync(home, { recursive: true, force: true });
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
const auth = path.join(sourceHome, 'auth.json');
|
|
298
|
-
if (fs.existsSync(auth)) copyRegular(auth, path.join(home, 'auth.json'), 0o600);
|
|
299
|
-
const providerEnvironment = sanitizedConfig(
|
|
300
|
-
path.join(sourceHome, 'config.toml'),
|
|
301
|
-
path.join(home, 'config.toml'),
|
|
302
|
-
sourceHome,
|
|
303
|
-
home
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
const hooks = path.join(repoRoot, '.codex', 'hooks.json');
|
|
307
|
-
const executor = path.join(repoRoot, '.codex', 'agents', 'agent-infra-lifecycle-executor.toml');
|
|
308
|
-
const reviewer = path.join(repoRoot, '.codex', 'agents', 'agent-infra-lifecycle-reviewer.toml');
|
|
309
|
-
copyRegular(hooks, path.join(home, 'hooks.json'), 0o600);
|
|
310
|
-
copyRegular(executor, path.join(home, 'agents', path.basename(executor)), 0o600);
|
|
311
|
-
copyRegular(reviewer, path.join(home, 'agents', path.basename(reviewer)), 0o600);
|
|
312
|
-
|
|
313
|
-
const opened = (options.openController ?? requestCodexControllerOpen)({
|
|
314
|
-
controllerProcess: { pid: process.pid, startTime: parentStartTime },
|
|
315
|
-
...control,
|
|
316
|
-
timeoutMs: 30_000
|
|
317
|
-
});
|
|
318
|
-
const taskId = opened.lease.taskId;
|
|
319
|
-
if (opened.lease.buildIdentity.protocolVersion !== LIFECYCLE_PROTOCOL_VERSION) {
|
|
320
|
-
throw new Error('CODEX_LIFECYCLE_PROTOCOL_MISMATCH: Codex lifecycle protocol version does not match');
|
|
321
|
-
}
|
|
322
|
-
context = contextFromControllerLease(opened.lease, {
|
|
323
|
-
hookDefinitionHash: crypto.createHash('sha256').update(fs.readFileSync(hooks)).digest('hex')
|
|
324
|
-
});
|
|
325
|
-
if (opened.lease.controlGeneration !== control.generation
|
|
326
|
-
|| opened.lease.controllerProcess.pid !== process.pid
|
|
327
|
-
|| opened.lease.controllerProcess.startTime !== parentStartTime) {
|
|
328
|
-
throw new Error('SANDBOX_CONTROL_RESULT_INVALID');
|
|
329
|
-
}
|
|
330
|
-
verifyControllerBinding(context, control, options.verifyController ?? requestCodexControllerVerify);
|
|
331
|
-
const contextPath = path.join(home, 'controller-context.json');
|
|
332
|
-
writeCodexSandboxControllerContext(contextPath, context);
|
|
333
|
-
|
|
334
|
-
const shimDir = path.join(home, 'bin');
|
|
335
|
-
fs.mkdirSync(shimDir, { mode: 0o700 });
|
|
336
|
-
const internalCli = path.resolve(process.argv[1] ?? path.join(repoRoot, 'bin', 'internal-cli.ts'));
|
|
337
|
-
const launcherPrefix = ['#!/bin/sh', 'set -eu', 'unset NODE_OPTIONS NODE_PATH'].join('\n') + '\n';
|
|
338
|
-
const source = internalCli.endsWith('.ts')
|
|
339
|
-
? `${launcherPrefix}exec ${shellQuote(process.execPath)} --experimental-strip-types ${shellQuote(internalCli)} "$@"\n`
|
|
340
|
-
: `${launcherPrefix}exec ${shellQuote(process.execPath)} ${shellQuote(internalCli)} "$@"\n`;
|
|
341
|
-
fs.writeFileSync(path.join(shimDir, 'agent-infra-internal'), source, { mode: 0o700 });
|
|
342
|
-
|
|
343
|
-
const policy = [
|
|
344
|
-
input.executorModel ? `--executor-model ${input.executorModel}` : '',
|
|
345
|
-
input.executorReasoningEffort ? `--executor-reasoning-effort ${input.executorReasoningEffort}` : '',
|
|
346
|
-
input.reviewerModel ? `--reviewer-model ${input.reviewerModel}` : '',
|
|
347
|
-
input.reviewerReasoningEffort ? `--reviewer-reasoning-effort ${input.reviewerReasoningEffort}` : ''
|
|
348
|
-
].filter(Boolean).join(' ');
|
|
349
|
-
const prompt = `$run-task ${taskId}${policy ? ` ${policy}` : ''}`;
|
|
350
|
-
const args = Object.freeze([
|
|
351
|
-
'exec',
|
|
352
|
-
'--enable', 'hooks',
|
|
353
|
-
'--enable', 'multi_agent',
|
|
354
|
-
'--dangerously-bypass-hook-trust',
|
|
355
|
-
'--dangerously-bypass-approvals-and-sandbox',
|
|
356
|
-
'--json',
|
|
357
|
-
'-C', repoRoot,
|
|
358
|
-
prompt
|
|
359
|
-
]);
|
|
360
|
-
const env = isolatedEnvironment(
|
|
361
|
-
home,
|
|
362
|
-
shimDir,
|
|
363
|
-
contextPath,
|
|
364
|
-
taskId,
|
|
365
|
-
control,
|
|
366
|
-
providerEnvironment,
|
|
367
|
-
options.environment ?? process.env
|
|
368
|
-
);
|
|
369
|
-
return Object.freeze({
|
|
370
|
-
command: 'codex' as const,
|
|
371
|
-
args,
|
|
372
|
-
env,
|
|
373
|
-
home,
|
|
374
|
-
contextPath,
|
|
375
|
-
context,
|
|
376
|
-
cleanup
|
|
377
|
-
});
|
|
378
|
-
} catch (error) {
|
|
379
|
-
cleanup();
|
|
380
|
-
throw error;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
async function runCodexSandboxController(
|
|
385
|
-
input: ControllerInput,
|
|
386
|
-
options: ControllerOptions = {}
|
|
387
|
-
): Promise<number> {
|
|
388
|
-
const prepared = prepareCodexSandboxController(input, options);
|
|
389
|
-
try {
|
|
390
|
-
const child = spawn(prepared.command, [...prepared.args], {
|
|
391
|
-
cwd: options.repoRoot ?? process.cwd(),
|
|
392
|
-
env: prepared.env,
|
|
393
|
-
stdio: 'inherit',
|
|
394
|
-
detached: process.platform !== 'win32'
|
|
395
|
-
});
|
|
396
|
-
let forceKill: NodeJS.Timeout | undefined;
|
|
397
|
-
const terminate = () => {
|
|
398
|
-
if (child.pid && process.platform !== 'win32') {
|
|
399
|
-
try { process.kill(-child.pid, 'SIGTERM'); } catch { child.kill('SIGTERM'); }
|
|
400
|
-
} else child.kill('SIGTERM');
|
|
401
|
-
forceKill = setTimeout(() => {
|
|
402
|
-
if (child.pid && process.platform !== 'win32') {
|
|
403
|
-
try { process.kill(-child.pid, 'SIGKILL'); } catch { child.kill('SIGKILL'); }
|
|
404
|
-
} else child.kill('SIGKILL');
|
|
405
|
-
}, 5_000);
|
|
406
|
-
forceKill.unref();
|
|
407
|
-
};
|
|
408
|
-
process.once('SIGINT', terminate);
|
|
409
|
-
process.once('SIGTERM', terminate);
|
|
410
|
-
try {
|
|
411
|
-
return await new Promise<number>((resolve, reject) => {
|
|
412
|
-
child.once('error', reject);
|
|
413
|
-
child.once('exit', (code, signal) => resolve(code ?? (signal ? 1 : 0)));
|
|
414
|
-
});
|
|
415
|
-
} finally {
|
|
416
|
-
if (forceKill) clearTimeout(forceKill);
|
|
417
|
-
process.off('SIGINT', terminate);
|
|
418
|
-
process.off('SIGTERM', terminate);
|
|
419
|
-
}
|
|
420
|
-
} finally {
|
|
421
|
-
prepared.cleanup();
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
export {
|
|
426
|
-
prepareCodexSandboxController,
|
|
427
|
-
runCodexSandboxController,
|
|
428
|
-
verifyCodexSandboxControllerContextWithWarnings,
|
|
429
|
-
controllerProofFromContext
|
|
430
|
-
};
|
|
431
|
-
export type {
|
|
432
|
-
CodexSandboxControllerContextV2 as CodexSandboxControllerContext,
|
|
433
|
-
ControllerControl,
|
|
434
|
-
ControllerInput,
|
|
435
|
-
ControllerOptions,
|
|
436
|
-
PreparedCodexSandboxController
|
|
437
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { getAgentClientAdapter } from '../../agent-clients/registry.ts';
|
|
2
|
-
import type { AgentClientSandboxControlOperation } from '../../agent-clients/adapter.ts';
|
|
3
|
-
import type { SandboxControlManifest, SandboxAgentClientRequest } from './protocol.ts';
|
|
4
|
-
|
|
5
|
-
function operationFor(request: SandboxAgentClientRequest) {
|
|
6
|
-
const adapter = getAgentClientAdapter(request.agentClient);
|
|
7
|
-
const operation = adapter.sandboxControlOperation;
|
|
8
|
-
if (!operation) throw new Error('SANDBOX_CONTROL_AGENT_OPERATION_UNSUPPORTED');
|
|
9
|
-
return operation;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function validateAgentClientOperation(request: SandboxAgentClientRequest, manifest: SandboxControlManifest): void {
|
|
13
|
-
operationFor(request).validate({
|
|
14
|
-
operation: request.operation,
|
|
15
|
-
payload: request.payload,
|
|
16
|
-
manifest
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function executeAgentClientOperation(
|
|
21
|
-
request: SandboxAgentClientRequest,
|
|
22
|
-
manifest: SandboxControlManifest,
|
|
23
|
-
manifestPath: string,
|
|
24
|
-
override?: AgentClientSandboxControlOperation
|
|
25
|
-
) {
|
|
26
|
-
return (override ?? operationFor(request)).execute({
|
|
27
|
-
operation: request.operation,
|
|
28
|
-
payload: request.payload,
|
|
29
|
-
manifest,
|
|
30
|
-
manifestPath
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function recoverAgentClientOperation(params: Readonly<{
|
|
35
|
-
request: SandboxAgentClientRequest;
|
|
36
|
-
manifest: SandboxControlManifest;
|
|
37
|
-
manifestPath: string;
|
|
38
|
-
stdout: string | null;
|
|
39
|
-
}>): Readonly<Record<string, unknown>> {
|
|
40
|
-
const operation = operationFor(params.request);
|
|
41
|
-
return operation.recover?.({
|
|
42
|
-
operation: params.request.operation,
|
|
43
|
-
manifest: params.manifest,
|
|
44
|
-
manifestPath: params.manifestPath,
|
|
45
|
-
stdout: params.stdout
|
|
46
|
-
}) ?? { consistent: false };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export { executeAgentClientOperation, recoverAgentClientOperation, validateAgentClientOperation };
|