@fitlab-ai/agent-infra 0.9.6 → 0.9.7
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 +5 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +45 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +21 -5
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -3
- package/dist/lib/internal/codex-lifecycle.js +69 -1
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/task-orchestration.js +37 -5
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/sandbox/commands/create.js +103 -51
- package/dist/lib/sandbox/commands/rm.js +64 -15
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +13 -7
- package/dist/lib/sandbox/control/lifecycle.js +238 -29
- package/dist/lib/sandbox/control/protocol.js +7 -0
- package/dist/lib/sandbox/control/server.js +202 -26
- package/dist/lib/sandbox/control/state.js +25 -15
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/mounts.js +5 -13
- package/dist/lib/sandbox/recovery.js +59 -42
- package/dist/lib/sandbox/workspace-view.js +57 -40
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +11 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +82 -11
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +81 -6
- package/dist/lib/task/commands/validate.js +1 -1
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +84 -2
- package/dist/lib/task/events.js +139 -2
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +235 -14
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +40 -8
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +60 -3
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +33 -4
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -2
- package/lib/internal/codex-lifecycle.ts +68 -1
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/task-orchestration.ts +31 -4
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/sandbox/commands/create.ts +56 -7
- package/lib/sandbox/commands/rm.ts +64 -14
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +14 -10
- package/lib/sandbox/control/lifecycle.ts +264 -33
- package/lib/sandbox/control/protocol.ts +31 -5
- package/lib/sandbox/control/server.ts +184 -24
- package/lib/sandbox/control/state.ts +34 -17
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/mounts.ts +5 -15
- package/lib/sandbox/recovery.ts +71 -42
- package/lib/sandbox/workspace-view.ts +61 -40
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +13 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +73 -10
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +91 -5
- package/lib/task/commands/validate.ts +1 -1
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +160 -8
- package/lib/task/events.ts +122 -1
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +356 -17
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +51 -16
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +25 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -0
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -20,7 +20,8 @@ import { assertManagedPath, removeManagedDir, removeWorktreeDir } from '../manag
|
|
|
20
20
|
import { runEngine, runOk, runOkEngine, runSafe, runSafeEngine } from '../shell.ts';
|
|
21
21
|
import { resolveSandboxTarget, type SandboxWorkspaceIdentity } from '../workspace-identity.ts';
|
|
22
22
|
import { sandboxControlPaths, sandboxWorkspaceViewPaths } from '../workspace-view.ts';
|
|
23
|
-
import {
|
|
23
|
+
import { removeSandboxControlRoot, readSandboxControlManifest } from '../control/lifecycle.ts';
|
|
24
|
+
import { inspectSandboxControlContainer } from '../control/container-identity.ts';
|
|
24
25
|
import { toolConfigDirCandidates, toolProjectDirCandidates } from '../tools.ts';
|
|
25
26
|
import { createSandboxCapabilityPlan } from '../agent-client-reconciler.ts';
|
|
26
27
|
import type { SandboxTool } from '../tools.ts';
|
|
@@ -172,9 +173,40 @@ function removeEmptyManagedParent(base: string, directory: string): void {
|
|
|
172
173
|
assertRemoved(parent, 'Empty sandbox container directory');
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
async function
|
|
176
|
+
async function removeExactSandboxContainer(
|
|
177
|
+
engine: string,
|
|
178
|
+
manifest: ReturnType<typeof readSandboxControlManifest>,
|
|
179
|
+
timeoutMs: number
|
|
180
|
+
): Promise<void> {
|
|
181
|
+
const deadlineAt = Date.now() + timeoutMs;
|
|
182
|
+
const remaining = (): number => Math.max(1, deadlineAt - Date.now());
|
|
183
|
+
const inspect = () => inspectSandboxControlContainer(manifest, { timeoutMs: remaining() });
|
|
184
|
+
const before = await inspect();
|
|
185
|
+
if (before.state === 'unknown') throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${before.reason}`);
|
|
186
|
+
if (before.state === 'absent') return;
|
|
187
|
+
if (before.running && !runOkEngine(engine, 'docker', ['stop', manifest.containerIdentity.id], { timeout: remaining() })) {
|
|
188
|
+
const afterStop = await inspect();
|
|
189
|
+
if (afterStop.state !== 'absent' && afterStop.state !== 'found') {
|
|
190
|
+
throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${afterStop.reason}`);
|
|
191
|
+
}
|
|
192
|
+
if (afterStop.state === 'found' && afterStop.running) {
|
|
193
|
+
throw new Error(`Failed to stop sandbox container: ${manifest.containerIdentity.id}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const afterStop = await inspect();
|
|
197
|
+
if (afterStop.state === 'unknown') throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${afterStop.reason}`);
|
|
198
|
+
if (afterStop.state === 'found' && !runOkEngine(engine, 'docker', ['rm', manifest.containerIdentity.id], { timeout: remaining() })) {
|
|
199
|
+
const afterRemove = await inspect();
|
|
200
|
+
if (afterRemove.state !== 'absent') {
|
|
201
|
+
throw new Error(`Failed to remove sandbox container: ${manifest.containerIdentity.id}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function removeProjectControlRoots(config: SandboxConfig, engine: string): Promise<Set<string>> {
|
|
207
|
+
const containers = new Set<string>();
|
|
176
208
|
const projectRoot = path.join(config.controlBase, config.project);
|
|
177
|
-
if (!fs.existsSync(projectRoot)) return;
|
|
209
|
+
if (!fs.existsSync(projectRoot)) return containers;
|
|
178
210
|
assertManagedPath(config.controlBase, projectRoot);
|
|
179
211
|
const projectStat = fs.lstatSync(projectRoot);
|
|
180
212
|
if (!projectStat.isDirectory() || projectStat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
@@ -183,9 +215,18 @@ async function quiesceProjectControlRoots(config: SandboxConfig): Promise<void>
|
|
|
183
215
|
const containerRoot = path.join(projectRoot, container.name);
|
|
184
216
|
for (const identity of fs.readdirSync(containerRoot, { withFileTypes: true })) {
|
|
185
217
|
if (!identity.isDirectory() || identity.isSymbolicLink()) continue;
|
|
186
|
-
|
|
218
|
+
const root = path.join(containerRoot, identity.name);
|
|
219
|
+
const manifestPath = path.join(root, 'manifest.json');
|
|
220
|
+
if (!fs.existsSync(manifestPath)) throw new Error(`SANDBOX_CONTROL_TARGET_EVIDENCE_MISSING: ${root}`);
|
|
221
|
+
const manifest = readSandboxControlManifest(manifestPath);
|
|
222
|
+
containers.add(manifest.container);
|
|
223
|
+
await removeSandboxControlRoot(root, {
|
|
224
|
+
inspectContainer: (timeoutMs) => inspectSandboxControlContainer(manifest, { timeoutMs }),
|
|
225
|
+
removeContainer: (timeoutMs) => removeExactSandboxContainer(engine, manifest, timeoutMs)
|
|
226
|
+
});
|
|
187
227
|
}
|
|
188
228
|
}
|
|
229
|
+
return containers;
|
|
189
230
|
}
|
|
190
231
|
|
|
191
232
|
function inspectionBlockers(inspections: readonly WorktreeInspection[]): WorktreeInspection[] {
|
|
@@ -277,23 +318,30 @@ async function rmOne(
|
|
|
277
318
|
return;
|
|
278
319
|
}
|
|
279
320
|
|
|
321
|
+
const coordinatedContainers = new Set<string>();
|
|
280
322
|
for (const root of controlRoots.filter((candidate) => fs.existsSync(candidate))) {
|
|
281
323
|
assertLegacyCandidateEvidence(root, config.controlBase, config, effectiveBranch, matchedContainers);
|
|
282
324
|
assertControlRootMatchesTarget(root, effectiveBranch, workspace);
|
|
283
|
-
|
|
325
|
+
const manifest = readSandboxControlManifest(path.join(root, 'manifest.json'));
|
|
326
|
+
coordinatedContainers.add(manifest.container);
|
|
327
|
+
await removeSandboxControlRoot(root, {
|
|
328
|
+
inspectContainer: (timeoutMs) => inspectSandboxControlContainer(manifest, { timeoutMs }),
|
|
329
|
+
removeContainer: (timeoutMs) => removeExactSandboxContainer(engine, manifest, timeoutMs)
|
|
330
|
+
});
|
|
284
331
|
}
|
|
285
332
|
|
|
286
|
-
|
|
333
|
+
const uncoordinatedContainers = matchedContainers.filter((name) => !coordinatedContainers.has(name));
|
|
334
|
+
if (uncoordinatedContainers.length > 0) {
|
|
287
335
|
const spinner = p.spinner();
|
|
288
|
-
spinner.start(`Stopping container(s): ${
|
|
289
|
-
for (const name of
|
|
336
|
+
spinner.start(`Stopping container(s): ${uncoordinatedContainers.join(', ')}`);
|
|
337
|
+
for (const name of uncoordinatedContainers) {
|
|
290
338
|
if (!runOkEngine(engine, 'docker', ['stop', name])) throw new Error(`Failed to stop sandbox container: ${name}`);
|
|
291
339
|
if (!runOkEngine(engine, 'docker', ['rm', name])) throw new Error(`Failed to remove sandbox container: ${name}`);
|
|
292
340
|
}
|
|
293
341
|
const remaining = runEngine(engine, 'docker', ['ps', '-a', '--format', '{{.Names}}']).split('\n').filter(Boolean);
|
|
294
|
-
const leftovers =
|
|
342
|
+
const leftovers = uncoordinatedContainers.filter((name) => remaining.includes(name));
|
|
295
343
|
if (leftovers.length > 0) throw new Error(`Sandbox container(s) still exist after removal: ${leftovers.join(', ')}`);
|
|
296
|
-
spinner.stop(pc.green(`Removed container(s): ${
|
|
344
|
+
spinner.stop(pc.green(`Removed container(s): ${uncoordinatedContainers.join(', ')}`));
|
|
297
345
|
} else {
|
|
298
346
|
p.log.warn(`No sandbox container found for '${branch}'`);
|
|
299
347
|
}
|
|
@@ -400,7 +448,7 @@ async function rmPurge(
|
|
|
400
448
|
}
|
|
401
449
|
const permits = cleanPermits(inspections);
|
|
402
450
|
|
|
403
|
-
await
|
|
451
|
+
const coordinatedContainers = await removeProjectControlRoots(config, engine);
|
|
404
452
|
|
|
405
453
|
const containers = runEngine(engine, 'docker', [
|
|
406
454
|
'ps',
|
|
@@ -410,17 +458,19 @@ async function rmPurge(
|
|
|
410
458
|
'--format',
|
|
411
459
|
'{{.Names}}'
|
|
412
460
|
]);
|
|
413
|
-
|
|
461
|
+
const uncoordinatedContainers = containers.split('\n').filter((name) => name && !coordinatedContainers.has(name));
|
|
462
|
+
if (uncoordinatedContainers.length > 0) {
|
|
414
463
|
const spinner = p.spinner();
|
|
415
464
|
spinner.start('Stopping project sandbox containers...');
|
|
416
|
-
for (const name of
|
|
465
|
+
for (const name of uncoordinatedContainers) {
|
|
417
466
|
if (!runOkEngine(engine, 'docker', ['stop', name])) throw new Error(`Failed to stop sandbox container: ${name}`);
|
|
418
467
|
if (!runOkEngine(engine, 'docker', ['rm', name])) throw new Error(`Failed to remove sandbox container: ${name}`);
|
|
419
468
|
}
|
|
420
469
|
const remaining = runEngine(engine, 'docker', [
|
|
421
470
|
'ps', '-a', '--filter', `label=${sandboxLabel(config)}`, '--format', '{{.Names}}'
|
|
422
471
|
]);
|
|
423
|
-
|
|
472
|
+
const leftovers = remaining.split('\n').filter((name) => name && !coordinatedContainers.has(name));
|
|
473
|
+
if (leftovers.length > 0) throw new Error(`Project sandbox container(s) still exist after removal: ${leftovers.join(', ')}`);
|
|
424
474
|
spinner.stop(pc.green('Project sandbox containers removed'));
|
|
425
475
|
} else {
|
|
426
476
|
p.log.warn('No project sandbox containers found');
|
|
@@ -17,9 +17,9 @@ const USAGE = `Usage: ai sandbox start [--recreate] <branch | TASK-id | N>
|
|
|
17
17
|
|
|
18
18
|
Start an existing sandbox container that has stopped (for example after the
|
|
19
19
|
Docker daemon was restarted or replaced). The container must already exist:
|
|
20
|
-
if none is found, run 'ai sandbox create <branch>' first.
|
|
21
|
-
already running is checked without modifying healthy runtime
|
|
22
|
-
|
|
20
|
+
if none is found, run 'ai sandbox create <branch>' first. Without --recreate, a
|
|
21
|
+
container that is already running is checked without modifying healthy runtime
|
|
22
|
+
state. --recreate replaces the existing container even when it is healthy.`;
|
|
23
23
|
|
|
24
24
|
export function parseStartArgs(args: string[]): { target: string; recreate: boolean; help: boolean } {
|
|
25
25
|
let recreate = false;
|
|
@@ -83,6 +83,7 @@ export async function start(args: string[]): Promise<void> {
|
|
|
83
83
|
workspace: target.workspace,
|
|
84
84
|
row: found,
|
|
85
85
|
allowRecreate: parsed.recreate,
|
|
86
|
+
forceRecreate: parsed.recreate,
|
|
86
87
|
recreate: async () => {
|
|
87
88
|
const { create } = await import('./create.ts');
|
|
88
89
|
await create([target.requestedRef, '--no-refresh']);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { SandboxControlManifest } from './protocol.ts';
|
|
2
|
+
import { commandForEngine, runEngine, runProbe } from '../shell.ts';
|
|
3
|
+
|
|
4
|
+
export type ContainerObservation =
|
|
5
|
+
| Readonly<{ state: 'found'; id: string; running: boolean; labels: Readonly<Record<string, string>> }>
|
|
6
|
+
| Readonly<{ state: 'absent'; id: string }>
|
|
7
|
+
| Readonly<{ state: 'unknown'; reason: string }>;
|
|
8
|
+
|
|
9
|
+
export type ContainerInspection = Readonly<{
|
|
10
|
+
id?: unknown;
|
|
11
|
+
running?: unknown;
|
|
12
|
+
labels?: unknown;
|
|
13
|
+
}>;
|
|
14
|
+
|
|
15
|
+
function isAuthoritativeNotFound(error: unknown): boolean {
|
|
16
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
17
|
+
return /(?:no such container|container .* not found|cannot find container)/i.test(message);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function classifySandboxContainerInspection(
|
|
21
|
+
expected: SandboxControlManifest['containerIdentity'],
|
|
22
|
+
inspection: ContainerInspection | { notFound: true } | { error: unknown }
|
|
23
|
+
): ContainerObservation {
|
|
24
|
+
if ('notFound' in inspection) return { state: 'absent', id: expected.id };
|
|
25
|
+
if ('error' in inspection) {
|
|
26
|
+
return isAuthoritativeNotFound(inspection.error)
|
|
27
|
+
? { state: 'absent', id: expected.id }
|
|
28
|
+
: { state: 'unknown', reason: `container inspection failed: ${inspection.error instanceof Error ? inspection.error.message : String(inspection.error)}` };
|
|
29
|
+
}
|
|
30
|
+
const response = inspection as ContainerInspection;
|
|
31
|
+
if (typeof response.id !== 'string' || response.id !== expected.id
|
|
32
|
+
|| typeof response.running !== 'boolean' || !response.labels
|
|
33
|
+
|| typeof response.labels !== 'object' || Array.isArray(response.labels)) {
|
|
34
|
+
return { state: 'unknown', reason: 'container inspection response is incomplete' };
|
|
35
|
+
}
|
|
36
|
+
const labels = response.labels as Record<string, unknown>;
|
|
37
|
+
for (const [key, value] of Object.entries(expected.labels)) {
|
|
38
|
+
if (labels[key] !== value) return { state: 'unknown', reason: 'container labels do not match the manifest' };
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
state: 'found',
|
|
42
|
+
id: response.id,
|
|
43
|
+
running: response.running,
|
|
44
|
+
labels: Object.fromEntries(
|
|
45
|
+
Object.entries(labels).flatMap(([key, value]) => typeof value === 'string' ? [[key, value]] : [])
|
|
46
|
+
)
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function inspectSandboxControlContainer(
|
|
51
|
+
manifest: SandboxControlManifest,
|
|
52
|
+
options: { run?: typeof runEngine; probe?: typeof runProbe; timeoutMs?: number } = {}
|
|
53
|
+
): Promise<ContainerObservation> {
|
|
54
|
+
try {
|
|
55
|
+
const args = ['inspect', '--format', '{{json .}}', manifest.containerIdentity.id];
|
|
56
|
+
let raw: string;
|
|
57
|
+
if (options.run) {
|
|
58
|
+
raw = options.run(manifest.engine, 'docker', args, options.timeoutMs === undefined ? {} : { timeout: options.timeoutMs });
|
|
59
|
+
} else {
|
|
60
|
+
const command = commandForEngine(manifest.engine, 'docker', args);
|
|
61
|
+
const result = (options.probe ?? runProbe)(
|
|
62
|
+
command.cmd,
|
|
63
|
+
command.args,
|
|
64
|
+
options.timeoutMs === undefined ? {} : { timeout: options.timeoutMs }
|
|
65
|
+
);
|
|
66
|
+
if (result.status !== 0) {
|
|
67
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr : result.stderr?.toString('utf8') ?? '';
|
|
68
|
+
return classifySandboxContainerInspection(manifest.containerIdentity, {
|
|
69
|
+
error: new Error(stderr || `container inspection exited with ${result.status ?? 'unknown'}`)
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
raw = typeof result.stdout === 'string' ? result.stdout : result.stdout?.toString('utf8') ?? '';
|
|
73
|
+
}
|
|
74
|
+
const parsed = JSON.parse(raw) as {
|
|
75
|
+
Id?: unknown;
|
|
76
|
+
State?: { Running?: unknown };
|
|
77
|
+
Config?: { Labels?: unknown };
|
|
78
|
+
} | Array<{
|
|
79
|
+
Id?: unknown;
|
|
80
|
+
State?: { Running?: unknown };
|
|
81
|
+
Config?: { Labels?: unknown };
|
|
82
|
+
}>;
|
|
83
|
+
const value = Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
|
|
84
|
+
if (!value || Array.isArray(value)) return { state: 'unknown', reason: 'container inspection response is incomplete' };
|
|
85
|
+
return classifySandboxContainerInspection(manifest.containerIdentity, {
|
|
86
|
+
id: value.Id,
|
|
87
|
+
running: value.State?.Running,
|
|
88
|
+
labels: value.Config?.Labels
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return classifySandboxContainerInspection(manifest.containerIdentity, { error });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -15,9 +15,9 @@ export type SandboxControlExecutionResult = {
|
|
|
15
15
|
|
|
16
16
|
export type PreparedSandboxControlExecution = {
|
|
17
17
|
execution: SandboxControlExecution;
|
|
18
|
-
start(): void;
|
|
18
|
+
start(canWrite?: () => boolean): void;
|
|
19
19
|
completion: Promise<SandboxControlExecutionResult>;
|
|
20
|
-
terminate(): boolean;
|
|
20
|
+
terminate(updateState?: boolean): boolean;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
function safeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
@@ -30,11 +30,11 @@ export function nodeEntryArgs(entry: string, args: string[]): string[] {
|
|
|
30
30
|
: [entry, ...args];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
async function waitForStartTime(pid: number, timeoutMs = 2_000): Promise<
|
|
33
|
+
async function waitForStartTime(pid: number, timeoutMs = 2_000): Promise<number> {
|
|
34
34
|
const deadline = Date.now() + timeoutMs;
|
|
35
35
|
while (Date.now() < deadline) {
|
|
36
36
|
const startTime = getProcessStartTime(pid);
|
|
37
|
-
if (startTime) return startTime;
|
|
37
|
+
if (startTime !== null) return startTime;
|
|
38
38
|
await new Promise<void>((resolve) => setTimeout(resolve, 10));
|
|
39
39
|
}
|
|
40
40
|
throw new Error('SANDBOX_CONTROL_EXECUTOR_IDENTITY_UNAVAILABLE');
|
|
@@ -73,7 +73,7 @@ export async function prepareSandboxControlExecution(params: {
|
|
|
73
73
|
});
|
|
74
74
|
const startTime = await waitForStartTime(child.pid);
|
|
75
75
|
const execution: SandboxControlExecution = {
|
|
76
|
-
version:
|
|
76
|
+
version: 2,
|
|
77
77
|
generation: params.manifest.generation,
|
|
78
78
|
requestId: params.request.id,
|
|
79
79
|
nonce,
|
|
@@ -88,16 +88,20 @@ export async function prepareSandboxControlExecution(params: {
|
|
|
88
88
|
atomicWriteJson(executionPath(params.manifest, params.request.id), execution);
|
|
89
89
|
return {
|
|
90
90
|
execution,
|
|
91
|
-
start() {
|
|
91
|
+
start(canWrite = () => true) {
|
|
92
92
|
if (!child.connected) throw new Error('SANDBOX_CONTROL_EXECUTOR_GATE_CLOSED');
|
|
93
|
+
if (!canWrite()) throw new Error('SANDBOX_CONTROL_OWNER_LOST');
|
|
93
94
|
atomicWriteJson(executionPath(params.manifest, params.request.id), { ...execution, phase: 'running', updatedAt: Date.now() });
|
|
95
|
+
if (!canWrite()) throw new Error('SANDBOX_CONTROL_OWNER_LOST');
|
|
94
96
|
child.send({ version: 1, nonce });
|
|
95
97
|
},
|
|
96
98
|
completion,
|
|
97
|
-
terminate() {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
terminate(updateState = true) {
|
|
100
|
+
if (updateState) {
|
|
101
|
+
atomicWriteJson(executionPath(params.manifest, params.request.id), {
|
|
102
|
+
...execution, phase: 'terminating', updatedAt: Date.now()
|
|
103
|
+
});
|
|
104
|
+
}
|
|
101
105
|
return terminateSandboxControlExecution(execution);
|
|
102
106
|
}
|
|
103
107
|
};
|