@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.
Files changed (154) hide show
  1. package/dist/bin/internal-cli.js +2 -2
  2. package/dist/lib/agent-clients/adapter.js +7 -1
  3. package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
  9. package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
  10. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
  12. package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
  13. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
  14. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
  15. package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
  16. package/dist/lib/agent-clients/adapters/codex.js +40 -1
  17. package/dist/lib/agent-clients/adapters/index.js +2 -10
  18. package/dist/lib/agent-clients/tokens.js +3 -2
  19. package/dist/lib/internal/agent-client.js +132 -72
  20. package/dist/lib/internal/cli-route-inventory.js +2 -2
  21. package/dist/lib/internal/sandbox-control.js +1 -81
  22. package/dist/lib/internal/task-finalization.js +26 -19
  23. package/dist/lib/internal/task-lifecycle.js +1 -1
  24. package/dist/lib/internal/task-operation-registry.js +1 -1
  25. package/dist/lib/internal/task-validate.js +1 -1
  26. package/dist/lib/platform/github-client.js +33 -3
  27. package/dist/lib/platform/issues.js +18 -6
  28. package/dist/lib/runtime/agent-runtime.js +6 -29
  29. package/dist/lib/sandbox/commands/control-status.js +1 -0
  30. package/dist/lib/sandbox/commands/create.js +7 -2
  31. package/dist/lib/sandbox/commands/ls.js +4 -5
  32. package/dist/lib/sandbox/commands/rm.js +1 -1
  33. package/dist/lib/sandbox/control/agent-operation.js +34 -0
  34. package/dist/lib/sandbox/control/client.js +14 -201
  35. package/dist/lib/sandbox/control/executor.js +12 -125
  36. package/dist/lib/sandbox/control/lifecycle.js +66 -4
  37. package/dist/lib/sandbox/control/protocol.js +15 -54
  38. package/dist/lib/sandbox/control/server.js +216 -281
  39. package/dist/lib/sandbox/control/task-view.js +0 -13
  40. package/dist/lib/sandbox/mounts.js +9 -1
  41. package/dist/lib/sandbox/recovery.js +31 -10
  42. package/dist/lib/sandbox/removal.js +28 -5
  43. package/dist/lib/sandbox/workspace-identity.js +18 -6
  44. package/dist/lib/sandbox/workspace-view.js +56 -19
  45. package/dist/lib/task/commands/ls.js +3 -3
  46. package/dist/lib/task/control-authority.js +56 -264
  47. package/dist/lib/task/control-recovery.js +2 -3
  48. package/dist/lib/task/create.js +2 -2
  49. package/dist/lib/task/delegation-receipts.js +74 -161
  50. package/dist/lib/task/events.js +88 -44
  51. package/dist/lib/task/finalization.js +35 -6
  52. package/dist/lib/task/lifecycle.js +274 -115
  53. package/dist/lib/task/orchestration.js +201 -194
  54. package/dist/lib/task/runtime-paths.js +10 -0
  55. package/dist/lib/task/short-id.js +37 -27
  56. package/dist/lib/task/verification-engine.js +32 -26
  57. package/dist/lib/task/workspace-snapshot.js +1 -1
  58. package/dist/lifecycle-build-manifest.json +3 -3
  59. package/lib/agent-clients/adapter.ts +39 -0
  60. package/lib/agent-clients/adapters/claude-code.ts +5 -1
  61. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
  62. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
  63. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
  64. package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
  65. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
  66. package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
  67. package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
  68. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
  69. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
  70. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
  71. package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
  72. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
  73. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
  74. package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
  75. package/lib/agent-clients/adapters/codex.ts +44 -1
  76. package/lib/agent-clients/adapters/index.ts +7 -20
  77. package/lib/agent-clients/tokens.ts +4 -3
  78. package/lib/internal/agent-client.ts +142 -73
  79. package/lib/internal/cli-route-inventory.ts +2 -2
  80. package/lib/internal/sandbox-control.ts +0 -97
  81. package/lib/internal/task-finalization.ts +30 -18
  82. package/lib/internal/task-lifecycle.ts +1 -1
  83. package/lib/internal/task-operation-registry.ts +1 -1
  84. package/lib/internal/task-validate.ts +1 -1
  85. package/lib/platform/github-client.ts +34 -3
  86. package/lib/platform/issues.ts +18 -6
  87. package/lib/runtime/agent-runtime.ts +6 -41
  88. package/lib/sandbox/commands/control-status.ts +1 -0
  89. package/lib/sandbox/commands/create.ts +7 -1
  90. package/lib/sandbox/commands/ls.ts +4 -5
  91. package/lib/sandbox/commands/rm.ts +1 -1
  92. package/lib/sandbox/control/agent-operation.ts +49 -0
  93. package/lib/sandbox/control/client.ts +20 -254
  94. package/lib/sandbox/control/executor.ts +13 -136
  95. package/lib/sandbox/control/lifecycle.ts +67 -4
  96. package/lib/sandbox/control/protocol.ts +22 -66
  97. package/lib/sandbox/control/server.ts +21 -95
  98. package/lib/sandbox/control/task-view.ts +0 -19
  99. package/lib/sandbox/mounts.ts +10 -1
  100. package/lib/sandbox/recovery.ts +38 -17
  101. package/lib/sandbox/removal.ts +33 -7
  102. package/lib/sandbox/workspace-identity.ts +16 -6
  103. package/lib/sandbox/workspace-view.ts +50 -18
  104. package/lib/task/commands/ls.ts +3 -3
  105. package/lib/task/control-authority.ts +66 -378
  106. package/lib/task/control-recovery.ts +3 -4
  107. package/lib/task/create.ts +3 -3
  108. package/lib/task/delegation-receipts.ts +87 -231
  109. package/lib/task/events.ts +100 -42
  110. package/lib/task/finalization.ts +37 -7
  111. package/lib/task/lifecycle.ts +257 -109
  112. package/lib/task/orchestration.ts +260 -237
  113. package/lib/task/runtime-paths.ts +12 -0
  114. package/lib/task/short-id.ts +33 -40
  115. package/lib/task/verification-engine.ts +30 -25
  116. package/lib/task/workspace-snapshot.ts +1 -1
  117. package/package.json +1 -1
  118. package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
  119. package/templates/.agents/rules/README.en.md +2 -1
  120. package/templates/.agents/rules/README.zh-CN.md +2 -1
  121. package/templates/.agents/rules/next-step-output.en.md +6 -19
  122. package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
  123. package/templates/.agents/rules/strategy-pattern.en.md +25 -0
  124. package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
  125. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  126. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
  127. package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
  128. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
  129. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  130. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  131. package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
  132. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
  133. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
  134. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
  135. package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
  136. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
  137. package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
  138. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
  139. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  140. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  141. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  142. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  143. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  144. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  145. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  146. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  147. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  148. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  149. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
  150. package/dist/lib/internal/codex-lifecycle.js +0 -385
  151. package/dist/lib/task/codex-orchestration.js +0 -206
  152. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
  153. package/lib/internal/codex-lifecycle.ts +0 -390
  154. 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 { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.ts';
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 === 'codex-controller') {
325
- try {
326
- if (request.command === 'verify') {
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
- const result = await executeRequestInner(manifest, manifestPath, request, options);
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
- const operation = request.family === 'task-lifecycle'
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?.family === 'task-lifecycle'
401
+ operation: operation && typeof operation === 'object' && 'family' in operation && operation.family === 'task-lifecycle'
512
402
  ? operation.request.intent
513
- : request.family === 'task-finalization' ? request.operation : null,
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-finalization'
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
- if (path.resolve(candidate.channelDir) !== path.join(root, 'channel')
1112
- || path.resolve(candidate.publicStatusDir) !== path.join(root, 'public')
1113
- || path.resolve(candidate.processingDir) !== path.join(root, 'processing')) {
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 (path.resolve(candidate.runtimeDir) !== path.join(root, 'runtime')) {
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 { normalizeAgentToken } from '../../agent-clients/tokens.ts';
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-finalization', 'task-create', 'codex-controller'] as const;
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 SandboxCodexControllerRequest = RequestBase & Readonly<{
74
- family: 'codex-controller';
75
- command: 'open' | 'close' | 'verify';
76
- args: [];
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 | SandboxTaskFinalizationRequest | SandboxTaskCreateRequest | SandboxCodexControllerRequest;
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', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
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 === 'codex-controller') {
265
- const expected = ['args', 'command', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
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.operation !== 'complete'
285
- || !Array.isArray(request.args) || request.args.length !== 0
286
- || typeof request.agent !== 'string' || normalizeAgentToken(request.agent) !== request.agent
287
- || request.controllerProcess !== null || request.controllerProof !== null) {
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 finalize tasks');
265
+ fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot coordinate agent clients');
292
266
  }
293
- return request as SandboxTaskFinalizationRequest;
267
+ return request as SandboxAgentClientRequest;
294
268
  }
295
- const expected = ['args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
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 === 'codex-controller' || request.family === 'task-finalization') {
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 };