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