@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
|
@@ -10,8 +10,6 @@ import {
|
|
|
10
10
|
taskCreateFailure,
|
|
11
11
|
type TaskCreateResult
|
|
12
12
|
} from '../../task/create-service.ts';
|
|
13
|
-
import { applyTaskFinalization } from '../../task/finalization.ts';
|
|
14
|
-
import { serializeTaskFinalizationEnvelope } from '../../task/finalization-envelope.ts';
|
|
15
13
|
import {
|
|
16
14
|
bindSandboxControlTask,
|
|
17
15
|
validateSandboxControlRequest,
|
|
@@ -35,13 +33,8 @@ import {
|
|
|
35
33
|
type TaskControlOperation
|
|
36
34
|
} from '../../task/control-authority.ts';
|
|
37
35
|
import { assertSandboxControlBrokerOwner, readSandboxControlManifest, type BrokerOwner } from './lifecycle.ts';
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
closeCodexControllerRegistration,
|
|
41
|
-
CodexControllerRegistrationError,
|
|
42
|
-
openCodexControllerRegistration,
|
|
43
|
-
resolveCodexControllerBinding
|
|
44
|
-
} from './controller-registration.ts';
|
|
36
|
+
import { executeAgentClientOperation, validateAgentClientOperation } from './agent-operation.ts';
|
|
37
|
+
import type { AgentClientSandboxControlOperation } from '../../agent-clients/adapter.ts';
|
|
45
38
|
|
|
46
39
|
export type SandboxControlExecutionResult = {
|
|
47
40
|
exitCode: number;
|
|
@@ -234,24 +227,6 @@ function waitForGate(nonce: string, timeoutMs = 2_000): Promise<Readonly<{ owner
|
|
|
234
227
|
});
|
|
235
228
|
}
|
|
236
229
|
|
|
237
|
-
function controllerFailure(error: unknown): SandboxControlExecutionResult {
|
|
238
|
-
const code = error instanceof CodexControllerRegistrationError
|
|
239
|
-
? error.code
|
|
240
|
-
: /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
|
|
241
|
-
?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
|
|
242
|
-
const message = error instanceof CodexControllerRegistrationError
|
|
243
|
-
? error.message
|
|
244
|
-
: `${code}: controller operation failed; inspect the sandbox controller and rebuild the sandbox if needed`;
|
|
245
|
-
const payload = {
|
|
246
|
-
version: 1,
|
|
247
|
-
status: 'failed',
|
|
248
|
-
changed: false,
|
|
249
|
-
lease: null,
|
|
250
|
-
error: { code, message, retryable: false }
|
|
251
|
-
};
|
|
252
|
-
return { exitCode: 1, stdout: `${JSON.stringify(payload)}\n`, stderr: '' };
|
|
253
|
-
}
|
|
254
|
-
|
|
255
230
|
function lifecycleFailure(code: string, message: string): SandboxControlExecutionResult {
|
|
256
231
|
return {
|
|
257
232
|
exitCode: 1,
|
|
@@ -279,26 +254,9 @@ function taskCreateExecutionFailure(requestId: string, code: string, message: st
|
|
|
279
254
|
}));
|
|
280
255
|
}
|
|
281
256
|
|
|
282
|
-
function finalizationResult(result: Awaited<ReturnType<typeof applyTaskFinalization>>): SandboxControlExecutionResult {
|
|
283
|
-
const payload = {
|
|
284
|
-
version: 1,
|
|
285
|
-
status: result.status,
|
|
286
|
-
changed: result.changed,
|
|
287
|
-
accepted: true,
|
|
288
|
-
result,
|
|
289
|
-
error: result.error
|
|
290
|
-
};
|
|
291
|
-
return {
|
|
292
|
-
exitCode: result.status === 'completed' ? 0 : result.status === 'blocked' ? 2 : 1,
|
|
293
|
-
stdout: `${JSON.stringify(payload)}\n`,
|
|
294
|
-
stderr: ''
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
257
|
type ExecuteRequestOptions = Readonly<{
|
|
299
|
-
buildIdentity?: typeof computeLifecycleBuildIdentity;
|
|
300
|
-
resolveControllerBinding?: typeof resolveCodexControllerBinding;
|
|
301
258
|
createTask?: typeof createTask;
|
|
259
|
+
agentClientOperation?: AgentClientSandboxControlOperation;
|
|
302
260
|
}>;
|
|
303
261
|
|
|
304
262
|
async function executeRequestInner(
|
|
@@ -321,78 +279,9 @@ async function executeRequestInner(
|
|
|
321
279
|
return taskCreateExecutionFailure(request.id, code, detail);
|
|
322
280
|
}
|
|
323
281
|
}
|
|
324
|
-
if (request.family === '
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const binding = (options.resolveControllerBinding ?? resolveCodexControllerBinding)({
|
|
328
|
-
manifest,
|
|
329
|
-
manifestPath,
|
|
330
|
-
proof: request.controllerProof!,
|
|
331
|
-
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
332
|
-
});
|
|
333
|
-
return {
|
|
334
|
-
exitCode: 0,
|
|
335
|
-
stdout: `${JSON.stringify({
|
|
336
|
-
version: 1,
|
|
337
|
-
status: 'verified',
|
|
338
|
-
changed: false,
|
|
339
|
-
lease: null,
|
|
340
|
-
binding: {
|
|
341
|
-
taskId: manifest.taskId,
|
|
342
|
-
controlGeneration: binding.controlGeneration,
|
|
343
|
-
controllerInstanceDigest: binding.instanceDigest
|
|
344
|
-
},
|
|
345
|
-
error: null
|
|
346
|
-
})}\n`,
|
|
347
|
-
stderr: ''
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
const result = request.command === 'open'
|
|
351
|
-
? openCodexControllerRegistration({
|
|
352
|
-
manifest,
|
|
353
|
-
manifestPath,
|
|
354
|
-
controllerProcess: request.controllerProcess!,
|
|
355
|
-
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
356
|
-
})
|
|
357
|
-
: closeCodexControllerRegistration({
|
|
358
|
-
manifest,
|
|
359
|
-
manifestPath,
|
|
360
|
-
proof: request.controllerProof!
|
|
361
|
-
});
|
|
362
|
-
return { exitCode: 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
|
|
363
|
-
} catch (error) {
|
|
364
|
-
const errorCode = error instanceof CodexControllerRegistrationError
|
|
365
|
-
? error.code
|
|
366
|
-
: /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
|
|
367
|
-
?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
|
|
368
|
-
appendExecutorAudit(manifest, 'controller-operation-failed', {
|
|
369
|
-
...requestAuditFields(manifest, request),
|
|
370
|
-
controllerCommand: request.command,
|
|
371
|
-
errorCode,
|
|
372
|
-
errorType: error instanceof Error ? error.name : typeof error,
|
|
373
|
-
});
|
|
374
|
-
return controllerFailure(error);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (request.family === 'task-finalization') {
|
|
378
|
-
const parsed = parseTaskControlOperation(
|
|
379
|
-
'task-finalization', [manifest.taskId!, 'complete', '--agent', request.agent]
|
|
380
|
-
);
|
|
381
|
-
if (parsed.family !== 'task-finalization') throw new Error('SANDBOX_CONTROL_FINALIZATION_OPERATION_INVALID');
|
|
382
|
-
const operation = parsed;
|
|
383
|
-
const result = dispatchTaskControlOperation(
|
|
384
|
-
createSandboxExecutorExecutionContext({
|
|
385
|
-
repoRoot: manifest.repoRoot,
|
|
386
|
-
worktreeRoot: manifest.worktreeRoot,
|
|
387
|
-
runtimeDir: manifest.runtimeDir,
|
|
388
|
-
taskId: manifest.taskId!,
|
|
389
|
-
generation: manifest.generation,
|
|
390
|
-
manifestPath,
|
|
391
|
-
requestId: request.id
|
|
392
|
-
}),
|
|
393
|
-
operation
|
|
394
|
-
);
|
|
395
|
-
return finalizationResult(await result);
|
|
282
|
+
if (request.family === 'agent-client') {
|
|
283
|
+
validateAgentClientOperation(request, manifest);
|
|
284
|
+
return executeAgentClientOperation(request, manifest, manifestPath, options.agentClientOperation);
|
|
396
285
|
}
|
|
397
286
|
const boundArgs = bindSandboxControlTask(request, manifest.taskId!);
|
|
398
287
|
let operation: TaskControlOperation;
|
|
@@ -433,7 +322,8 @@ export async function executeRequest(
|
|
|
433
322
|
const fields = requestAuditFields(manifest, request);
|
|
434
323
|
appendExecutorAudit(manifest, 'executor-request-start', fields);
|
|
435
324
|
try {
|
|
436
|
-
|
|
325
|
+
let result: SandboxControlExecutionResult;
|
|
326
|
+
result = await executeRequestInner(manifest, manifestPath, request, options);
|
|
437
327
|
appendExecutorAudit(manifest, 'executor-request-finished', {
|
|
438
328
|
requestId: request.id,
|
|
439
329
|
requestFamily: request.family,
|
|
@@ -500,17 +390,17 @@ export async function runSandboxControlExecutor(requestPath: string, nonce: stri
|
|
|
500
390
|
let result: SandboxControlExecutionResult;
|
|
501
391
|
try {
|
|
502
392
|
assertSandboxControlExecutorAuthority(manifest, gateOwner);
|
|
503
|
-
|
|
393
|
+
const operation = request.family === 'task-lifecycle'
|
|
504
394
|
? (() => {
|
|
505
395
|
try { return parseTaskControlOperation(request.family, request.args); } catch { return null; }
|
|
506
396
|
})()
|
|
507
|
-
: null;
|
|
397
|
+
: request.family === 'agent-client' ? request.operation : null;
|
|
508
398
|
const context = createSandboxControlAuditContext(manifest, {
|
|
509
399
|
requestId: request.id,
|
|
510
400
|
family: request.family,
|
|
511
|
-
operation: operation
|
|
401
|
+
operation: operation && typeof operation === 'object' && 'family' in operation && operation.family === 'task-lifecycle'
|
|
512
402
|
? operation.request.intent
|
|
513
|
-
|
|
403
|
+
: request.family === 'agent-client' ? request.operation : null,
|
|
514
404
|
phase: 'started-committed',
|
|
515
405
|
outcome: 'in-progress'
|
|
516
406
|
});
|
|
@@ -526,20 +416,7 @@ export async function runSandboxControlExecutor(requestPath: string, nonce: stri
|
|
|
526
416
|
});
|
|
527
417
|
const detail = error instanceof Error ? error.message : String(error);
|
|
528
418
|
const code = /^([A-Z][A-Z0-9_]+)/u.exec(detail)?.[1] ?? 'SANDBOX_CONTROL_EXECUTOR_FAILED';
|
|
529
|
-
result = request.family === 'task-
|
|
530
|
-
? {
|
|
531
|
-
exitCode: 1,
|
|
532
|
-
stdout: serializeTaskFinalizationEnvelope({
|
|
533
|
-
status: code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 'unknown' : 'failed',
|
|
534
|
-
changed: code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? null : false,
|
|
535
|
-
accepted: true,
|
|
536
|
-
requestId: request.id,
|
|
537
|
-
result: null,
|
|
538
|
-
error: { code, message: detail, retryable: false }
|
|
539
|
-
}),
|
|
540
|
-
stderr: ''
|
|
541
|
-
}
|
|
542
|
-
: request.family === 'task-create'
|
|
419
|
+
result = request.family === 'task-create'
|
|
543
420
|
? taskCreateExecutionFailure(request.id, code, detail)
|
|
544
421
|
: lifecycleFailure(code, detail);
|
|
545
422
|
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type SandboxResourceLock
|
|
24
24
|
} from './native-file-lock.ts';
|
|
25
25
|
import { isSandboxAuthorityEvidence } from '../engines/authority.ts';
|
|
26
|
+
import { validateSandboxControlIdentity } from './identity-sentinel.ts';
|
|
26
27
|
import {
|
|
27
28
|
parseSandboxControlStatus,
|
|
28
29
|
readExecution,
|
|
@@ -639,6 +640,51 @@ function markSandboxControlRootQuiescing(root: string): void {
|
|
|
639
640
|
}
|
|
640
641
|
}
|
|
641
642
|
|
|
643
|
+
function readTaskCompletionManifest(
|
|
644
|
+
root: string,
|
|
645
|
+
binding: Readonly<{ taskId: string; generation: string; controlRootId: string; statusDir: string }>
|
|
646
|
+
): SandboxControlManifest {
|
|
647
|
+
const manifest = readSandboxControlManifest(path.join(root, 'manifest.json'));
|
|
648
|
+
const identity = validateSandboxControlIdentity({
|
|
649
|
+
publicStatusDir: binding.statusDir,
|
|
650
|
+
root,
|
|
651
|
+
mode: manifest.mode,
|
|
652
|
+
taskId: manifest.taskId,
|
|
653
|
+
generation: manifest.generation,
|
|
654
|
+
controlRootId: manifest.controlRootId
|
|
655
|
+
});
|
|
656
|
+
if (identity.state !== 'valid' || manifest.mode !== 'task-bound'
|
|
657
|
+
|| manifest.taskId !== binding.taskId || manifest.generation !== binding.generation
|
|
658
|
+
|| manifest.controlRootId !== binding.controlRootId) {
|
|
659
|
+
throw new Error('SANDBOX_CONTROL_IDENTITY_MISMATCH');
|
|
660
|
+
}
|
|
661
|
+
return manifest;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export async function quiesceSandboxControlForTaskCompletion(
|
|
665
|
+
root: string,
|
|
666
|
+
binding: Readonly<{ taskId: string; generation: string; controlRootId: string; statusDir: string }>,
|
|
667
|
+
timeoutMs = DEFAULT_QUIESCE_TIMEOUT_MS
|
|
668
|
+
): Promise<void> {
|
|
669
|
+
const resolvedRoot = path.resolve(root);
|
|
670
|
+
const manifest = readTaskCompletionManifest(resolvedRoot, binding);
|
|
671
|
+
markSandboxControlRootQuiescing(resolvedRoot);
|
|
672
|
+
const deadline = Date.now() + timeoutMs;
|
|
673
|
+
const startupPath = path.join(resolvedRoot, BROKER_STARTING_FILE);
|
|
674
|
+
const brokerPath = path.join(resolvedRoot, 'broker.json');
|
|
675
|
+
while (true) {
|
|
676
|
+
const startup = readStartupOwner(startupPath);
|
|
677
|
+
if (fs.existsSync(startupPath) && !startup) throw new Error('SANDBOX_CONTROL_BROKER_START_TRANSITION');
|
|
678
|
+
const broker = readBrokerOwner(brokerPath);
|
|
679
|
+
if (broker && (broker.token !== manifest.token || broker.generation !== manifest.generation)) {
|
|
680
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
681
|
+
}
|
|
682
|
+
if (!startup && !broker) return;
|
|
683
|
+
if (Date.now() >= deadline) throw new Error('SANDBOX_CONTROL_BROKER_DRAIN_TIMEOUT');
|
|
684
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
642
688
|
function recordSandboxRemovalPending(
|
|
643
689
|
root: string,
|
|
644
690
|
manifest: SandboxControlManifest,
|
|
@@ -1108,12 +1154,29 @@ function readSandboxControlManifestValue(manifestPath: string): SandboxControlMa
|
|
|
1108
1154
|
|| (candidate.mode === 'branch-only' && candidate.taskId !== null)
|
|
1109
1155
|
|| !isSandboxAuthorityEvidence(candidate.authorityEvidence)) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
1110
1156
|
const root = path.dirname(path.resolve(manifestPath));
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1157
|
+
const canonicalRoot = fs.realpathSync.native(root);
|
|
1158
|
+
const matchesControlChildPath = (candidatePath: string, name: string): boolean => {
|
|
1159
|
+
const resolved = path.resolve(candidatePath);
|
|
1160
|
+
if (path.basename(resolved) !== name) return false;
|
|
1161
|
+
const expected = path.join(canonicalRoot, name);
|
|
1162
|
+
try {
|
|
1163
|
+
return fs.realpathSync.native(resolved) === expected;
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
1166
|
+
try {
|
|
1167
|
+
return fs.realpathSync.native(path.dirname(resolved)) === canonicalRoot;
|
|
1168
|
+
} catch (parentError) {
|
|
1169
|
+
if ((parentError as NodeJS.ErrnoException).code !== 'ENOENT') throw parentError;
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
if (!matchesControlChildPath(candidate.channelDir, 'channel')
|
|
1175
|
+
|| !matchesControlChildPath(candidate.publicStatusDir, 'public')
|
|
1176
|
+
|| !matchesControlChildPath(candidate.processingDir, 'processing')) {
|
|
1114
1177
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
1115
1178
|
}
|
|
1116
|
-
if (
|
|
1179
|
+
if (!matchesControlChildPath(candidate.runtimeDir, 'runtime')) {
|
|
1117
1180
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
1118
1181
|
}
|
|
1119
1182
|
const normalized: SandboxControlManifest = {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { validateTaskCreateCandidate, type TaskCreateCandidateV1 } from '../../task/create.ts';
|
|
3
|
-
import {
|
|
3
|
+
import { isAgentClientId } from '../../agent-clients/types.ts';
|
|
4
4
|
import type { ProcessIdentity } from '../../server/process-state.ts';
|
|
5
|
-
import type { CodexControllerLeaseProofV1 } from './controller-registration.ts';
|
|
6
5
|
import type { SandboxAuthorityEvidenceV1 } from '../engines/authority.ts';
|
|
7
6
|
import type { SandboxTaskView } from './task-view.ts';
|
|
8
7
|
|
|
@@ -15,7 +14,7 @@ export const SANDBOX_CONTROL_ADMISSION_WINDOW_MS = 2_000;
|
|
|
15
14
|
export const SANDBOX_CONTROL_STATUS_INTERVAL_MS = 250;
|
|
16
15
|
export const SANDBOX_CONTROL_STATUS_STALE_MS = 1_500;
|
|
17
16
|
export const SANDBOX_CONTROL_FUTURE_SKEW_MS = 1_000;
|
|
18
|
-
export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-
|
|
17
|
+
export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-create', 'agent-client'] as const;
|
|
19
18
|
export type SandboxControlTimingPolicy = Readonly<{
|
|
20
19
|
controlTickMs: number;
|
|
21
20
|
parkedBindingInitialMs: number;
|
|
@@ -58,24 +57,20 @@ export type SandboxControlExecutorGateV2 = Readonly<{
|
|
|
58
57
|
}>;
|
|
59
58
|
type RequestBase = Readonly<{
|
|
60
59
|
version: 4; id: string; token: string; generation: string; issuedAt: number; expiresAt: number;
|
|
61
|
-
controllerProcess: ProcessIdentity | null;
|
|
62
|
-
controllerProof: CodexControllerLeaseProofV1 | null;
|
|
63
60
|
}>;
|
|
64
61
|
export type SandboxTaskCommandRequest = RequestBase & Readonly<{
|
|
65
62
|
family: 'task-lifecycle'; args: string[];
|
|
66
63
|
}>;
|
|
67
|
-
export type SandboxTaskFinalizationRequest = RequestBase & Readonly<{
|
|
68
|
-
family: 'task-finalization'; operation: 'complete'; agent: string; args: [];
|
|
69
|
-
}>;
|
|
70
64
|
export type SandboxTaskCreateRequest = RequestBase & Readonly<{
|
|
71
65
|
family: 'task-create'; candidate: TaskCreateCandidateV1;
|
|
72
66
|
}>;
|
|
73
|
-
export type
|
|
74
|
-
family: '
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
export type SandboxAgentClientRequest = RequestBase & Readonly<{
|
|
68
|
+
family: 'agent-client';
|
|
69
|
+
agentClient: string;
|
|
70
|
+
operation: string;
|
|
71
|
+
payload: Record<string, unknown>;
|
|
77
72
|
}>;
|
|
78
|
-
export type SandboxControlRequest = SandboxTaskCommandRequest |
|
|
73
|
+
export type SandboxControlRequest = SandboxTaskCommandRequest | SandboxTaskCreateRequest | SandboxAgentClientRequest;
|
|
79
74
|
export type SandboxControlError = Readonly<{ code: string; message: string; retryable: boolean }>;
|
|
80
75
|
export type SandboxControlResultEvidence = Readonly<{
|
|
81
76
|
version: 1;
|
|
@@ -252,89 +247,50 @@ export function validateSandboxControlRequest(
|
|
|
252
247
|
fail('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit');
|
|
253
248
|
}
|
|
254
249
|
if (request.family === 'task-create') {
|
|
255
|
-
const expected = ['candidate', '
|
|
250
|
+
const expected = ['candidate', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
|
|
256
251
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')) {
|
|
257
252
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
|
|
258
253
|
}
|
|
259
|
-
if (request.controllerProcess !== null || request.controllerProof !== null) {
|
|
260
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-create cannot carry controller authority');
|
|
261
|
-
}
|
|
262
254
|
return { ...request, candidate: validateTaskCreateCandidate(request.candidate) } as SandboxTaskCreateRequest;
|
|
263
255
|
}
|
|
264
|
-
if (request.family === '
|
|
265
|
-
const expected = ['
|
|
266
|
-
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
267
|
-
|| !Array.isArray(request.args) || request.args.length !== 0
|
|
268
|
-
|| !['open', 'close', 'verify'].includes(request.command as string)
|
|
269
|
-
|| !validControllerProcess(request.controllerProcess)
|
|
270
|
-
|| (request.command === 'open' && request.controllerProof !== null)
|
|
271
|
-
|| (request.command !== 'open' && !validControllerProof(request.controllerProof))
|
|
272
|
-
|| (request.command === 'verify'
|
|
273
|
-
&& JSON.stringify(request.controllerProcess) !== JSON.stringify((request.controllerProof as CodexControllerLeaseProofV1).controllerProcess))) {
|
|
274
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller request schema is invalid');
|
|
275
|
-
}
|
|
276
|
-
if (manifest.mode !== 'task-bound' || !manifest.taskId) {
|
|
277
|
-
fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot register a Codex controller');
|
|
278
|
-
}
|
|
279
|
-
return request as SandboxCodexControllerRequest;
|
|
280
|
-
}
|
|
281
|
-
if (request.family === 'task-finalization') {
|
|
282
|
-
const expected = ['agent', 'args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'token', 'version'];
|
|
256
|
+
if (request.family === 'agent-client') {
|
|
257
|
+
const expected = ['agentClient', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'payload', 'token', 'version'];
|
|
283
258
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
284
|
-
|| request.
|
|
285
|
-
||
|
|
286
|
-
|| typeof request.
|
|
287
|
-
|
|
288
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-finalization request schema or authorization is invalid');
|
|
259
|
+
|| !isAgentClientId(request.agentClient)
|
|
260
|
+
|| typeof request.operation !== 'string' || !/^[a-z][a-z0-9.-]{0,63}$/u.test(request.operation)
|
|
261
|
+
|| !request.payload || typeof request.payload !== 'object' || Array.isArray(request.payload)) {
|
|
262
|
+
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'agent-client request schema is invalid');
|
|
289
263
|
}
|
|
290
264
|
if (manifest.mode !== 'task-bound' || !manifest.taskId) {
|
|
291
|
-
fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot
|
|
265
|
+
fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot coordinate agent clients');
|
|
292
266
|
}
|
|
293
|
-
return request as
|
|
267
|
+
return request as SandboxAgentClientRequest;
|
|
294
268
|
}
|
|
295
|
-
const expected = ['args', '
|
|
269
|
+
const expected = ['args', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
|
|
296
270
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
297
271
|
|| !Array.isArray(request.args) || !request.args.every((arg) => typeof arg === 'string')) {
|
|
298
272
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
|
|
299
273
|
}
|
|
274
|
+
if (request.args[1] === 'complete') {
|
|
275
|
+
fail('SANDBOX_CONTROL_COMMAND_DENIED', 'complete must use task-finalization');
|
|
276
|
+
}
|
|
300
277
|
if (manifest.mode !== 'task-bound' || !manifest.taskId) {
|
|
301
278
|
fail(
|
|
302
279
|
'SANDBOX_CONTROL_BRANCH_ONLY',
|
|
303
280
|
"branch-only sandboxes cannot coordinate tasks; return to the host and run 'ai sandbox start --recreate <task-ref-or-correct-branch>'"
|
|
304
281
|
);
|
|
305
282
|
}
|
|
306
|
-
if (request.controllerProcess !== null || request.controllerProof !== null) {
|
|
307
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller authority is invalid');
|
|
308
|
-
}
|
|
309
283
|
return request as SandboxTaskCommandRequest;
|
|
310
284
|
}
|
|
311
285
|
|
|
312
286
|
export function bindSandboxControlTask(request: SandboxControlRequest, taskId: string): string[] {
|
|
313
|
-
if (request.family === 'task-create' || request.family === '
|
|
287
|
+
if (request.family === 'task-create' || request.family === 'agent-client') {
|
|
314
288
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', `${request.family} requests do not bind a current task`);
|
|
315
289
|
}
|
|
316
290
|
if (request.args.length === 0) fail('SANDBOX_CONTROL_REQUEST_INVALID', 'command arguments are required');
|
|
317
291
|
return [taskId, ...request.args.slice(1)];
|
|
318
292
|
}
|
|
319
293
|
|
|
320
|
-
function validControllerProcess(value: unknown): value is ProcessIdentity {
|
|
321
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
322
|
-
const processValue = value as Record<string, unknown>;
|
|
323
|
-
return Object.keys(processValue).sort().join(',') === 'pid,startTime'
|
|
324
|
-
&& Number.isSafeInteger(processValue.pid) && (processValue.pid as number) > 0
|
|
325
|
-
&& Number.isSafeInteger(processValue.startTime) && (processValue.startTime as number) >= 0;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function validControllerProof(value: unknown): value is CodexControllerLeaseProofV1 {
|
|
329
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
330
|
-
const proof = value as Record<string, unknown>;
|
|
331
|
-
return Object.keys(proof).sort().join(',') === 'controllerProcess,leaseId,leaseSecret,version'
|
|
332
|
-
&& proof.version === 1
|
|
333
|
-
&& typeof proof.leaseId === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseId)
|
|
334
|
-
&& typeof proof.leaseSecret === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseSecret)
|
|
335
|
-
&& validControllerProcess(proof.controllerProcess);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
294
|
export function controlError(error: unknown): SandboxControlError {
|
|
339
295
|
if (error instanceof SandboxControlProtocolError) {
|
|
340
296
|
return { code: error.code, message: error.message, retryable: error.retryable };
|