@fitlab-ai/agent-infra 0.11.13 → 0.11.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/internal-cli.js +2 -7
- package/dist/lib/agent-clients/adapter.js +0 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +5 -9
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +1 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +1 -5
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -3
- package/dist/lib/agent-clients/adapters/codex.js +0 -2
- package/dist/lib/internal/cli-route-inventory.js +0 -1
- package/dist/lib/internal/task-finalization.js +6 -4
- package/dist/lib/internal/task-lifecycle.js +3 -1
- package/dist/lib/internal/task-operation-registry.js +1 -3
- package/dist/lib/internal/task-validate.js +14 -124
- package/dist/lib/platform/release-notes.js +2 -2
- package/dist/lib/sandbox/commands/control-status.js +0 -1
- package/dist/lib/sandbox/commands/create.js +5 -4
- package/dist/lib/sandbox/config.js +12 -0
- package/dist/lib/sandbox/control/audit.js +1 -2
- package/dist/lib/sandbox/control/client.js +12 -47
- package/dist/lib/sandbox/control/executor.js +6 -10
- package/dist/lib/sandbox/control/host-recovery.js +89 -27
- package/dist/lib/sandbox/control/lifecycle.js +20 -16
- package/dist/lib/sandbox/control/protocol.js +8 -36
- package/dist/lib/sandbox/control/server.js +2 -21
- package/dist/lib/sandbox/environment.js +7 -8
- package/dist/lib/sandbox/mounts.js +71 -59
- package/dist/lib/sandbox/recovery.js +2 -3
- package/dist/lib/sandbox/removal.js +19 -4
- package/dist/lib/sandbox/workspace-view.js +41 -4
- package/dist/lib/task/control-authority.js +39 -17
- package/dist/lib/task/control-recovery.js +1 -2
- package/dist/lib/task/finalization.js +13 -2
- package/dist/lib/task/lifecycle.js +92 -33
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +0 -27
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +6 -12
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +1 -5
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -7
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +1 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -4
- package/lib/agent-clients/adapters/codex-orchestration.ts +1 -3
- package/lib/agent-clients/adapters/codex.ts +0 -2
- package/lib/internal/cli-route-inventory.ts +0 -1
- package/lib/internal/task-finalization.ts +6 -4
- package/lib/internal/task-lifecycle.ts +3 -1
- package/lib/internal/task-operation-registry.ts +1 -3
- package/lib/internal/task-validate.ts +13 -127
- package/lib/platform/release-notes.ts +2 -2
- package/lib/sandbox/commands/control-status.ts +0 -1
- package/lib/sandbox/commands/create.ts +5 -4
- package/lib/sandbox/config.ts +13 -0
- package/lib/sandbox/control/audit.ts +1 -2
- package/lib/sandbox/control/client.ts +13 -61
- package/lib/sandbox/control/executor.ts +7 -13
- package/lib/sandbox/control/host-recovery.ts +94 -21
- package/lib/sandbox/control/lifecycle.ts +18 -16
- package/lib/sandbox/control/protocol.ts +9 -45
- package/lib/sandbox/control/server.ts +2 -17
- package/lib/sandbox/environment.ts +7 -8
- package/lib/sandbox/mounts.ts +92 -75
- package/lib/sandbox/recovery.ts +2 -3
- package/lib/sandbox/removal.ts +16 -4
- package/lib/sandbox/workspace-view.ts +40 -5
- package/lib/task/control-authority.ts +40 -26
- package/lib/task/control-recovery.ts +2 -3
- package/lib/task/finalization.ts +15 -2
- package/lib/task/lifecycle.ts +103 -31
- package/package.json +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +3 -4
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +3 -4
- package/templates/.agents/rules/task-short-id.en.md +18 -12
- package/templates/.agents/rules/task-short-id.zh-CN.md +12 -7
- package/templates/.agents/skills/create-release-note/SKILL.en.md +3 -1
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +0 -130
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +0 -127
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +0 -131
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-registration.js +0 -275
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js +0 -106
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +0 -345
- package/dist/lib/sandbox/control/agent-operation.js +0 -34
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +0 -158
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +0 -163
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +0 -151
- package/lib/agent-clients/adapters/codex-lifecycle/controller-registration.ts +0 -352
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts +0 -109
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +0 -437
- package/lib/sandbox/control/agent-operation.ts +0 -49
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import {
|
|
5
|
-
import { isAgentClientId } from '../../agent-clients/types.js';
|
|
4
|
+
import { SANDBOX_CONTROL_MAX_BYTES, SANDBOX_CONTROL_STATUS_STALE_MS, isSandboxControlFamily } from './protocol.js';
|
|
6
5
|
import { readSandboxControlPayload, readSandboxControlStatus } from './state.js';
|
|
7
6
|
import { accessSandboxTaskView, taskViewFromStatus } from './task-view.js';
|
|
8
7
|
import { readSandboxControlIdentitySentinel } from './identity-sentinel.js';
|
|
@@ -29,7 +28,7 @@ function sleep(milliseconds) {
|
|
|
29
28
|
function clientError(code, message, retryable, accepted = false, requestId = null) {
|
|
30
29
|
throw new SandboxControlClientError({ code, message: `${code}: ${message}`, retryable }, accepted, requestId);
|
|
31
30
|
}
|
|
32
|
-
function preflight(statusDir,
|
|
31
|
+
function preflight(statusDir, taskViewEffect = null, now = Date.now(), env = process.env) {
|
|
33
32
|
if (fs.existsSync(statusDir)) {
|
|
34
33
|
let identity;
|
|
35
34
|
try {
|
|
@@ -41,9 +40,6 @@ function preflight(statusDir, generation, taskViewEffect = null, now = Date.now(
|
|
|
41
40
|
: 'SANDBOX_CONTROL_IDENTITY_MALFORMED';
|
|
42
41
|
clientError(code, 'sandbox control identity is missing or invalid', false);
|
|
43
42
|
}
|
|
44
|
-
if (identity.generation !== generation) {
|
|
45
|
-
clientError('SANDBOX_CONTROL_IDENTITY_GENERATION_MISMATCH', 'sandbox control identity generation does not match the request', false);
|
|
46
|
-
}
|
|
47
43
|
if (env.AGENT_INFRA_CONTROL_ROOT_ID && identity.controlRootId !== env.AGENT_INFRA_CONTROL_ROOT_ID) {
|
|
48
44
|
clientError('SANDBOX_CONTROL_IDENTITY_ROOT_ID_MISMATCH', 'sandbox control identity root does not match the client configuration', false);
|
|
49
45
|
}
|
|
@@ -55,9 +51,6 @@ function preflight(statusDir, generation, taskViewEffect = null, now = Date.now(
|
|
|
55
51
|
catch {
|
|
56
52
|
clientError('SANDBOX_CONTROL_BROKER_UNAVAILABLE', 'broker status is missing or invalid', true);
|
|
57
53
|
}
|
|
58
|
-
if (status.generation !== generation) {
|
|
59
|
-
clientError('SANDBOX_CONTROL_GENERATION_INVALID', 'broker status generation does not match this sandbox', false);
|
|
60
|
-
}
|
|
61
54
|
if (now - status.updatedAt > SANDBOX_CONTROL_STATUS_STALE_MS || status.updatedAt > now + 1_000) {
|
|
62
55
|
clientError('SANDBOX_CONTROL_BROKER_UNAVAILABLE', 'broker status heartbeat is stale', true);
|
|
63
56
|
}
|
|
@@ -79,6 +72,7 @@ function preflight(statusDir, generation, taskViewEffect = null, now = Date.now(
|
|
|
79
72
|
clientError(access.reasonCode ?? 'SANDBOX_TASK_VIEW_DENIED', access.message ?? 'task view denied', false);
|
|
80
73
|
}
|
|
81
74
|
}
|
|
75
|
+
return status.generation;
|
|
82
76
|
}
|
|
83
77
|
function taskViewEffectForRequest(request) {
|
|
84
78
|
if (request.family === 'task-lifecycle')
|
|
@@ -165,7 +159,7 @@ function exchangeSandboxControl(request, params) {
|
|
|
165
159
|
const channelDir = params.channelDir ?? process.env.AGENT_INFRA_CONTROL_DIR ?? '/run/agent-infra/control';
|
|
166
160
|
const statusDir = params.statusDir ?? process.env.AGENT_INFRA_CONTROL_STATUS_DIR ?? '/run/agent-infra/control-status';
|
|
167
161
|
const taskViewEffect = taskViewEffectForRequest(request);
|
|
168
|
-
preflight(statusDir,
|
|
162
|
+
const hostGeneration = preflight(statusDir, taskViewEffect);
|
|
169
163
|
const identityPath = path.join(statusDir, 'identity.json');
|
|
170
164
|
if (fs.existsSync(statusDir)) {
|
|
171
165
|
const identity = readSandboxControlIdentitySentinel(statusDir);
|
|
@@ -213,7 +207,7 @@ function exchangeSandboxControl(request, params) {
|
|
|
213
207
|
try {
|
|
214
208
|
// The request-specific terminal proves the broker claimed the request; preserve recovery evidence if marker cleanup won the race.
|
|
215
209
|
accepted = true;
|
|
216
|
-
response = readPublishedResponse(raw, request.id, channelDir, accepted,
|
|
210
|
+
response = readPublishedResponse(raw, request.id, channelDir, accepted, hostGeneration);
|
|
217
211
|
malformedResponseRaw = null;
|
|
218
212
|
}
|
|
219
213
|
catch (error) {
|
|
@@ -239,7 +233,7 @@ function exchangeSandboxControl(request, params) {
|
|
|
239
233
|
}
|
|
240
234
|
if (!accepted) {
|
|
241
235
|
try {
|
|
242
|
-
preflight(statusDir,
|
|
236
|
+
preflight(statusDir, taskViewEffect);
|
|
243
237
|
}
|
|
244
238
|
catch (error) {
|
|
245
239
|
if (cancelPendingRequest(requestPath))
|
|
@@ -266,6 +260,9 @@ export function recoverSandboxControl(requestId, params = {}) {
|
|
|
266
260
|
const config = loadConfig();
|
|
267
261
|
const response = recoverSandboxControlFromHost(requestId, {
|
|
268
262
|
managedRoot: path.join(config.controlBase, config.project),
|
|
263
|
+
controlBase: config.controlBase,
|
|
264
|
+
project: config.project,
|
|
265
|
+
repoRoot: config.repoRoot,
|
|
269
266
|
timeoutMs: params.timeoutMs
|
|
270
267
|
});
|
|
271
268
|
if (response.phase === 'rejected' && response.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN') {
|
|
@@ -352,56 +349,24 @@ export function recoverSandboxControlFromChannel(requestId, params) {
|
|
|
352
349
|
}
|
|
353
350
|
clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'request did not produce a final result; inspect domain state before retrying', false, accepted, requestId);
|
|
354
351
|
}
|
|
355
|
-
function authority(params) {
|
|
356
|
-
const token = params.token ?? process.env.AGENT_INFRA_CONTROL_TOKEN;
|
|
357
|
-
const generation = params.generation ?? process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
358
|
-
if (!token || !generation)
|
|
359
|
-
clientError('SANDBOX_CONTROL_UNAVAILABLE', 'control authority is missing', false);
|
|
360
|
-
return { token, generation };
|
|
361
|
-
}
|
|
362
352
|
export function requestSandboxControl(params) {
|
|
363
353
|
if (!isSandboxControlFamily(params.family))
|
|
364
354
|
clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' is not allowed`, false);
|
|
365
|
-
if (params.family === 'task-create'
|
|
355
|
+
if (params.family === 'task-create') {
|
|
366
356
|
clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' requires a typed request`, false);
|
|
367
357
|
}
|
|
368
358
|
if (params.args[1] === 'complete') {
|
|
369
359
|
clientError('SANDBOX_CONTROL_COMMAND_DENIED', 'complete must use task-finalization', false);
|
|
370
360
|
}
|
|
371
|
-
const auth = authority(params);
|
|
372
|
-
const issuedAt = Date.now();
|
|
373
361
|
const request = {
|
|
374
|
-
version:
|
|
375
|
-
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
362
|
+
version: 5, id: randomUUID(),
|
|
376
363
|
family: params.family, args: params.args
|
|
377
364
|
};
|
|
378
365
|
return exchangeSandboxControl(request, params);
|
|
379
366
|
}
|
|
380
|
-
export function requestSandboxAgentOperation(params) {
|
|
381
|
-
if (!isAgentClientId(params.agentClient)) {
|
|
382
|
-
clientError('SANDBOX_CONTROL_REQUEST_INVALID', 'agent-client id is invalid', false);
|
|
383
|
-
}
|
|
384
|
-
const auth = authority(params);
|
|
385
|
-
const issuedAt = Date.now();
|
|
386
|
-
const request = {
|
|
387
|
-
version: 4,
|
|
388
|
-
id: randomUUID(),
|
|
389
|
-
...auth,
|
|
390
|
-
issuedAt,
|
|
391
|
-
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
392
|
-
family: 'agent-client',
|
|
393
|
-
agentClient: params.agentClient,
|
|
394
|
-
operation: params.operation,
|
|
395
|
-
payload: params.payload
|
|
396
|
-
};
|
|
397
|
-
return exchangeSandboxControl(request, params);
|
|
398
|
-
}
|
|
399
367
|
export function requestSandboxTaskCreate(params) {
|
|
400
|
-
const auth = authority(params);
|
|
401
|
-
const issuedAt = Date.now();
|
|
402
368
|
const request = {
|
|
403
|
-
version:
|
|
404
|
-
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
369
|
+
version: 5, id: randomUUID(),
|
|
405
370
|
family: 'task-create', candidate: params.candidate,
|
|
406
371
|
};
|
|
407
372
|
return exchangeSandboxControl(request, { ...params, timeoutMs: params.timeoutMs ?? 120_000 });
|
|
@@ -10,7 +10,6 @@ import { requestAuditFields, resultAuditFields, appendDiagnosticAudit, createSan
|
|
|
10
10
|
import { validateSandboxControlIdentity } from './identity-sentinel.js';
|
|
11
11
|
import { createSandboxExecutorExecutionContext, dispatchTaskControlOperation, parseTaskControlOperation } from '../../task/control-authority.js';
|
|
12
12
|
import { assertSandboxControlBrokerOwner, readSandboxControlManifest } from './lifecycle.js';
|
|
13
|
-
import { executeAgentClientOperation, validateAgentClientOperation } from './agent-operation.js';
|
|
14
13
|
function sameBrokerOwner(left, right) {
|
|
15
14
|
return left.version === right.version
|
|
16
15
|
&& left.pid === right.pid
|
|
@@ -206,10 +205,6 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
206
205
|
return taskCreateExecutionFailure(request.id, code, detail);
|
|
207
206
|
}
|
|
208
207
|
}
|
|
209
|
-
if (request.family === 'agent-client') {
|
|
210
|
-
validateAgentClientOperation(request, manifest);
|
|
211
|
-
return executeAgentClientOperation(request, manifest, manifestPath, options.agentClientOperation);
|
|
212
|
-
}
|
|
213
208
|
const boundArgs = bindSandboxControlTask(request, manifest.taskId);
|
|
214
209
|
let operation;
|
|
215
210
|
try {
|
|
@@ -269,10 +264,11 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
|
|
|
269
264
|
const gate = await waitForGate(nonce);
|
|
270
265
|
const gateOwner = gate.owner;
|
|
271
266
|
process.disconnect?.();
|
|
272
|
-
const
|
|
273
|
-
if (!
|
|
267
|
+
const requestedManifestPath = process.env.AGENT_INFRA_EXECUTOR_MANIFEST;
|
|
268
|
+
if (!requestedManifestPath)
|
|
274
269
|
throw new Error('SANDBOX_CONTROL_EXECUTOR_MANIFEST_MISSING');
|
|
275
|
-
const manifest = readSandboxControlManifest(
|
|
270
|
+
const manifest = readSandboxControlManifest(requestedManifestPath);
|
|
271
|
+
const manifestPath = fs.realpathSync.native(requestedManifestPath);
|
|
276
272
|
const identity = validateSandboxControlIdentity({
|
|
277
273
|
publicStatusDir: manifest.publicStatusDir,
|
|
278
274
|
root: path.dirname(path.resolve(manifestPath)),
|
|
@@ -324,13 +320,13 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
|
|
|
324
320
|
return null;
|
|
325
321
|
}
|
|
326
322
|
})()
|
|
327
|
-
:
|
|
323
|
+
: null;
|
|
328
324
|
const context = createSandboxControlAuditContext(manifest, {
|
|
329
325
|
requestId: request.id,
|
|
330
326
|
family: request.family,
|
|
331
327
|
operation: operation && typeof operation === 'object' && 'family' in operation && operation.family === 'task-lifecycle'
|
|
332
328
|
? operation.request.intent
|
|
333
|
-
:
|
|
329
|
+
: null,
|
|
334
330
|
phase: 'started-committed',
|
|
335
331
|
outcome: 'in-progress'
|
|
336
332
|
});
|
|
@@ -19,30 +19,92 @@ function manifestDigest(manifest) {
|
|
|
19
19
|
generation: manifest.generation, controlRootId: manifest.controlRootId
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
function readManagedManifest(manifestPath) {
|
|
23
|
+
try {
|
|
24
|
+
return readSandboxControlManifest(manifestPath);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
27
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function branchOnlyManifests(parentPath) {
|
|
31
|
+
const manifests = [];
|
|
32
|
+
for (const identity of fs.readdirSync(parentPath, { withFileTypes: true })) {
|
|
33
|
+
if (!identity.isDirectory() || identity.isSymbolicLink() || !/^[a-f0-9]{16}$/u.test(identity.name))
|
|
31
34
|
continue;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
manifests.push(readManagedManifest(path.join(parentPath, identity.name, 'manifest.json')));
|
|
36
|
+
}
|
|
37
|
+
return manifests;
|
|
38
|
+
}
|
|
39
|
+
function taskBoundManifests(params) {
|
|
40
|
+
assertDirectory(params.tasksRoot);
|
|
41
|
+
const canonicalManagedRoot = fs.realpathSync.native(path.dirname(params.tasksRoot));
|
|
42
|
+
const canonicalTasksRoot = fs.realpathSync.native(params.tasksRoot);
|
|
43
|
+
if (path.dirname(canonicalTasksRoot) !== canonicalManagedRoot)
|
|
44
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
45
|
+
const canonicalRepoRoot = fs.realpathSync.native(params.repoRoot);
|
|
46
|
+
const manifests = [];
|
|
47
|
+
for (const task of fs.readdirSync(params.tasksRoot, { withFileTypes: true })) {
|
|
48
|
+
if (!/^TASK-\d{8}-\d{6}$/u.test(task.name))
|
|
49
|
+
continue;
|
|
50
|
+
if (!task.isDirectory() || task.isSymbolicLink())
|
|
51
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
52
|
+
const taskRoot = path.join(params.tasksRoot, task.name);
|
|
53
|
+
assertNoSymlinkPath(params.controlBase, taskRoot);
|
|
54
|
+
const canonicalTaskRoot = fs.realpathSync.native(taskRoot);
|
|
55
|
+
if (path.dirname(canonicalTaskRoot) !== canonicalTasksRoot)
|
|
56
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
57
|
+
const manifestPath = path.join(taskRoot, 'manifest.json');
|
|
58
|
+
if (!fs.existsSync(manifestPath))
|
|
59
|
+
continue;
|
|
60
|
+
const manifest = readManagedManifest(manifestPath);
|
|
61
|
+
if (manifest.mode !== 'task-bound' || manifest.taskId !== task.name || manifest.project !== params.project
|
|
62
|
+
|| fs.realpathSync.native(manifest.repoRoot) !== canonicalRepoRoot
|
|
63
|
+
|| path.dirname(fs.realpathSync.native(manifestPath)) !== canonicalTaskRoot) {
|
|
64
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
37
65
|
}
|
|
66
|
+
manifests.push(manifest);
|
|
67
|
+
}
|
|
68
|
+
return manifests;
|
|
69
|
+
}
|
|
70
|
+
function managedManifestCandidates(params) {
|
|
71
|
+
const managedRoot = params.managedRoot;
|
|
72
|
+
const { controlBase, project, repoRoot } = params;
|
|
73
|
+
if (controlBase && project && path.resolve(managedRoot) !== path.resolve(controlBase, project)) {
|
|
74
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
38
75
|
}
|
|
39
|
-
|
|
76
|
+
if (!fs.existsSync(managedRoot))
|
|
77
|
+
return [];
|
|
78
|
+
assertDirectory(managedRoot);
|
|
79
|
+
return fs.readdirSync(managedRoot, { withFileTypes: true })
|
|
80
|
+
.flatMap((parent) => {
|
|
81
|
+
const parentPath = path.join(managedRoot, parent.name);
|
|
82
|
+
if (parent.name === 'tasks' && controlBase && project && repoRoot) {
|
|
83
|
+
return taskBoundManifests({ tasksRoot: parentPath, controlBase, project, repoRoot });
|
|
84
|
+
}
|
|
85
|
+
if (!parent.isDirectory() || parent.isSymbolicLink())
|
|
86
|
+
return [];
|
|
87
|
+
return branchOnlyManifests(parentPath);
|
|
88
|
+
});
|
|
40
89
|
}
|
|
41
90
|
function assertDirectory(directory) {
|
|
42
91
|
const stat = fs.lstatSync(directory);
|
|
43
92
|
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
44
93
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
45
94
|
}
|
|
95
|
+
function assertNoSymlinkPath(root, target) {
|
|
96
|
+
const relative = path.relative(root, target);
|
|
97
|
+
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
98
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
99
|
+
let current = path.resolve(root);
|
|
100
|
+
if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink())
|
|
101
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
102
|
+
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
103
|
+
current = path.join(current, segment);
|
|
104
|
+
if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink())
|
|
105
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
46
108
|
function readAudit(manifest) {
|
|
47
109
|
const root = path.dirname(path.resolve(manifest.publicStatusDir));
|
|
48
110
|
const records = [];
|
|
@@ -80,12 +142,16 @@ function auditProof(manifest, requestId) {
|
|
|
80
142
|
return [];
|
|
81
143
|
return related;
|
|
82
144
|
}
|
|
83
|
-
function publishedEvidence(records,
|
|
145
|
+
function publishedEvidence(records, manifest) {
|
|
84
146
|
const publications = records.filter((record) => record.event === 'executor-result-published');
|
|
85
147
|
let selected = null;
|
|
86
148
|
let selectedSummary = null;
|
|
149
|
+
const digest = manifestDigest(manifest);
|
|
87
150
|
for (const record of publications) {
|
|
88
|
-
if (record.version !== 2 || record.
|
|
151
|
+
if (record.version !== 2 || record.generation !== manifest.generation || record.identityDigest !== digest
|
|
152
|
+
|| typeof record.requestFamily !== 'string'
|
|
153
|
+
|| !['task-lifecycle', 'task-create'].includes(record.requestFamily)
|
|
154
|
+
|| (record.sandboxTaskId !== null && typeof record.sandboxTaskId !== 'string')
|
|
89
155
|
|| !Number.isSafeInteger(record.exitCode)
|
|
90
156
|
|| !Number.isSafeInteger(record.outputBytes) || !Number.isSafeInteger(record.errorBytes)
|
|
91
157
|
|| typeof record.outputDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(record.outputDigest)
|
|
@@ -93,7 +159,7 @@ function publishedEvidence(records, generation) {
|
|
|
93
159
|
throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
|
|
94
160
|
}
|
|
95
161
|
const summary = JSON.stringify([
|
|
96
|
-
record.requestFamily
|
|
162
|
+
record.requestFamily, record.sandboxTaskId, record.generation, record.identityDigest, record.exitCode,
|
|
97
163
|
record.outputBytes, record.errorBytes, record.outputDigest, record.errorDigest
|
|
98
164
|
]);
|
|
99
165
|
if (selectedSummary !== null && summary !== selectedSummary) {
|
|
@@ -102,6 +168,9 @@ function publishedEvidence(records, generation) {
|
|
|
102
168
|
selected ??= record;
|
|
103
169
|
selectedSummary ??= summary;
|
|
104
170
|
}
|
|
171
|
+
if (selected && selected.sandboxTaskId !== manifest.taskId) {
|
|
172
|
+
throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
|
|
173
|
+
}
|
|
105
174
|
return selected;
|
|
106
175
|
}
|
|
107
176
|
function verifiedResponse(manifest, requestId, records) {
|
|
@@ -131,7 +200,7 @@ function verifiedResponse(manifest, requestId, records) {
|
|
|
131
200
|
if (response.phase === 'rejected' && response.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN')
|
|
132
201
|
return unknown(requestId);
|
|
133
202
|
const evidencePath = path.join(manifest.processingDir, requestId, 'result.json');
|
|
134
|
-
const published = publishedEvidence(records, manifest
|
|
203
|
+
const published = publishedEvidence(records, manifest);
|
|
135
204
|
if (!published)
|
|
136
205
|
return unknown(requestId);
|
|
137
206
|
let evidence = null;
|
|
@@ -187,15 +256,8 @@ export function recoverSandboxControlFromHost(requestId, params) {
|
|
|
187
256
|
let candidates = [];
|
|
188
257
|
while (Date.now() < deadline) {
|
|
189
258
|
candidates = [];
|
|
190
|
-
for (const
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
manifest = readSandboxControlManifest(manifestPath);
|
|
194
|
-
}
|
|
195
|
-
catch {
|
|
196
|
-
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
197
|
-
}
|
|
198
|
-
if (manifest.project !== path.basename(path.resolve(params.managedRoot)) || !manifest.generation) {
|
|
259
|
+
for (const manifest of managedManifestCandidates(params)) {
|
|
260
|
+
if (manifest.project !== (params.project ?? path.basename(path.resolve(params.managedRoot))) || !manifest.generation) {
|
|
199
261
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
200
262
|
}
|
|
201
263
|
const records = auditProof(manifest, requestId);
|
|
@@ -975,35 +975,39 @@ function readSandboxControlManifestValue(manifestPath) {
|
|
|
975
975
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
976
976
|
const root = path.dirname(path.resolve(manifestPath));
|
|
977
977
|
const canonicalRoot = fs.realpathSync.native(root);
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
978
|
+
const resolveControlChildPath = (candidatePath, name) => {
|
|
979
|
+
if (path.isAbsolute(candidatePath) || candidatePath !== name)
|
|
980
|
+
return null;
|
|
981
|
+
const resolved = path.resolve(root, candidatePath);
|
|
982
982
|
const expected = path.join(canonicalRoot, name);
|
|
983
983
|
try {
|
|
984
|
-
|
|
984
|
+
const stat = fs.lstatSync(resolved);
|
|
985
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
986
|
+
return null;
|
|
987
|
+
return fs.realpathSync.native(resolved) === expected ? expected : null;
|
|
985
988
|
}
|
|
986
989
|
catch (error) {
|
|
987
990
|
if (error.code !== 'ENOENT')
|
|
988
991
|
throw error;
|
|
989
992
|
try {
|
|
990
|
-
return fs.realpathSync.native(path.dirname(resolved)) === canonicalRoot;
|
|
993
|
+
return fs.realpathSync.native(path.dirname(resolved)) === canonicalRoot ? expected : null;
|
|
991
994
|
}
|
|
992
995
|
catch (parentError) {
|
|
993
996
|
if (parentError.code !== 'ENOENT')
|
|
994
997
|
throw parentError;
|
|
995
|
-
return
|
|
998
|
+
return null;
|
|
996
999
|
}
|
|
997
1000
|
}
|
|
998
1001
|
};
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
+
const channelDir = resolveControlChildPath(candidate.channelDir, 'channel');
|
|
1003
|
+
const publicStatusDir = resolveControlChildPath(candidate.publicStatusDir, 'public');
|
|
1004
|
+
const processingDir = resolveControlChildPath(candidate.processingDir, 'processing');
|
|
1005
|
+
const runtimeDir = resolveControlChildPath(candidate.runtimeDir, 'runtime');
|
|
1006
|
+
if (!channelDir || !publicStatusDir || !processingDir) {
|
|
1002
1007
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
1003
1008
|
}
|
|
1004
|
-
if (!
|
|
1009
|
+
if (!runtimeDir)
|
|
1005
1010
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
1006
|
-
}
|
|
1007
1011
|
const normalized = {
|
|
1008
1012
|
engine: candidate.engine,
|
|
1009
1013
|
repoRoot: candidate.repoRoot,
|
|
@@ -1021,10 +1025,10 @@ function readSandboxControlManifestValue(manifestPath) {
|
|
|
1021
1025
|
token: candidate.token,
|
|
1022
1026
|
generation: candidate.generation,
|
|
1023
1027
|
controlRootId: candidate.controlRootId,
|
|
1024
|
-
channelDir
|
|
1025
|
-
publicStatusDir
|
|
1026
|
-
processingDir
|
|
1027
|
-
runtimeDir
|
|
1028
|
+
channelDir,
|
|
1029
|
+
publicStatusDir,
|
|
1030
|
+
processingDir,
|
|
1031
|
+
runtimeDir,
|
|
1028
1032
|
};
|
|
1029
1033
|
return normalized;
|
|
1030
1034
|
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { validateTaskCreateCandidate } from '../../task/create.js';
|
|
3
|
-
import { isAgentClientId } from '../../agent-clients/types.js';
|
|
4
3
|
export const SANDBOX_CONTROL_MAX_BYTES = 64 * 1024;
|
|
5
4
|
export const SANDBOX_CONTROL_MAX_LOGICAL_RECORDS = 1024;
|
|
6
5
|
export const SANDBOX_CONTROL_MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
7
6
|
export const SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES = 1024 * 1024;
|
|
8
7
|
export const SANDBOX_CONTROL_RESERVATION_BYTES = SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES + 4 * 1024;
|
|
9
|
-
export const SANDBOX_CONTROL_ADMISSION_WINDOW_MS = 2_000;
|
|
10
8
|
export const SANDBOX_CONTROL_STATUS_INTERVAL_MS = 250;
|
|
11
9
|
export const SANDBOX_CONTROL_STATUS_STALE_MS = 1_500;
|
|
12
10
|
export const SANDBOX_CONTROL_FUTURE_SKEW_MS = 1_000;
|
|
13
|
-
export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-create'
|
|
11
|
+
export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-create'];
|
|
14
12
|
export const DEFAULT_SANDBOX_CONTROL_TIMING = Object.freeze({
|
|
15
13
|
controlTickMs: 250,
|
|
16
14
|
parkedBindingInitialMs: 1_000,
|
|
@@ -106,51 +104,26 @@ export function parseSandboxControlReservation(value) {
|
|
|
106
104
|
export function isSandboxControlFamily(value) {
|
|
107
105
|
return SANDBOX_CONTROL_FAMILIES.includes(value);
|
|
108
106
|
}
|
|
109
|
-
export function validateSandboxControlRequest(value, manifest
|
|
107
|
+
export function validateSandboxControlRequest(value, manifest) {
|
|
110
108
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
111
109
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request must be an object');
|
|
112
110
|
}
|
|
113
111
|
const request = value;
|
|
114
|
-
if (request.version !==
|
|
115
|
-
|| !/^[a-f0-9-]{16,64}$/.test(request.id)
|
|
116
|
-
|| typeof request.family !== 'string' || !isSandboxControlFamily(request.family)
|
|
117
|
-
|| !Number.isSafeInteger(request.issuedAt) || !Number.isSafeInteger(request.expiresAt))
|
|
112
|
+
if (request.version !== 5 || typeof request.id !== 'string'
|
|
113
|
+
|| !/^[a-f0-9-]{16,64}$/.test(request.id)
|
|
114
|
+
|| typeof request.family !== 'string' || !isSandboxControlFamily(request.family))
|
|
118
115
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
|
|
119
|
-
if (request.generation !== manifest.generation) {
|
|
120
|
-
fail('SANDBOX_CONTROL_GENERATION_INVALID', 'request generation does not match the broker');
|
|
121
|
-
}
|
|
122
|
-
const issuedAt = request.issuedAt;
|
|
123
|
-
const expiresAt = request.expiresAt;
|
|
124
|
-
const now = options.now ?? Date.now();
|
|
125
|
-
if (expiresAt <= issuedAt || expiresAt - issuedAt > SANDBOX_CONTROL_ADMISSION_WINDOW_MS || issuedAt > now + SANDBOX_CONTROL_FUTURE_SKEW_MS) {
|
|
126
|
-
fail('SANDBOX_CONTROL_REQUEST_DEADLINE_INVALID', 'request admission deadline is invalid');
|
|
127
|
-
}
|
|
128
|
-
if (now > expiresAt)
|
|
129
|
-
fail('SANDBOX_CONTROL_REQUEST_EXPIRED', 'request admission deadline has passed', true);
|
|
130
116
|
if (Buffer.byteLength(JSON.stringify(request), 'utf8') > SANDBOX_CONTROL_MAX_BYTES) {
|
|
131
117
|
fail('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit');
|
|
132
118
|
}
|
|
133
119
|
if (request.family === 'task-create') {
|
|
134
|
-
const expected = ['candidate', '
|
|
120
|
+
const expected = ['candidate', 'family', 'id', 'version'];
|
|
135
121
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')) {
|
|
136
122
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
|
|
137
123
|
}
|
|
138
124
|
return { ...request, candidate: validateTaskCreateCandidate(request.candidate) };
|
|
139
125
|
}
|
|
140
|
-
|
|
141
|
-
const expected = ['agentClient', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'payload', 'token', 'version'];
|
|
142
|
-
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
143
|
-
|| !isAgentClientId(request.agentClient)
|
|
144
|
-
|| typeof request.operation !== 'string' || !/^[a-z][a-z0-9.-]{0,63}$/u.test(request.operation)
|
|
145
|
-
|| !request.payload || typeof request.payload !== 'object' || Array.isArray(request.payload)) {
|
|
146
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'agent-client request schema is invalid');
|
|
147
|
-
}
|
|
148
|
-
if (manifest.mode !== 'task-bound' || !manifest.taskId) {
|
|
149
|
-
fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot coordinate agent clients');
|
|
150
|
-
}
|
|
151
|
-
return request;
|
|
152
|
-
}
|
|
153
|
-
const expected = ['args', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
|
|
126
|
+
const expected = ['args', 'family', 'id', 'version'];
|
|
154
127
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
155
128
|
|| !Array.isArray(request.args) || !request.args.every((arg) => typeof arg === 'string')) {
|
|
156
129
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
|
|
@@ -164,7 +137,7 @@ export function validateSandboxControlRequest(value, manifest, options = {}) {
|
|
|
164
137
|
return request;
|
|
165
138
|
}
|
|
166
139
|
export function bindSandboxControlTask(request, taskId) {
|
|
167
|
-
if (request.family === 'task-create'
|
|
140
|
+
if (request.family === 'task-create') {
|
|
168
141
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', `${request.family} requests do not bind a current task`);
|
|
169
142
|
}
|
|
170
143
|
if (request.args.length === 0)
|
|
@@ -180,7 +153,6 @@ export function controlError(error) {
|
|
|
180
153
|
const retryable = new Set([
|
|
181
154
|
'SANDBOX_CONTROL_BUSY',
|
|
182
155
|
'SANDBOX_CONTROL_HANDOFF_ACTIVE',
|
|
183
|
-
'SANDBOX_CONTROL_REQUEST_EXPIRED',
|
|
184
156
|
'SANDBOX_WORKTREE_BINDING_LOST'
|
|
185
157
|
]).has(code);
|
|
186
158
|
return { code, message, retryable };
|
|
@@ -18,7 +18,6 @@ import { inspectSandboxControlContainer } from './container-identity.js';
|
|
|
18
18
|
import { acquireSandboxControlBrokerStartup, garbageCollectSandboxControlRoot, isSandboxControlRootQuiescing, readSandboxControlManifest } from './lifecycle.js';
|
|
19
19
|
import { nextSandboxControlBackoff } from './timing.js';
|
|
20
20
|
import { readTaskFinalizationReceipt } from '../../task/finalization.js';
|
|
21
|
-
import { recoverAgentClientOperation, validateAgentClientOperation } from './agent-operation.js';
|
|
22
21
|
import { validateSandboxControlIdentity } from './identity-sentinel.js';
|
|
23
22
|
import { mergeSandboxTaskView, taskViewForManifest } from './task-view.js';
|
|
24
23
|
import { taskCreateOutputUnavailableResult } from '../../task/create-service.js';
|
|
@@ -28,10 +27,6 @@ function appendBrokerAudit(manifest, event, fields = {}) {
|
|
|
28
27
|
function operationKey(request) {
|
|
29
28
|
if (request.family === 'task-create')
|
|
30
29
|
return 'create';
|
|
31
|
-
if (request.family === 'agent-client')
|
|
32
|
-
return request.operation;
|
|
33
|
-
if (request.family !== 'task-lifecycle')
|
|
34
|
-
return null;
|
|
35
30
|
try {
|
|
36
31
|
const operation = parseTaskControlOperation(request.family, request.args);
|
|
37
32
|
return operation.family === 'task-lifecycle' ? operation.request.intent : null;
|
|
@@ -193,7 +188,7 @@ async function readRecoveryDomain(manifest, manifestPath, request, operation, te
|
|
|
193
188
|
return { domain: null };
|
|
194
189
|
const taskRef = 'args' in request ? request.args[0] ?? null : manifest.taskId;
|
|
195
190
|
const output = parseControlOutput(payloadOutput);
|
|
196
|
-
if (!taskRef && operation.family !== 'task-create'
|
|
191
|
+
if (!taskRef && operation.family !== 'task-create') {
|
|
197
192
|
return { domain: null };
|
|
198
193
|
}
|
|
199
194
|
if (operation.family === 'task-lifecycle') {
|
|
@@ -221,14 +216,6 @@ async function readRecoveryDomain(manifest, manifestPath, request, operation, te
|
|
|
221
216
|
if (operation.family === 'task-create') {
|
|
222
217
|
return { domain: taskCreateDomainEvidence(manifest, output) };
|
|
223
218
|
}
|
|
224
|
-
if (operation.family === 'agent-client' && request.family === 'agent-client') {
|
|
225
|
-
return { domain: recoverAgentClientOperation({
|
|
226
|
-
request,
|
|
227
|
-
manifest,
|
|
228
|
-
manifestPath,
|
|
229
|
-
stdout: payloadOutput
|
|
230
|
-
}) };
|
|
231
|
-
}
|
|
232
219
|
return { domain: null };
|
|
233
220
|
}
|
|
234
221
|
function readCommittedCriticalPhases(manifest, requestId) {
|
|
@@ -443,11 +430,7 @@ async function recoverProcessing(manifest, manifestPath, broker, brokerOwns) {
|
|
|
443
430
|
let terminalReconciled = false;
|
|
444
431
|
try {
|
|
445
432
|
const rawRequest = JSON.parse(fs.readFileSync(path.join(processingDirectory, 'request.json'), 'utf8'));
|
|
446
|
-
request = validateSandboxControlRequest(rawRequest, manifest
|
|
447
|
-
now: typeof rawRequest.issuedAt === 'number' ? rawRequest.issuedAt : undefined
|
|
448
|
-
});
|
|
449
|
-
if (request.family === 'agent-client')
|
|
450
|
-
validateAgentClientOperation(request, manifest);
|
|
433
|
+
request = validateSandboxControlRequest(rawRequest, manifest);
|
|
451
434
|
}
|
|
452
435
|
catch {
|
|
453
436
|
// Missing or malformed request evidence remains fail-closed below.
|
|
@@ -926,8 +909,6 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
926
909
|
if (active)
|
|
927
910
|
throw new Error('SANDBOX_CONTROL_BUSY');
|
|
928
911
|
const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
|
|
929
|
-
if (request.family === 'agent-client')
|
|
930
|
-
validateAgentClientOperation(request, manifest);
|
|
931
912
|
validatedRequest = request;
|
|
932
913
|
criticalRequestPhase(manifest, request, 'validated', 'in-progress');
|
|
933
914
|
appendBrokerAudit(manifest, 'request-validated', {
|
|
@@ -5,14 +5,13 @@ export const SANDBOX_CONTROL_STATUS_MOUNT = '/run/agent-infra/control-status';
|
|
|
5
5
|
const TASK_MARKER_KEYS = [
|
|
6
6
|
'AGENT_INFRA_SANDBOX',
|
|
7
7
|
'AGENT_INFRA_TASK_ID',
|
|
8
|
-
'AGENT_INFRA_CONTROL_TOKEN',
|
|
9
8
|
'AGENT_INFRA_CONTROL_GENERATION',
|
|
10
9
|
'AGENT_INFRA_CONTROL_DIR',
|
|
10
|
+
'AGENT_INFRA_CONTROL_ROOT_DIR',
|
|
11
11
|
'AGENT_INFRA_CONTROL_STATUS_DIR',
|
|
12
12
|
'AGENT_INFRA_RUNTIME_DIR'
|
|
13
13
|
];
|
|
14
14
|
const TASK_CONTROL_MARKER_KEYS = [
|
|
15
|
-
'AGENT_INFRA_CONTROL_TOKEN',
|
|
16
15
|
'AGENT_INFRA_CONTROL_GENERATION',
|
|
17
16
|
'AGENT_INFRA_CONTROL_DIR',
|
|
18
17
|
'AGENT_INFRA_CONTROL_STATUS_DIR'
|
|
@@ -77,7 +76,6 @@ export function hasSandboxControlMarker(env = process.env) {
|
|
|
77
76
|
export function resolveSandboxControlTransport(env = process.env, options = {}) {
|
|
78
77
|
const markerState = sandboxTaskMarkerState(env);
|
|
79
78
|
const hasAnyMarker = markerState !== 'none'
|
|
80
|
-
|| Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING)
|
|
81
79
|
|| Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST);
|
|
82
80
|
const fixedStatusDir = options.statusMountPath ?? SANDBOX_CONTROL_STATUS_MOUNT;
|
|
83
81
|
const fixedStatusProbe = path.isAbsolute(fixedStatusDir)
|
|
@@ -98,6 +96,10 @@ export function resolveSandboxControlTransport(env = process.env, options = {})
|
|
|
98
96
|
if (!hasCompleteConfig) {
|
|
99
97
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
|
|
100
98
|
}
|
|
99
|
+
if (markerState === 'task-bound'
|
|
100
|
+
&& (!env.AGENT_INFRA_CONTROL_ROOT_DIR || !path.isAbsolute(env.AGENT_INFRA_CONTROL_ROOT_DIR))) {
|
|
101
|
+
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
|
|
102
|
+
}
|
|
101
103
|
const configuredStatusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
102
104
|
const configuredStatusProbe = configuredStatusDir && path.isAbsolute(configuredStatusDir)
|
|
103
105
|
? nativeDirectoryProbe(configuredStatusDir) : 'absent';
|
|
@@ -108,7 +110,7 @@ export function resolveSandboxControlTransport(env = process.env, options = {})
|
|
|
108
110
|
? configuredStatusProbe === 'present' ? configuredStatusDir : null
|
|
109
111
|
: fixedStatusMounted ? fixedStatusDir : null;
|
|
110
112
|
const statusMounted = statusDir !== null;
|
|
111
|
-
if (env.AGENT_INFRA_EXECUTOR_MANIFEST
|
|
113
|
+
if (env.AGENT_INFRA_EXECUTOR_MANIFEST) {
|
|
112
114
|
return { kind: 'fail-closed', reasonCode: 'TASK_CONTROL_TRANSPORT_INVALID' };
|
|
113
115
|
}
|
|
114
116
|
const taskBound = Boolean(env.AGENT_INFRA_TASK_ID);
|
|
@@ -124,9 +126,6 @@ export function resolveSandboxControlTransport(env = process.env, options = {})
|
|
|
124
126
|
? 'SANDBOX_CONTROL_IDENTITY_MISSING'
|
|
125
127
|
: 'SANDBOX_CONTROL_IDENTITY_MALFORMED' };
|
|
126
128
|
}
|
|
127
|
-
if (sentinel.generation !== env.AGENT_INFRA_CONTROL_GENERATION) {
|
|
128
|
-
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_GENERATION_MISMATCH' };
|
|
129
|
-
}
|
|
130
129
|
if (sentinel.controlRootId !== env.AGENT_INFRA_CONTROL_ROOT_ID) {
|
|
131
130
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_ROOT_ID_MISMATCH' };
|
|
132
131
|
}
|
|
@@ -134,7 +133,7 @@ export function resolveSandboxControlTransport(env = process.env, options = {})
|
|
|
134
133
|
&& (sentinel.taskId !== env.AGENT_INFRA_TASK_ID || !runtime)) {
|
|
135
134
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH' };
|
|
136
135
|
}
|
|
137
|
-
if (sentinel.mode === 'branch-only' && (taskBound || runtime)) {
|
|
136
|
+
if (sentinel.mode === 'branch-only' && (taskBound || runtime || Boolean(env.AGENT_INFRA_CONTROL_ROOT_DIR))) {
|
|
138
137
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH' };
|
|
139
138
|
}
|
|
140
139
|
return { kind: options.localWorkflow ? 'sandbox-local' : 'broker-client', reasonCode: null };
|