@fitlab-ai/agent-infra 0.11.11 → 0.11.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/internal-cli.js +2 -2
- package/dist/lib/agent-clients/adapter.js +7 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
- package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
- package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
- package/dist/lib/agent-clients/adapters/codex.js +40 -1
- package/dist/lib/agent-clients/adapters/index.js +2 -10
- package/dist/lib/agent-clients/tokens.js +3 -2
- package/dist/lib/internal/agent-client.js +132 -72
- package/dist/lib/internal/cli-route-inventory.js +2 -2
- package/dist/lib/internal/sandbox-control.js +1 -81
- package/dist/lib/internal/task-finalization.js +26 -19
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -1
- package/dist/lib/internal/task-validate.js +1 -1
- package/dist/lib/platform/github-client.js +33 -3
- package/dist/lib/platform/issues.js +18 -6
- package/dist/lib/runtime/agent-runtime.js +6 -29
- package/dist/lib/sandbox/commands/control-status.js +1 -0
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/ls.js +4 -5
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/control/agent-operation.js +34 -0
- package/dist/lib/sandbox/control/client.js +14 -201
- package/dist/lib/sandbox/control/executor.js +12 -125
- package/dist/lib/sandbox/control/lifecycle.js +66 -4
- package/dist/lib/sandbox/control/protocol.js +15 -54
- package/dist/lib/sandbox/control/server.js +216 -281
- package/dist/lib/sandbox/control/task-view.js +0 -13
- package/dist/lib/sandbox/mounts.js +9 -1
- package/dist/lib/sandbox/recovery.js +31 -10
- package/dist/lib/sandbox/removal.js +28 -5
- package/dist/lib/sandbox/workspace-identity.js +18 -6
- package/dist/lib/sandbox/workspace-view.js +56 -19
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/control-authority.js +56 -264
- package/dist/lib/task/control-recovery.js +2 -3
- package/dist/lib/task/create.js +2 -2
- package/dist/lib/task/delegation-receipts.js +74 -161
- package/dist/lib/task/events.js +88 -44
- package/dist/lib/task/finalization.js +35 -6
- package/dist/lib/task/lifecycle.js +274 -115
- package/dist/lib/task/orchestration.js +201 -194
- package/dist/lib/task/runtime-paths.js +10 -0
- package/dist/lib/task/short-id.js +37 -27
- package/dist/lib/task/verification-engine.js +32 -26
- package/dist/lib/task/workspace-snapshot.js +1 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +39 -0
- package/lib/agent-clients/adapters/claude-code.ts +5 -1
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
- package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
- package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
- package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
- package/lib/agent-clients/adapters/codex.ts +44 -1
- package/lib/agent-clients/adapters/index.ts +7 -20
- package/lib/agent-clients/tokens.ts +4 -3
- package/lib/internal/agent-client.ts +142 -73
- package/lib/internal/cli-route-inventory.ts +2 -2
- package/lib/internal/sandbox-control.ts +0 -97
- package/lib/internal/task-finalization.ts +30 -18
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -1
- package/lib/internal/task-validate.ts +1 -1
- package/lib/platform/github-client.ts +34 -3
- package/lib/platform/issues.ts +18 -6
- package/lib/runtime/agent-runtime.ts +6 -41
- package/lib/sandbox/commands/control-status.ts +1 -0
- package/lib/sandbox/commands/create.ts +7 -1
- package/lib/sandbox/commands/ls.ts +4 -5
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/control/agent-operation.ts +49 -0
- package/lib/sandbox/control/client.ts +20 -254
- package/lib/sandbox/control/executor.ts +13 -136
- package/lib/sandbox/control/lifecycle.ts +67 -4
- package/lib/sandbox/control/protocol.ts +22 -66
- package/lib/sandbox/control/server.ts +21 -95
- package/lib/sandbox/control/task-view.ts +0 -19
- package/lib/sandbox/mounts.ts +10 -1
- package/lib/sandbox/recovery.ts +38 -17
- package/lib/sandbox/removal.ts +33 -7
- package/lib/sandbox/workspace-identity.ts +16 -6
- package/lib/sandbox/workspace-view.ts +50 -18
- package/lib/task/commands/ls.ts +3 -3
- package/lib/task/control-authority.ts +66 -378
- package/lib/task/control-recovery.ts +3 -4
- package/lib/task/create.ts +3 -3
- package/lib/task/delegation-receipts.ts +87 -231
- package/lib/task/events.ts +100 -42
- package/lib/task/finalization.ts +37 -7
- package/lib/task/lifecycle.ts +257 -109
- package/lib/task/orchestration.ts +260 -237
- package/lib/task/runtime-paths.ts +12 -0
- package/lib/task/short-id.ts +33 -40
- package/lib/task/verification-engine.ts +30 -25
- package/lib/task/workspace-snapshot.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
- package/templates/.agents/rules/README.en.md +2 -1
- package/templates/.agents/rules/README.zh-CN.md +2 -1
- package/templates/.agents/rules/next-step-output.en.md +6 -19
- package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
- package/templates/.agents/rules/strategy-pattern.en.md +25 -0
- package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
- package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
- package/dist/lib/internal/codex-lifecycle.js +0 -385
- package/dist/lib/task/codex-orchestration.js +0 -206
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
- package/lib/internal/codex-lifecycle.ts +0 -390
- package/lib/task/codex-orchestration.ts +0 -276
|
@@ -23,7 +23,7 @@ import { inspectSandboxControlContainer } from '../control/container-identity.js
|
|
|
23
23
|
import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.js';
|
|
24
24
|
import { sandboxCoreBindMounts } from '../mounts.js';
|
|
25
25
|
import { materializeSandboxGitMetadata } from '../git-metadata.js';
|
|
26
|
-
import { assertSandboxTaskSource, finalizeSandboxControlManifest, materializeSandboxControl, materializeSandboxWorkspaceView, prepareSandboxWorkspaceMountTargets, sandboxControlPaths, } from '../workspace-view.js';
|
|
26
|
+
import { assertSandboxTaskSource, finalizeSandboxControlManifest, materializeSandboxControl, materializeSandboxWorkspaceView, prepareSandboxWorkspaceMountTargets, prepareSandboxCompletedTaskMountSource, sandboxControlPaths, } from '../workspace-view.js';
|
|
27
27
|
import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.js';
|
|
28
28
|
import { validateSelinuxDisableEnv } from '../engines/selinux.js';
|
|
29
29
|
import { captureSandboxAuthority, commandForSandboxAuthority, verifySandboxAuthority } from '../engines/authority.js';
|
|
@@ -741,6 +741,7 @@ export async function create(args, { runProjectInitCommand = true, workspace: wo
|
|
|
741
741
|
const container = containerName(effectiveConfig, branch);
|
|
742
742
|
const controlPaths = sandboxControlPaths({
|
|
743
743
|
base: effectiveConfig.controlBase,
|
|
744
|
+
repoRoot: effectiveConfig.repoRoot,
|
|
744
745
|
project: effectiveConfig.project,
|
|
745
746
|
container,
|
|
746
747
|
identity: target.workspace
|
|
@@ -1037,7 +1038,10 @@ export async function create(args, { runProjectInitCommand = true, workspace: wo
|
|
|
1037
1038
|
const taskSource = target.workspace.mode === 'task-bound'
|
|
1038
1039
|
? assertSandboxTaskSource(effectiveConfig.repoRoot, target.workspace.taskId)
|
|
1039
1040
|
: null;
|
|
1040
|
-
|
|
1041
|
+
const completedTaskSource = target.workspace.mode === 'task-bound'
|
|
1042
|
+
? prepareSandboxCompletedTaskMountSource(effectiveConfig.repoRoot, target.workspace.taskId)
|
|
1043
|
+
: null;
|
|
1044
|
+
prepareSandboxWorkspaceMountTargets(worktree, target.workspace.mode === 'task-bound' ? target.workspace.taskId : undefined);
|
|
1041
1045
|
if (previousCutoverSnapshot) {
|
|
1042
1046
|
assertSandboxControlCutoverSnapshot(previousCutoverSnapshot);
|
|
1043
1047
|
if (previousManifest) {
|
|
@@ -1080,6 +1084,7 @@ export async function create(args, { runProjectInitCommand = true, workspace: wo
|
|
|
1080
1084
|
...(target.workspace.mode === 'task-bound'
|
|
1081
1085
|
? {
|
|
1082
1086
|
taskSources: [taskSource],
|
|
1087
|
+
completedTaskSource: completedTaskSource,
|
|
1083
1088
|
taskId: target.workspace.taskId
|
|
1084
1089
|
}
|
|
1085
1090
|
: {})
|
|
@@ -11,11 +11,10 @@ export { containerListFormat, parseLabels } from './list-running.js';
|
|
|
11
11
|
const USAGE = `Usage: ai sandbox ls
|
|
12
12
|
|
|
13
13
|
Lists all containers for the current project. The '#' column is a
|
|
14
|
-
display-only row number; the 'SHORT' column shows the
|
|
15
|
-
|
|
16
|
-
.
|
|
17
|
-
|
|
18
|
-
A SHORT value of '-' means this container has no active task short id and may
|
|
14
|
+
display-only row number; the 'SHORT' column shows the task short id bound to
|
|
15
|
+
each task-bound container, or '-' if no short id is registered. Pass the
|
|
16
|
+
SHORT value to "ai sandbox exec" (e.g. 'ai sandbox exec 11').
|
|
17
|
+
A SHORT value of '-' means this container has no task short id and may
|
|
19
18
|
be eligible for "ai sandbox rm --unbound" cleanup. Use a full TASK-id to
|
|
20
19
|
remove a specific task-bound sandbox.
|
|
21
20
|
|
|
@@ -7,7 +7,7 @@ import { sandboxBranchLabel, sandboxLabel, sandboxTaskIdLabel, sandboxWorkspaceM
|
|
|
7
7
|
import { fetchSandboxRows } from './list-running.js';
|
|
8
8
|
import { rmOne, rmPurge, rmUnbound } from '../removal.js';
|
|
9
9
|
const USAGE = `Usage:
|
|
10
|
-
ai sandbox rm <branch | TASK-id | short id> Remove one sandbox; use a full TASK-id for a task-bound sandbox and a branch for branch-only sandboxes
|
|
10
|
+
ai sandbox rm <branch | TASK-id | short id> Remove one sandbox; use a retained short id or full TASK-id for a task-bound sandbox and a branch for branch-only sandboxes
|
|
11
11
|
ai sandbox rm --unbound [--dry-run] [--yes] Remove sandboxes not bound to an active task short id
|
|
12
12
|
ai sandbox rm --purge Tear down ALL sandboxes for the project (containers, worktrees, image, VM)`;
|
|
13
13
|
function resolveMissingTaskSandboxBranch(taskId, config) {
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { SANDBOX_CONTROL_ADMISSION_WINDOW_MS, SANDBOX_CONTROL_MAX_BYTES, SANDBOX_CONTROL_STATUS_STALE_MS, isSandboxControlFamily } from './protocol.js';
|
|
5
|
-
import {
|
|
5
|
+
import { isAgentClientId } from '../../agent-clients/types.js';
|
|
6
6
|
import { readSandboxControlPayload, readSandboxControlStatus } from './state.js';
|
|
7
7
|
import { accessSandboxTaskView, taskViewFromStatus } from './task-view.js';
|
|
8
8
|
import { readSandboxControlIdentitySentinel } from './identity-sentinel.js';
|
|
@@ -11,7 +11,6 @@ import { hasTaskBoundMarker, isSandbox } from '../environment.js';
|
|
|
11
11
|
import { loadConfig } from '../config.js';
|
|
12
12
|
import { recoverSandboxControlFromHost } from './host-recovery.js';
|
|
13
13
|
const SANDBOX_CONTROL_RESPONSE_SETTLE_MS = 250;
|
|
14
|
-
const SANDBOX_TASK_FINALIZATION_RECOVERY_BUDGET_MS = 5 * 60_000;
|
|
15
14
|
export class SandboxControlClientError extends Error {
|
|
16
15
|
detail;
|
|
17
16
|
accepted;
|
|
@@ -84,8 +83,6 @@ function preflight(statusDir, generation, taskViewEffect = null, now = Date.now(
|
|
|
84
83
|
function taskViewEffectForRequest(request) {
|
|
85
84
|
if (request.family === 'task-lifecycle')
|
|
86
85
|
return 'progress';
|
|
87
|
-
if (request.family === 'task-finalization')
|
|
88
|
-
return 'recovery';
|
|
89
86
|
return null;
|
|
90
87
|
}
|
|
91
88
|
function parseResponse(raw, id, accepted = false) {
|
|
@@ -179,9 +176,6 @@ function exchangeSandboxControl(request, params) {
|
|
|
179
176
|
|| (requestTask.kind === 'task' && requestTask.taskId !== identity.taskId))) {
|
|
180
177
|
clientError('SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH', 'request task does not match the sandbox identity', false);
|
|
181
178
|
}
|
|
182
|
-
if (identity.mode === 'branch-only' && request.family === 'task-finalization') {
|
|
183
|
-
clientError('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot finalize tasks', false);
|
|
184
|
-
}
|
|
185
179
|
}
|
|
186
180
|
const encoded = `${JSON.stringify(request)}\n`;
|
|
187
181
|
if (Buffer.byteLength(encoded, 'utf8') > SANDBOX_CONTROL_MAX_BYTES) {
|
|
@@ -368,173 +362,25 @@ function authority(params) {
|
|
|
368
362
|
export function requestSandboxControl(params) {
|
|
369
363
|
if (!isSandboxControlFamily(params.family))
|
|
370
364
|
clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' is not allowed`, false);
|
|
371
|
-
if (params.family === 'task-create' || params.family === '
|
|
365
|
+
if (params.family === 'task-create' || params.family === 'agent-client') {
|
|
372
366
|
clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' requires a typed request`, false);
|
|
373
367
|
}
|
|
368
|
+
if (params.args[1] === 'complete') {
|
|
369
|
+
clientError('SANDBOX_CONTROL_COMMAND_DENIED', 'complete must use task-finalization', false);
|
|
370
|
+
}
|
|
374
371
|
const auth = authority(params);
|
|
375
372
|
const issuedAt = Date.now();
|
|
376
373
|
const request = {
|
|
377
374
|
version: 4, id: randomUUID(), ...auth, issuedAt,
|
|
378
375
|
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
379
|
-
family: params.family, args: params.args
|
|
380
|
-
controllerProcess: null,
|
|
381
|
-
controllerProof: null
|
|
376
|
+
family: params.family, args: params.args
|
|
382
377
|
};
|
|
383
378
|
return exchangeSandboxControl(request, params);
|
|
384
379
|
}
|
|
385
|
-
export function
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
clientError('SANDBOX_CONTROL_REQUEST_INVALID', 'task-finalization agent is invalid', false);
|
|
389
|
-
const auth = authority(params);
|
|
390
|
-
const issuedAt = Date.now();
|
|
391
|
-
const id = randomUUID();
|
|
392
|
-
const request = {
|
|
393
|
-
version: 4,
|
|
394
|
-
id,
|
|
395
|
-
...auth,
|
|
396
|
-
issuedAt,
|
|
397
|
-
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
398
|
-
family: 'task-finalization',
|
|
399
|
-
operation: 'complete',
|
|
400
|
-
agent,
|
|
401
|
-
args: [],
|
|
402
|
-
controllerProcess: null,
|
|
403
|
-
controllerProof: null
|
|
404
|
-
};
|
|
405
|
-
let response;
|
|
406
|
-
try {
|
|
407
|
-
response = exchangeSandboxControl(request, params);
|
|
408
|
-
}
|
|
409
|
-
catch (error) {
|
|
410
|
-
if (!(error instanceof SandboxControlClientError)
|
|
411
|
-
|| error.detail.code !== 'SANDBOX_CONTROL_RESULT_UNKNOWN'
|
|
412
|
-
|| !error.accepted || !error.requestId)
|
|
413
|
-
throw error;
|
|
414
|
-
return recoverAcceptedTaskFinalization(error.requestId, params);
|
|
415
|
-
}
|
|
416
|
-
if (response.phase === 'rejected' && response.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN') {
|
|
417
|
-
return recoverAcceptedTaskFinalization(response.id, params);
|
|
418
|
-
}
|
|
419
|
-
return response;
|
|
420
|
-
}
|
|
421
|
-
export function recoverAcceptedTaskFinalization(requestId, params, recover = recoverSandboxControl) {
|
|
422
|
-
const deadline = Date.now() + (params.recoveryBudgetMs ?? SANDBOX_TASK_FINALIZATION_RECOVERY_BUDGET_MS);
|
|
423
|
-
while (Date.now() < deadline) {
|
|
424
|
-
const timeoutMs = Math.min(30_000, deadline - Date.now());
|
|
425
|
-
if (timeoutMs <= 0)
|
|
426
|
-
break;
|
|
427
|
-
try {
|
|
428
|
-
const response = recover(requestId, { timeoutMs });
|
|
429
|
-
if (response.phase === 'rejected' && response.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN')
|
|
430
|
-
continue;
|
|
431
|
-
return response;
|
|
432
|
-
}
|
|
433
|
-
catch (error) {
|
|
434
|
-
if (error instanceof SandboxControlClientError
|
|
435
|
-
&& error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN'
|
|
436
|
-
&& error.requestId === requestId)
|
|
437
|
-
continue;
|
|
438
|
-
throw error;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'accepted finalization request remained unknown after the automatic recovery budget; inspect the receipt and resume by request id', false, true, requestId);
|
|
442
|
-
}
|
|
443
|
-
function exactKeys(value, keys) {
|
|
444
|
-
return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
445
|
-
}
|
|
446
|
-
function validProcess(value) {
|
|
447
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
448
|
-
return false;
|
|
449
|
-
const identity = value;
|
|
450
|
-
return exactKeys(identity, ['pid', 'startTime'])
|
|
451
|
-
&& Number.isSafeInteger(identity.pid) && identity.pid > 0
|
|
452
|
-
&& Number.isSafeInteger(identity.startTime) && identity.startTime >= 0;
|
|
453
|
-
}
|
|
454
|
-
function validBuild(value) {
|
|
455
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
456
|
-
return false;
|
|
457
|
-
const build = value;
|
|
458
|
-
return exactKeys(build, ['internalExecutableBuildHash', 'lifecycleContractHash', 'packageVersion', 'protocolVersion'])
|
|
459
|
-
&& build.protocolVersion === 3
|
|
460
|
-
&& typeof build.packageVersion === 'string' && build.packageVersion.length > 0
|
|
461
|
-
&& typeof build.internalExecutableBuildHash === 'string' && /^[a-f0-9]{64}$/u.test(build.internalExecutableBuildHash)
|
|
462
|
-
&& typeof build.lifecycleContractHash === 'string' && /^[a-f0-9]{64}$/u.test(build.lifecycleContractHash);
|
|
463
|
-
}
|
|
464
|
-
export function parseCodexControllerResult(response) {
|
|
465
|
-
if (response.phase !== 'completed' || response.error !== null || response.stderr !== '') {
|
|
466
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller result outer response is invalid', false, true);
|
|
467
|
-
}
|
|
468
|
-
let value;
|
|
469
|
-
try {
|
|
470
|
-
if (!response.stdout.endsWith('\n') || response.stdout.slice(0, -1).includes('\n'))
|
|
471
|
-
throw new Error('not canonical');
|
|
472
|
-
value = JSON.parse(response.stdout);
|
|
473
|
-
}
|
|
474
|
-
catch {
|
|
475
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller result payload is invalid', false, true);
|
|
476
|
-
}
|
|
477
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
478
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller result payload is invalid', false, true);
|
|
479
|
-
}
|
|
480
|
-
const result = value;
|
|
481
|
-
const resultKeys = result.status === 'verified'
|
|
482
|
-
? ['binding', 'changed', 'error', 'lease', 'status', 'version']
|
|
483
|
-
: ['changed', 'error', 'lease', 'status', 'version'];
|
|
484
|
-
if (!exactKeys(result, resultKeys)
|
|
485
|
-
|| result.version !== 1 || !['opened', 'closed', 'failed', 'verified'].includes(result.status)) {
|
|
486
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller result schema is invalid', false, true);
|
|
487
|
-
}
|
|
488
|
-
if (result.status === 'failed') {
|
|
489
|
-
const error = result.error;
|
|
490
|
-
if (response.exitCode !== 1 || result.changed !== false || result.lease !== null
|
|
491
|
-
|| !error || !exactKeys(error, ['code', 'message', 'retryable'])
|
|
492
|
-
|| typeof error.code !== 'string' || !/^[A-Z][A-Z0-9_]+$/u.test(error.code)
|
|
493
|
-
|| typeof error.message !== 'string' || error.retryable !== false) {
|
|
494
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller failure result is invalid', false, true);
|
|
495
|
-
}
|
|
496
|
-
clientError(error.code, error.message, false, true);
|
|
380
|
+
export function requestSandboxAgentOperation(params) {
|
|
381
|
+
if (!isAgentClientId(params.agentClient)) {
|
|
382
|
+
clientError('SANDBOX_CONTROL_REQUEST_INVALID', 'agent-client id is invalid', false);
|
|
497
383
|
}
|
|
498
|
-
if (response.exitCode !== 0 || result.error !== null) {
|
|
499
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller success result is invalid', false, true);
|
|
500
|
-
}
|
|
501
|
-
if (result.status === 'closed') {
|
|
502
|
-
if (typeof result.changed !== 'boolean' || result.lease !== null) {
|
|
503
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller close result is invalid', false, true);
|
|
504
|
-
}
|
|
505
|
-
return result;
|
|
506
|
-
}
|
|
507
|
-
if (result.status === 'verified') {
|
|
508
|
-
const binding = result.binding;
|
|
509
|
-
if (result.changed !== false || result.lease !== null || !binding
|
|
510
|
-
|| !exactKeys(binding, ['controllerInstanceDigest', 'controlGeneration', 'taskId'])
|
|
511
|
-
|| typeof binding.taskId !== 'string' || binding.taskId.length === 0
|
|
512
|
-
|| typeof binding.controlGeneration !== 'string' || binding.controlGeneration.length === 0
|
|
513
|
-
|| typeof binding.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(binding.controllerInstanceDigest)) {
|
|
514
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller verify result is invalid', false, true);
|
|
515
|
-
}
|
|
516
|
-
return result;
|
|
517
|
-
}
|
|
518
|
-
const lease = result.lease;
|
|
519
|
-
if (result.changed !== true || !lease
|
|
520
|
-
|| !exactKeys(lease, [
|
|
521
|
-
'buildIdentity', 'controlGeneration', 'controllerInstanceDigest', 'controllerProcess',
|
|
522
|
-
'expiresAt', 'issuedAt', 'leaseId', 'leaseSecret', 'taskId', 'version'
|
|
523
|
-
])
|
|
524
|
-
|| lease.version !== 1
|
|
525
|
-
|| typeof lease.leaseId !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.leaseId)
|
|
526
|
-
|| typeof lease.leaseSecret !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.leaseSecret)
|
|
527
|
-
|| typeof lease.taskId !== 'string' || lease.taskId.length === 0
|
|
528
|
-
|| typeof lease.controlGeneration !== 'string' || lease.controlGeneration.length === 0
|
|
529
|
-
|| typeof lease.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.controllerInstanceDigest)
|
|
530
|
-
|| !validProcess(lease.controllerProcess) || !validBuild(lease.buildIdentity)
|
|
531
|
-
|| !Number.isSafeInteger(lease.issuedAt) || !Number.isSafeInteger(lease.expiresAt)
|
|
532
|
-
|| lease.expiresAt <= lease.issuedAt) {
|
|
533
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller open result is invalid', false, true);
|
|
534
|
-
}
|
|
535
|
-
return result;
|
|
536
|
-
}
|
|
537
|
-
function requestCodexController(params) {
|
|
538
384
|
const auth = authority(params);
|
|
539
385
|
const issuedAt = Date.now();
|
|
540
386
|
const request = {
|
|
@@ -543,43 +389,12 @@ function requestCodexController(params) {
|
|
|
543
389
|
...auth,
|
|
544
390
|
issuedAt,
|
|
545
391
|
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
546
|
-
family: '
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
controllerProof: params.controllerProof
|
|
392
|
+
family: 'agent-client',
|
|
393
|
+
agentClient: params.agentClient,
|
|
394
|
+
operation: params.operation,
|
|
395
|
+
payload: params.payload
|
|
551
396
|
};
|
|
552
|
-
|
|
553
|
-
if (result.status === 'opened'
|
|
554
|
-
&& (result.lease.controlGeneration !== auth.generation
|
|
555
|
-
|| result.lease.controllerProcess.pid !== params.controllerProcess.pid
|
|
556
|
-
|| result.lease.controllerProcess.startTime !== params.controllerProcess.startTime)) {
|
|
557
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller result does not match the request', false, true);
|
|
558
|
-
}
|
|
559
|
-
return result;
|
|
560
|
-
}
|
|
561
|
-
export function requestCodexControllerOpen(params) {
|
|
562
|
-
const result = requestCodexController({ ...params, command: 'open', controllerProof: null });
|
|
563
|
-
if (result.status !== 'opened')
|
|
564
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller open returned the wrong result', false, true);
|
|
565
|
-
return result;
|
|
566
|
-
}
|
|
567
|
-
export function requestCodexControllerClose(params) {
|
|
568
|
-
const result = requestCodexController({ ...params, command: 'close' });
|
|
569
|
-
if (result.status !== 'closed')
|
|
570
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller close returned the wrong result', false, true);
|
|
571
|
-
return result;
|
|
572
|
-
}
|
|
573
|
-
export function requestCodexControllerVerify(params) {
|
|
574
|
-
const result = requestCodexController({
|
|
575
|
-
...params,
|
|
576
|
-
controllerProcess: params.controllerProof.controllerProcess,
|
|
577
|
-
command: 'verify'
|
|
578
|
-
});
|
|
579
|
-
if (result.status !== 'verified') {
|
|
580
|
-
clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller verify returned the wrong result', false, true);
|
|
581
|
-
}
|
|
582
|
-
return result;
|
|
397
|
+
return exchangeSandboxControl(request, params);
|
|
583
398
|
}
|
|
584
399
|
export function requestSandboxTaskCreate(params) {
|
|
585
400
|
const auth = authority(params);
|
|
@@ -588,8 +403,6 @@ export function requestSandboxTaskCreate(params) {
|
|
|
588
403
|
version: 4, id: randomUUID(), ...auth, issuedAt,
|
|
589
404
|
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
590
405
|
family: 'task-create', candidate: params.candidate,
|
|
591
|
-
controllerProcess: null,
|
|
592
|
-
controllerProof: null
|
|
593
406
|
};
|
|
594
407
|
return exchangeSandboxControl(request, { ...params, timeoutMs: params.timeoutMs ?? 120_000 });
|
|
595
408
|
}
|
|
@@ -4,16 +4,13 @@ import path from 'node:path';
|
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { getProcessStartTime } from '../../server/process-state.js';
|
|
6
6
|
import { createTask, projectTaskCreateResult, taskCreateExitCode, taskCreateFailure } from '../../task/create-service.js';
|
|
7
|
-
import { applyTaskFinalization } from '../../task/finalization.js';
|
|
8
|
-
import { serializeTaskFinalizationEnvelope } from '../../task/finalization-envelope.js';
|
|
9
7
|
import { bindSandboxControlTask, validateSandboxControlRequest } from './protocol.js';
|
|
10
8
|
import { atomicWriteJson, executionPath, readActiveLease, terminateSandboxControlExecution } from './state.js';
|
|
11
9
|
import { requestAuditFields, resultAuditFields, appendDiagnosticAudit, createSandboxControlAuditContext, appendCriticalAudit, writeSandboxControlTransition } from './audit.js';
|
|
12
10
|
import { validateSandboxControlIdentity } from './identity-sentinel.js';
|
|
13
11
|
import { createSandboxExecutorExecutionContext, dispatchTaskControlOperation, parseTaskControlOperation } from '../../task/control-authority.js';
|
|
14
12
|
import { assertSandboxControlBrokerOwner, readSandboxControlManifest } from './lifecycle.js';
|
|
15
|
-
import {
|
|
16
|
-
import { closeCodexControllerRegistration, CodexControllerRegistrationError, openCodexControllerRegistration, resolveCodexControllerBinding } from './controller-registration.js';
|
|
13
|
+
import { executeAgentClientOperation, validateAgentClientOperation } from './agent-operation.js';
|
|
17
14
|
function sameBrokerOwner(left, right) {
|
|
18
15
|
return left.version === right.version
|
|
19
16
|
&& left.pid === right.pid
|
|
@@ -169,23 +166,6 @@ function waitForGate(nonce, timeoutMs = 2_000) {
|
|
|
169
166
|
});
|
|
170
167
|
});
|
|
171
168
|
}
|
|
172
|
-
function controllerFailure(error) {
|
|
173
|
-
const code = error instanceof CodexControllerRegistrationError
|
|
174
|
-
? error.code
|
|
175
|
-
: /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
|
|
176
|
-
?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
|
|
177
|
-
const message = error instanceof CodexControllerRegistrationError
|
|
178
|
-
? error.message
|
|
179
|
-
: `${code}: controller operation failed; inspect the sandbox controller and rebuild the sandbox if needed`;
|
|
180
|
-
const payload = {
|
|
181
|
-
version: 1,
|
|
182
|
-
status: 'failed',
|
|
183
|
-
changed: false,
|
|
184
|
-
lease: null,
|
|
185
|
-
error: { code, message, retryable: false }
|
|
186
|
-
};
|
|
187
|
-
return { exitCode: 1, stdout: `${JSON.stringify(payload)}\n`, stderr: '' };
|
|
188
|
-
}
|
|
189
169
|
function lifecycleFailure(code, message) {
|
|
190
170
|
return {
|
|
191
171
|
exitCode: 1,
|
|
@@ -210,21 +190,6 @@ function taskCreateExecutionFailure(requestId, code, message) {
|
|
|
210
190
|
recovery: 'inspect-domain-state'
|
|
211
191
|
}));
|
|
212
192
|
}
|
|
213
|
-
function finalizationResult(result) {
|
|
214
|
-
const payload = {
|
|
215
|
-
version: 1,
|
|
216
|
-
status: result.status,
|
|
217
|
-
changed: result.changed,
|
|
218
|
-
accepted: true,
|
|
219
|
-
result,
|
|
220
|
-
error: result.error
|
|
221
|
-
};
|
|
222
|
-
return {
|
|
223
|
-
exitCode: result.status === 'completed' ? 0 : result.status === 'blocked' ? 2 : 1,
|
|
224
|
-
stdout: `${JSON.stringify(payload)}\n`,
|
|
225
|
-
stderr: ''
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
193
|
async function executeRequestInner(manifest, manifestPath, request, options = {}) {
|
|
229
194
|
if (request.family === 'task-create') {
|
|
230
195
|
try {
|
|
@@ -241,75 +206,9 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
241
206
|
return taskCreateExecutionFailure(request.id, code, detail);
|
|
242
207
|
}
|
|
243
208
|
}
|
|
244
|
-
if (request.family === '
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const binding = (options.resolveControllerBinding ?? resolveCodexControllerBinding)({
|
|
248
|
-
manifest,
|
|
249
|
-
manifestPath,
|
|
250
|
-
proof: request.controllerProof,
|
|
251
|
-
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
252
|
-
});
|
|
253
|
-
return {
|
|
254
|
-
exitCode: 0,
|
|
255
|
-
stdout: `${JSON.stringify({
|
|
256
|
-
version: 1,
|
|
257
|
-
status: 'verified',
|
|
258
|
-
changed: false,
|
|
259
|
-
lease: null,
|
|
260
|
-
binding: {
|
|
261
|
-
taskId: manifest.taskId,
|
|
262
|
-
controlGeneration: binding.controlGeneration,
|
|
263
|
-
controllerInstanceDigest: binding.instanceDigest
|
|
264
|
-
},
|
|
265
|
-
error: null
|
|
266
|
-
})}\n`,
|
|
267
|
-
stderr: ''
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
const result = request.command === 'open'
|
|
271
|
-
? openCodexControllerRegistration({
|
|
272
|
-
manifest,
|
|
273
|
-
manifestPath,
|
|
274
|
-
controllerProcess: request.controllerProcess,
|
|
275
|
-
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
276
|
-
})
|
|
277
|
-
: closeCodexControllerRegistration({
|
|
278
|
-
manifest,
|
|
279
|
-
manifestPath,
|
|
280
|
-
proof: request.controllerProof
|
|
281
|
-
});
|
|
282
|
-
return { exitCode: 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
const errorCode = error instanceof CodexControllerRegistrationError
|
|
286
|
-
? error.code
|
|
287
|
-
: /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
|
|
288
|
-
?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
|
|
289
|
-
appendExecutorAudit(manifest, 'controller-operation-failed', {
|
|
290
|
-
...requestAuditFields(manifest, request),
|
|
291
|
-
controllerCommand: request.command,
|
|
292
|
-
errorCode,
|
|
293
|
-
errorType: error instanceof Error ? error.name : typeof error,
|
|
294
|
-
});
|
|
295
|
-
return controllerFailure(error);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (request.family === 'task-finalization') {
|
|
299
|
-
const parsed = parseTaskControlOperation('task-finalization', [manifest.taskId, 'complete', '--agent', request.agent]);
|
|
300
|
-
if (parsed.family !== 'task-finalization')
|
|
301
|
-
throw new Error('SANDBOX_CONTROL_FINALIZATION_OPERATION_INVALID');
|
|
302
|
-
const operation = parsed;
|
|
303
|
-
const result = dispatchTaskControlOperation(createSandboxExecutorExecutionContext({
|
|
304
|
-
repoRoot: manifest.repoRoot,
|
|
305
|
-
worktreeRoot: manifest.worktreeRoot,
|
|
306
|
-
runtimeDir: manifest.runtimeDir,
|
|
307
|
-
taskId: manifest.taskId,
|
|
308
|
-
generation: manifest.generation,
|
|
309
|
-
manifestPath,
|
|
310
|
-
requestId: request.id
|
|
311
|
-
}), operation);
|
|
312
|
-
return finalizationResult(await result);
|
|
209
|
+
if (request.family === 'agent-client') {
|
|
210
|
+
validateAgentClientOperation(request, manifest);
|
|
211
|
+
return executeAgentClientOperation(request, manifest, manifestPath, options.agentClientOperation);
|
|
313
212
|
}
|
|
314
213
|
const boundArgs = bindSandboxControlTask(request, manifest.taskId);
|
|
315
214
|
let operation;
|
|
@@ -346,7 +245,8 @@ export async function executeRequest(manifest, manifestPath, request, options =
|
|
|
346
245
|
const fields = requestAuditFields(manifest, request);
|
|
347
246
|
appendExecutorAudit(manifest, 'executor-request-start', fields);
|
|
348
247
|
try {
|
|
349
|
-
|
|
248
|
+
let result;
|
|
249
|
+
result = await executeRequestInner(manifest, manifestPath, request, options);
|
|
350
250
|
appendExecutorAudit(manifest, 'executor-request-finished', {
|
|
351
251
|
requestId: request.id,
|
|
352
252
|
requestFamily: request.family,
|
|
@@ -424,13 +324,13 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
|
|
|
424
324
|
return null;
|
|
425
325
|
}
|
|
426
326
|
})()
|
|
427
|
-
: null;
|
|
327
|
+
: request.family === 'agent-client' ? request.operation : null;
|
|
428
328
|
const context = createSandboxControlAuditContext(manifest, {
|
|
429
329
|
requestId: request.id,
|
|
430
330
|
family: request.family,
|
|
431
|
-
operation: operation
|
|
331
|
+
operation: operation && typeof operation === 'object' && 'family' in operation && operation.family === 'task-lifecycle'
|
|
432
332
|
? operation.request.intent
|
|
433
|
-
: request.family === '
|
|
333
|
+
: request.family === 'agent-client' ? request.operation : null,
|
|
434
334
|
phase: 'started-committed',
|
|
435
335
|
outcome: 'in-progress'
|
|
436
336
|
});
|
|
@@ -447,22 +347,9 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
|
|
|
447
347
|
});
|
|
448
348
|
const detail = error instanceof Error ? error.message : String(error);
|
|
449
349
|
const code = /^([A-Z][A-Z0-9_]+)/u.exec(detail)?.[1] ?? 'SANDBOX_CONTROL_EXECUTOR_FAILED';
|
|
450
|
-
result = request.family === 'task-
|
|
451
|
-
?
|
|
452
|
-
|
|
453
|
-
stdout: serializeTaskFinalizationEnvelope({
|
|
454
|
-
status: code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 'unknown' : 'failed',
|
|
455
|
-
changed: code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? null : false,
|
|
456
|
-
accepted: true,
|
|
457
|
-
requestId: request.id,
|
|
458
|
-
result: null,
|
|
459
|
-
error: { code, message: detail, retryable: false }
|
|
460
|
-
}),
|
|
461
|
-
stderr: ''
|
|
462
|
-
}
|
|
463
|
-
: request.family === 'task-create'
|
|
464
|
-
? taskCreateExecutionFailure(request.id, code, detail)
|
|
465
|
-
: lifecycleFailure(code, detail);
|
|
350
|
+
result = request.family === 'task-create'
|
|
351
|
+
? taskCreateExecutionFailure(request.id, code, detail)
|
|
352
|
+
: lifecycleFailure(code, detail);
|
|
466
353
|
}
|
|
467
354
|
process.stdout.write(result.stdout);
|
|
468
355
|
process.stderr.write(result.stderr);
|
|
@@ -9,6 +9,7 @@ import { DEFAULT_SANDBOX_CONTROL_TIMING } from './protocol.js';
|
|
|
9
9
|
import { inspectSandboxControlContainer } from './container-identity.js';
|
|
10
10
|
import { acquireSandboxResourceLock, resolveSandboxLockNamespace } from './native-file-lock.js';
|
|
11
11
|
import { isSandboxAuthorityEvidence } from '../engines/authority.js';
|
|
12
|
+
import { validateSandboxControlIdentity } from './identity-sentinel.js';
|
|
12
13
|
import { parseSandboxControlStatus, readExecution, terminateSandboxControlExecution } from './state.js';
|
|
13
14
|
const DEFAULT_QUIESCE_TIMEOUT_MS = DEFAULT_SANDBOX_CONTROL_TIMING.quiesceDeadlineMs;
|
|
14
15
|
const QUIESCING_FILE = 'quiescing.json';
|
|
@@ -500,6 +501,45 @@ function markSandboxControlRootQuiescing(root) {
|
|
|
500
501
|
throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
501
502
|
}
|
|
502
503
|
}
|
|
504
|
+
function readTaskCompletionManifest(root, binding) {
|
|
505
|
+
const manifest = readSandboxControlManifest(path.join(root, 'manifest.json'));
|
|
506
|
+
const identity = validateSandboxControlIdentity({
|
|
507
|
+
publicStatusDir: binding.statusDir,
|
|
508
|
+
root,
|
|
509
|
+
mode: manifest.mode,
|
|
510
|
+
taskId: manifest.taskId,
|
|
511
|
+
generation: manifest.generation,
|
|
512
|
+
controlRootId: manifest.controlRootId
|
|
513
|
+
});
|
|
514
|
+
if (identity.state !== 'valid' || manifest.mode !== 'task-bound'
|
|
515
|
+
|| manifest.taskId !== binding.taskId || manifest.generation !== binding.generation
|
|
516
|
+
|| manifest.controlRootId !== binding.controlRootId) {
|
|
517
|
+
throw new Error('SANDBOX_CONTROL_IDENTITY_MISMATCH');
|
|
518
|
+
}
|
|
519
|
+
return manifest;
|
|
520
|
+
}
|
|
521
|
+
export async function quiesceSandboxControlForTaskCompletion(root, binding, timeoutMs = DEFAULT_QUIESCE_TIMEOUT_MS) {
|
|
522
|
+
const resolvedRoot = path.resolve(root);
|
|
523
|
+
const manifest = readTaskCompletionManifest(resolvedRoot, binding);
|
|
524
|
+
markSandboxControlRootQuiescing(resolvedRoot);
|
|
525
|
+
const deadline = Date.now() + timeoutMs;
|
|
526
|
+
const startupPath = path.join(resolvedRoot, BROKER_STARTING_FILE);
|
|
527
|
+
const brokerPath = path.join(resolvedRoot, 'broker.json');
|
|
528
|
+
while (true) {
|
|
529
|
+
const startup = readStartupOwner(startupPath);
|
|
530
|
+
if (fs.existsSync(startupPath) && !startup)
|
|
531
|
+
throw new Error('SANDBOX_CONTROL_BROKER_START_TRANSITION');
|
|
532
|
+
const broker = readBrokerOwner(brokerPath);
|
|
533
|
+
if (broker && (broker.token !== manifest.token || broker.generation !== manifest.generation)) {
|
|
534
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
535
|
+
}
|
|
536
|
+
if (!startup && !broker)
|
|
537
|
+
return;
|
|
538
|
+
if (Date.now() >= deadline)
|
|
539
|
+
throw new Error('SANDBOX_CONTROL_BROKER_DRAIN_TIMEOUT');
|
|
540
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
503
543
|
function recordSandboxRemovalPending(root, manifest, phase, journal) {
|
|
504
544
|
const pendingPath = path.join(root, 'removal-pending.json');
|
|
505
545
|
const temporary = `${pendingPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
@@ -934,12 +974,34 @@ function readSandboxControlManifestValue(manifestPath) {
|
|
|
934
974
|
|| !isSandboxAuthorityEvidence(candidate.authorityEvidence))
|
|
935
975
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
936
976
|
const root = path.dirname(path.resolve(manifestPath));
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
977
|
+
const canonicalRoot = fs.realpathSync.native(root);
|
|
978
|
+
const matchesControlChildPath = (candidatePath, name) => {
|
|
979
|
+
const resolved = path.resolve(candidatePath);
|
|
980
|
+
if (path.basename(resolved) !== name)
|
|
981
|
+
return false;
|
|
982
|
+
const expected = path.join(canonicalRoot, name);
|
|
983
|
+
try {
|
|
984
|
+
return fs.realpathSync.native(resolved) === expected;
|
|
985
|
+
}
|
|
986
|
+
catch (error) {
|
|
987
|
+
if (error.code !== 'ENOENT')
|
|
988
|
+
throw error;
|
|
989
|
+
try {
|
|
990
|
+
return fs.realpathSync.native(path.dirname(resolved)) === canonicalRoot;
|
|
991
|
+
}
|
|
992
|
+
catch (parentError) {
|
|
993
|
+
if (parentError.code !== 'ENOENT')
|
|
994
|
+
throw parentError;
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
if (!matchesControlChildPath(candidate.channelDir, 'channel')
|
|
1000
|
+
|| !matchesControlChildPath(candidate.publicStatusDir, 'public')
|
|
1001
|
+
|| !matchesControlChildPath(candidate.processingDir, 'processing')) {
|
|
940
1002
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
941
1003
|
}
|
|
942
|
-
if (
|
|
1004
|
+
if (!matchesControlChildPath(candidate.runtimeDir, 'runtime')) {
|
|
943
1005
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
944
1006
|
}
|
|
945
1007
|
const normalized = {
|