@fitlab-ai/agent-infra 0.11.11 → 0.11.12

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 (124) 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 +222 -210
  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/task-operation-registry.js +1 -1
  22. package/dist/lib/internal/task-validate.js +1 -1
  23. package/dist/lib/platform/issues.js +18 -6
  24. package/dist/lib/runtime/agent-runtime.js +6 -29
  25. package/dist/lib/sandbox/commands/control-status.js +1 -0
  26. package/dist/lib/sandbox/commands/create.js +1 -0
  27. package/dist/lib/sandbox/control/agent-operation.js +34 -0
  28. package/dist/lib/sandbox/control/client.js +23 -150
  29. package/dist/lib/sandbox/control/executor.js +9 -76
  30. package/dist/lib/sandbox/control/protocol.js +14 -42
  31. package/dist/lib/sandbox/control/server.js +15 -49
  32. package/dist/lib/sandbox/mounts.js +2 -1
  33. package/dist/lib/sandbox/recovery.js +3 -1
  34. package/dist/lib/sandbox/removal.js +9 -5
  35. package/dist/lib/sandbox/workspace-view.js +8 -17
  36. package/dist/lib/task/control-authority.js +54 -264
  37. package/dist/lib/task/control-recovery.js +1 -1
  38. package/dist/lib/task/create.js +2 -2
  39. package/dist/lib/task/delegation-receipts.js +74 -161
  40. package/dist/lib/task/events.js +88 -44
  41. package/dist/lib/task/finalization.js +21 -6
  42. package/dist/lib/task/lifecycle.js +84 -97
  43. package/dist/lib/task/orchestration.js +182 -129
  44. package/dist/lib/task/runtime-paths.js +10 -0
  45. package/dist/lib/task/verification-engine.js +28 -15
  46. package/dist/lib/task/workspace-snapshot.js +1 -1
  47. package/dist/lifecycle-build-manifest.json +3 -3
  48. package/lib/agent-clients/adapter.ts +39 -0
  49. package/lib/agent-clients/adapters/claude-code.ts +5 -1
  50. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
  51. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
  52. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
  53. package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
  54. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
  55. package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
  56. package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
  57. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
  58. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
  59. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
  60. package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
  61. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
  62. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +272 -223
  63. package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
  64. package/lib/agent-clients/adapters/codex.ts +44 -1
  65. package/lib/agent-clients/adapters/index.ts +7 -20
  66. package/lib/agent-clients/tokens.ts +4 -3
  67. package/lib/internal/agent-client.ts +142 -73
  68. package/lib/internal/cli-route-inventory.ts +2 -2
  69. package/lib/internal/task-operation-registry.ts +1 -1
  70. package/lib/internal/task-validate.ts +1 -1
  71. package/lib/platform/issues.ts +18 -6
  72. package/lib/runtime/agent-runtime.ts +6 -41
  73. package/lib/sandbox/commands/control-status.ts +1 -0
  74. package/lib/sandbox/commands/create.ts +1 -0
  75. package/lib/sandbox/control/agent-operation.ts +49 -0
  76. package/lib/sandbox/control/client.ts +35 -196
  77. package/lib/sandbox/control/executor.ts +12 -84
  78. package/lib/sandbox/control/protocol.ts +21 -51
  79. package/lib/sandbox/control/server.ts +12 -51
  80. package/lib/sandbox/mounts.ts +2 -1
  81. package/lib/sandbox/recovery.ts +3 -1
  82. package/lib/sandbox/removal.ts +12 -5
  83. package/lib/sandbox/workspace-view.ts +9 -16
  84. package/lib/task/control-authority.ts +65 -378
  85. package/lib/task/control-recovery.ts +2 -2
  86. package/lib/task/create.ts +3 -3
  87. package/lib/task/delegation-receipts.ts +87 -231
  88. package/lib/task/events.ts +100 -42
  89. package/lib/task/finalization.ts +23 -7
  90. package/lib/task/lifecycle.ts +82 -87
  91. package/lib/task/orchestration.ts +237 -176
  92. package/lib/task/runtime-paths.ts +12 -0
  93. package/lib/task/verification-engine.ts +27 -16
  94. package/lib/task/workspace-snapshot.ts +1 -1
  95. package/package.json +1 -1
  96. package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
  97. package/templates/.agents/rules/README.en.md +1 -1
  98. package/templates/.agents/rules/README.zh-CN.md +1 -1
  99. package/templates/.agents/rules/next-step-output.en.md +6 -19
  100. package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
  101. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  102. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
  103. package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
  104. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
  105. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  106. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  107. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
  108. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
  109. package/templates/.agents/skills/complete-task/SKILL.en.md +0 -3
  110. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +0 -3
  111. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  112. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  113. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  114. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  115. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  116. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  117. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  118. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  119. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
  120. package/dist/lib/internal/codex-lifecycle.js +0 -385
  121. package/dist/lib/task/codex-orchestration.js +0 -206
  122. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
  123. package/lib/internal/codex-lifecycle.ts +0 -390
  124. package/lib/task/codex-orchestration.ts +0 -276
@@ -12,8 +12,7 @@ import { requestAuditFields, resultAuditFields, appendDiagnosticAudit, createSan
12
12
  import { validateSandboxControlIdentity } from './identity-sentinel.js';
13
13
  import { createSandboxExecutorExecutionContext, dispatchTaskControlOperation, parseTaskControlOperation } from '../../task/control-authority.js';
14
14
  import { assertSandboxControlBrokerOwner, readSandboxControlManifest } from './lifecycle.js';
15
- import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.js';
16
- import { closeCodexControllerRegistration, CodexControllerRegistrationError, openCodexControllerRegistration, resolveCodexControllerBinding } from './controller-registration.js';
15
+ import { executeAgentClientOperation, validateAgentClientOperation } from './agent-operation.js';
17
16
  function sameBrokerOwner(left, right) {
18
17
  return left.version === right.version
19
18
  && left.pid === right.pid
@@ -169,23 +168,6 @@ function waitForGate(nonce, timeoutMs = 2_000) {
169
168
  });
170
169
  });
171
170
  }
172
- function controllerFailure(error) {
173
- const code = error instanceof CodexControllerRegistrationError
174
- ? error.code
175
- : /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
176
- ?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
177
- const message = error instanceof CodexControllerRegistrationError
178
- ? error.message
179
- : `${code}: controller operation failed; inspect the sandbox controller and rebuild the sandbox if needed`;
180
- const payload = {
181
- version: 1,
182
- status: 'failed',
183
- changed: false,
184
- lease: null,
185
- error: { code, message, retryable: false }
186
- };
187
- return { exitCode: 1, stdout: `${JSON.stringify(payload)}\n`, stderr: '' };
188
- }
189
171
  function lifecycleFailure(code, message) {
190
172
  return {
191
173
  exitCode: 1,
@@ -241,59 +223,9 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
241
223
  return taskCreateExecutionFailure(request.id, code, detail);
242
224
  }
243
225
  }
244
- if (request.family === 'codex-controller') {
245
- try {
246
- if (request.command === 'verify') {
247
- const binding = (options.resolveControllerBinding ?? resolveCodexControllerBinding)({
248
- manifest,
249
- manifestPath,
250
- proof: request.controllerProof,
251
- buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
252
- });
253
- return {
254
- exitCode: 0,
255
- stdout: `${JSON.stringify({
256
- version: 1,
257
- status: 'verified',
258
- changed: false,
259
- lease: null,
260
- binding: {
261
- taskId: manifest.taskId,
262
- controlGeneration: binding.controlGeneration,
263
- controllerInstanceDigest: binding.instanceDigest
264
- },
265
- error: null
266
- })}\n`,
267
- stderr: ''
268
- };
269
- }
270
- const result = request.command === 'open'
271
- ? openCodexControllerRegistration({
272
- manifest,
273
- manifestPath,
274
- controllerProcess: request.controllerProcess,
275
- buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
276
- })
277
- : closeCodexControllerRegistration({
278
- manifest,
279
- manifestPath,
280
- proof: request.controllerProof
281
- });
282
- return { exitCode: 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
283
- }
284
- catch (error) {
285
- const errorCode = error instanceof CodexControllerRegistrationError
286
- ? error.code
287
- : /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
288
- ?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
289
- appendExecutorAudit(manifest, 'controller-operation-failed', {
290
- ...requestAuditFields(manifest, request),
291
- controllerCommand: request.command,
292
- errorCode,
293
- errorType: error instanceof Error ? error.name : typeof error,
294
- });
295
- return controllerFailure(error);
296
- }
226
+ if (request.family === 'agent-client') {
227
+ validateAgentClientOperation(request, manifest);
228
+ return executeAgentClientOperation(request, manifest, manifestPath, options.agentClientOperation);
297
229
  }
298
230
  if (request.family === 'task-finalization') {
299
231
  const parsed = parseTaskControlOperation('task-finalization', [manifest.taskId, 'complete', '--agent', request.agent]);
@@ -346,7 +278,8 @@ export async function executeRequest(manifest, manifestPath, request, options =
346
278
  const fields = requestAuditFields(manifest, request);
347
279
  appendExecutorAudit(manifest, 'executor-request-start', fields);
348
280
  try {
349
- const result = await executeRequestInner(manifest, manifestPath, request, options);
281
+ let result;
282
+ result = await executeRequestInner(manifest, manifestPath, request, options);
350
283
  appendExecutorAudit(manifest, 'executor-request-finished', {
351
284
  requestId: request.id,
352
285
  requestFamily: request.family,
@@ -424,13 +357,13 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
424
357
  return null;
425
358
  }
426
359
  })()
427
- : null;
360
+ : request.family === 'agent-client' ? request.operation : null;
428
361
  const context = createSandboxControlAuditContext(manifest, {
429
362
  requestId: request.id,
430
363
  family: request.family,
431
- operation: operation?.family === 'task-lifecycle'
364
+ operation: operation && typeof operation === 'object' && 'family' in operation && operation.family === 'task-lifecycle'
432
365
  ? operation.request.intent
433
- : request.family === 'task-finalization' ? request.operation : null,
366
+ : request.family === 'task-finalization' || request.family === 'agent-client' ? request.operation : null,
434
367
  phase: 'started-committed',
435
368
  outcome: 'in-progress'
436
369
  });
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { validateTaskCreateCandidate } from '../../task/create.js';
3
3
  import { normalizeAgentToken } from '../../agent-clients/tokens.js';
4
+ import { isAgentClientId } from '../../agent-clients/types.js';
4
5
  export const SANDBOX_CONTROL_MAX_BYTES = 64 * 1024;
5
6
  export const SANDBOX_CONTROL_MAX_LOGICAL_RECORDS = 1024;
6
7
  export const SANDBOX_CONTROL_MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
@@ -10,7 +11,7 @@ export const SANDBOX_CONTROL_ADMISSION_WINDOW_MS = 2_000;
10
11
  export const SANDBOX_CONTROL_STATUS_INTERVAL_MS = 250;
11
12
  export const SANDBOX_CONTROL_STATUS_STALE_MS = 1_500;
12
13
  export const SANDBOX_CONTROL_FUTURE_SKEW_MS = 1_000;
13
- export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-finalization', 'task-create', 'codex-controller'];
14
+ export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-finalization', 'task-create', 'agent-client'];
14
15
  export const DEFAULT_SANDBOX_CONTROL_TIMING = Object.freeze({
15
16
  controlTickMs: 250,
16
17
  parkedBindingInitialMs: 1_000,
@@ -131,39 +132,31 @@ export function validateSandboxControlRequest(value, manifest, options = {}) {
131
132
  fail('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit');
132
133
  }
133
134
  if (request.family === 'task-create') {
134
- const expected = ['candidate', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
135
+ const expected = ['candidate', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
135
136
  if (Object.keys(request).sort().join(',') !== expected.sort().join(',')) {
136
137
  fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
137
138
  }
138
- if (request.controllerProcess !== null || request.controllerProof !== null) {
139
- fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-create cannot carry controller authority');
140
- }
141
139
  return { ...request, candidate: validateTaskCreateCandidate(request.candidate) };
142
140
  }
143
- if (request.family === 'codex-controller') {
144
- const expected = ['args', 'command', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
141
+ if (request.family === 'agent-client') {
142
+ const expected = ['agentClient', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'payload', 'token', 'version'];
145
143
  if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
146
- || !Array.isArray(request.args) || request.args.length !== 0
147
- || !['open', 'close', 'verify'].includes(request.command)
148
- || !validControllerProcess(request.controllerProcess)
149
- || (request.command === 'open' && request.controllerProof !== null)
150
- || (request.command !== 'open' && !validControllerProof(request.controllerProof))
151
- || (request.command === 'verify'
152
- && JSON.stringify(request.controllerProcess) !== JSON.stringify(request.controllerProof.controllerProcess))) {
153
- fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller request schema is invalid');
144
+ || !isAgentClientId(request.agentClient)
145
+ || typeof request.operation !== 'string' || !/^[a-z][a-z0-9.-]{0,63}$/u.test(request.operation)
146
+ || !request.payload || typeof request.payload !== 'object' || Array.isArray(request.payload)) {
147
+ fail('SANDBOX_CONTROL_REQUEST_INVALID', 'agent-client request schema is invalid');
154
148
  }
155
149
  if (manifest.mode !== 'task-bound' || !manifest.taskId) {
156
- fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot register a Codex controller');
150
+ fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot coordinate agent clients');
157
151
  }
158
152
  return request;
159
153
  }
160
154
  if (request.family === 'task-finalization') {
161
- const expected = ['agent', 'args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'token', 'version'];
155
+ const expected = ['agent', 'args', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'token', 'version'];
162
156
  if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
163
157
  || request.operation !== 'complete'
164
158
  || !Array.isArray(request.args) || request.args.length !== 0
165
- || typeof request.agent !== 'string' || normalizeAgentToken(request.agent) !== request.agent
166
- || request.controllerProcess !== null || request.controllerProof !== null) {
159
+ || typeof request.agent !== 'string' || normalizeAgentToken(request.agent) !== request.agent) {
167
160
  fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-finalization request schema or authorization is invalid');
168
161
  }
169
162
  if (manifest.mode !== 'task-bound' || !manifest.taskId) {
@@ -171,7 +164,7 @@ export function validateSandboxControlRequest(value, manifest, options = {}) {
171
164
  }
172
165
  return request;
173
166
  }
174
- const expected = ['args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
167
+ const expected = ['args', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
175
168
  if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
176
169
  || !Array.isArray(request.args) || !request.args.every((arg) => typeof arg === 'string')) {
177
170
  fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
@@ -179,37 +172,16 @@ export function validateSandboxControlRequest(value, manifest, options = {}) {
179
172
  if (manifest.mode !== 'task-bound' || !manifest.taskId) {
180
173
  fail('SANDBOX_CONTROL_BRANCH_ONLY', "branch-only sandboxes cannot coordinate tasks; return to the host and run 'ai sandbox start --recreate <task-ref-or-correct-branch>'");
181
174
  }
182
- if (request.controllerProcess !== null || request.controllerProof !== null) {
183
- fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller authority is invalid');
184
- }
185
175
  return request;
186
176
  }
187
177
  export function bindSandboxControlTask(request, taskId) {
188
- if (request.family === 'task-create' || request.family === 'codex-controller' || request.family === 'task-finalization') {
178
+ if (request.family === 'task-create' || request.family === 'agent-client' || request.family === 'task-finalization') {
189
179
  fail('SANDBOX_CONTROL_REQUEST_INVALID', `${request.family} requests do not bind a current task`);
190
180
  }
191
181
  if (request.args.length === 0)
192
182
  fail('SANDBOX_CONTROL_REQUEST_INVALID', 'command arguments are required');
193
183
  return [taskId, ...request.args.slice(1)];
194
184
  }
195
- function validControllerProcess(value) {
196
- if (!value || typeof value !== 'object' || Array.isArray(value))
197
- return false;
198
- const processValue = value;
199
- return Object.keys(processValue).sort().join(',') === 'pid,startTime'
200
- && Number.isSafeInteger(processValue.pid) && processValue.pid > 0
201
- && Number.isSafeInteger(processValue.startTime) && processValue.startTime >= 0;
202
- }
203
- function validControllerProof(value) {
204
- if (!value || typeof value !== 'object' || Array.isArray(value))
205
- return false;
206
- const proof = value;
207
- return Object.keys(proof).sort().join(',') === 'controllerProcess,leaseId,leaseSecret,version'
208
- && proof.version === 1
209
- && typeof proof.leaseId === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseId)
210
- && typeof proof.leaseSecret === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseSecret)
211
- && validControllerProcess(proof.controllerProcess);
212
- }
213
185
  export function controlError(error) {
214
186
  if (error instanceof SandboxControlProtocolError) {
215
187
  return { code: error.code, message: error.message, retryable: error.retryable };
@@ -18,7 +18,7 @@ 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 { readCodexControllerRegistration } from './controller-registration.js';
21
+ import { recoverAgentClientOperation, validateAgentClientOperation } from './agent-operation.js';
22
22
  import { validateSandboxControlIdentity } from './identity-sentinel.js';
23
23
  import { mergeSandboxTaskView, taskViewAfterFinalization, taskViewForManifest } from './task-view.js';
24
24
  import { taskCreateOutputUnavailableResult } from '../../task/create-service.js';
@@ -57,8 +57,8 @@ function operationKey(request) {
57
57
  return request.operation;
58
58
  if (request.family === 'task-create')
59
59
  return 'create';
60
- if (request.family === 'codex-controller')
61
- return request.command;
60
+ if (request.family === 'agent-client')
61
+ return request.operation;
62
62
  if (request.family !== 'task-lifecycle')
63
63
  return null;
64
64
  try {
@@ -217,43 +217,6 @@ function taskCreateDomainEvidence(manifest, output) {
217
217
  return { consistent: false };
218
218
  }
219
219
  }
220
- function controllerDomainEvidence(manifest, manifestPath, request, output) {
221
- if (!output || output.error !== null || typeof output.status !== 'string')
222
- return { consistent: false };
223
- try {
224
- const registration = readCodexControllerRegistration(manifestPath);
225
- if (request.family !== 'codex-controller')
226
- return { consistent: false };
227
- if (request.command === 'open') {
228
- const lease = output.lease && typeof output.lease === 'object' && !Array.isArray(output.lease)
229
- ? output.lease : null;
230
- return {
231
- consistent: output.status === 'opened' && output.changed === true
232
- && lease?.taskId === registration.taskId
233
- && lease.controlGeneration === registration.controlGeneration
234
- && lease.controllerInstanceDigest === registration.controllerInstanceDigest
235
- && registration.taskId === manifest.taskId
236
- && registration.controlGeneration === manifest.generation
237
- };
238
- }
239
- if (request.command === 'verify') {
240
- const binding = output.binding && typeof output.binding === 'object' && !Array.isArray(output.binding)
241
- ? output.binding : null;
242
- return {
243
- consistent: output.status === 'verified' && output.changed === false
244
- && binding?.taskId === registration.taskId
245
- && binding.controlGeneration === registration.controlGeneration
246
- && binding.controllerInstanceDigest === registration.controllerInstanceDigest
247
- };
248
- }
249
- return { consistent: false };
250
- }
251
- catch (error) {
252
- return { consistent: request.family === 'codex-controller' && request.command === 'close'
253
- && error.code === 'CODEX_SANDBOX_CONTROLLER_REGISTRATION_MISSING'
254
- && output?.status === 'closed' && typeof output.changed === 'boolean' };
255
- }
256
- }
257
220
  async function readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payloadOutput) {
258
221
  if (!operation)
259
222
  return { domain: null };
@@ -261,7 +224,7 @@ async function readRecoveryDomain(manifest, manifestPath, request, operation, te
261
224
  ? manifest.taskId
262
225
  : 'args' in request ? request.args[0] ?? null : manifest.taskId;
263
226
  const output = parseControlOutput(payloadOutput);
264
- if (!taskRef && operation.family !== 'task-create' && operation.family !== 'codex-controller') {
227
+ if (!taskRef && operation.family !== 'task-create' && operation.family !== 'agent-client') {
265
228
  return { domain: null };
266
229
  }
267
230
  if (operation.family === 'task-lifecycle') {
@@ -289,8 +252,13 @@ async function readRecoveryDomain(manifest, manifestPath, request, operation, te
289
252
  if (operation.family === 'task-create') {
290
253
  return { domain: taskCreateDomainEvidence(manifest, output) };
291
254
  }
292
- if (operation.family === 'codex-controller') {
293
- return { domain: controllerDomainEvidence(manifest, manifestPath, request, output) };
255
+ if (operation.family === 'agent-client' && request.family === 'agent-client') {
256
+ return { domain: recoverAgentClientOperation({
257
+ request,
258
+ manifest,
259
+ manifestPath,
260
+ stdout: payloadOutput
261
+ }) };
294
262
  }
295
263
  return { domain: null };
296
264
  }
@@ -509,6 +477,8 @@ async function recoverProcessing(manifest, manifestPath, broker, brokerOwns) {
509
477
  request = validateSandboxControlRequest(rawRequest, manifest, {
510
478
  now: typeof rawRequest.issuedAt === 'number' ? rawRequest.issuedAt : undefined
511
479
  });
480
+ if (request.family === 'agent-client')
481
+ validateAgentClientOperation(request, manifest);
512
482
  }
513
483
  catch {
514
484
  // Missing or malformed request evidence remains fail-closed below.
@@ -982,6 +952,8 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
982
952
  if (active)
983
953
  throw new Error('SANDBOX_CONTROL_BUSY');
984
954
  const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
955
+ if (request.family === 'agent-client')
956
+ validateAgentClientOperation(request, manifest);
985
957
  validatedRequest = request;
986
958
  criticalRequestPhase(manifest, request, 'validated', 'in-progress');
987
959
  appendBrokerAudit(manifest, 'request-validated', {
@@ -1075,12 +1047,6 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
1075
1047
  retiring = true;
1076
1048
  continue;
1077
1049
  }
1078
- if (!brokerOwns()) {
1079
- prepared.terminate(false);
1080
- active = null;
1081
- retiring = true;
1082
- continue;
1083
- }
1084
1050
  criticalRequestPhase(manifest, request, 'accepted-authorized', 'in-progress');
1085
1051
  writeAcceptedResponse(manifest, {
1086
1052
  version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
@@ -38,9 +38,10 @@ export function sandboxCoreBindMounts(config, branch, overrides) {
38
38
  ...workspaceMounts,
39
39
  ];
40
40
  if (taskBound) {
41
+ const taskMountPath = path.posix.join('/workspace/.agents/workspace/active', overrides.taskId);
41
42
  mounts.push({
42
43
  hostPaths: taskSources,
43
- containerPath: path.posix.join('/workspace/.agents/workspace/active', overrides.taskId),
44
+ containerPath: taskMountPath,
44
45
  readOnly: false
45
46
  });
46
47
  }
@@ -174,6 +174,7 @@ export async function startSandboxControlBroker(repoRoot, manifestPath) {
174
174
  async function ensureSandboxControlBroker(params) {
175
175
  const control = sandboxControlPaths({
176
176
  base: params.config.controlBase ?? path.join(params.config.home, '.agent-infra', 'sandbox-control'),
177
+ repoRoot: params.config.repoRoot,
177
178
  project: params.config.project,
178
179
  container: params.container,
179
180
  identity: params.workspace
@@ -509,6 +510,7 @@ function expectedMounts(params) {
509
510
  });
510
511
  const control = sandboxControlPaths({
511
512
  base: config.controlBase ?? path.join(config.home, '.agent-infra', 'sandbox-control'),
513
+ repoRoot: config.repoRoot,
512
514
  project: config.project,
513
515
  container: params.container,
514
516
  identity: params.workspace
@@ -905,7 +907,7 @@ export async function ensureSandboxReady(params) {
905
907
  let completedReentry = null;
906
908
  if (params.reentry === 'completed' && params.workspace?.mode === 'task-bound') {
907
909
  const control = sandboxControlPaths({
908
- base: params.config.controlBase, project: params.config.project,
910
+ base: params.config.controlBase, repoRoot: params.config.repoRoot, project: params.config.project,
909
911
  container: params.row.name, identity: params.workspace
910
912
  });
911
913
  const manifest = readSandboxControlManifest(control.manifestPath);
@@ -568,7 +568,7 @@ function resolveRmTarget(config, tools, cleanupTarget, options = {}) {
568
568
  ? matchedContainers
569
569
  : [...new Set([...containerNameCandidates(config, effectiveBranch), ...matchedContainers])];
570
570
  const controlRoots = containers.flatMap((container) => identities.map((identity) => sandboxControlPaths({
571
- base: config.controlBase, project: config.project, container, identity
571
+ base: config.controlBase, repoRoot: config.repoRoot, project: config.project, container, identity
572
572
  }).root));
573
573
  const workspaceViewRoots = containers.flatMap((container) => identities.map((identity) => sandboxWorkspaceViewPaths({
574
574
  base: config.workspaceViewBase, project: config.project, container, identity
@@ -711,7 +711,9 @@ async function removeUncheckedSandbox(config, target, options) {
711
711
  }
712
712
  for (const root of controlRoots) {
713
713
  fs.rmSync(root, { recursive: true, force: true });
714
- removeEmptyManagedParent(path.join(config.controlBase, config.project), root);
714
+ if (target.workspace.mode !== 'task-bound') {
715
+ removeEmptyManagedParent(path.join(config.controlBase, config.project), root);
716
+ }
715
717
  }
716
718
  for (const root of workspaceViewRoots) {
717
719
  fs.rmSync(root, { recursive: true, force: true });
@@ -735,13 +737,15 @@ async function removeUncheckedSandbox(config, target, options) {
735
737
  assertBranchRemovalIdentity(config, effectiveBranch, worktreePermits);
736
738
  runSafe('git', ['-C', config.repoRoot, 'branch', '-D', effectiveBranch]);
737
739
  }
738
- if (target.workspace.mode === 'task-bound') {
739
- releaseStaleShortIdRegistry(config.repoRoot, target.workspace.taskId);
740
- }
740
+ releaseTaskBoundSandboxIdentity(config.repoRoot, target.workspace);
741
741
  if (!options.quiet)
742
742
  p.outro('Sandbox removed');
743
743
  return null;
744
744
  }
745
+ function releaseTaskBoundSandboxIdentity(repoRoot, workspace) {
746
+ if (workspace.mode === 'task-bound')
747
+ releaseStaleShortIdRegistry(repoRoot, workspace.taskId);
748
+ }
745
749
  async function rmOneCore(config, tools, branch, options = {}) {
746
750
  const target = options.target ?? resolveRmTarget(config, tools, options.cleanupTarget ?? resolveSandboxCleanupTarget(branch, config.repoRoot));
747
751
  return removeUncheckedSandbox(config, target, options);
@@ -2,6 +2,7 @@ import { createHash, randomBytes } from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { resolveTaskWorkspace } from './task-resolver.js';
5
+ import { resolveTaskRuntimeRoot } from '../task/runtime-paths.js';
5
6
  import { createSandboxControlIdentitySentinel, writeSandboxControlIdentitySentinel } from './control/identity-sentinel.js';
6
7
  export const SANDBOX_WORKSPACE_VIEW_STATES = Object.freeze([
7
8
  'active',
@@ -25,11 +26,12 @@ export function sandboxWorkspaceViewPaths(params) {
25
26
  return { root };
26
27
  }
27
28
  export function sandboxControlPaths(params) {
28
- const identityKey = params.identity.mode === 'task-bound'
29
- ? `task-bound:${params.identity.taskId}`
30
- : 'branch-only';
31
- const digest = createHash('sha256').update(identityKey).digest('hex').slice(0, 16);
32
- const root = path.resolve(params.base, params.project, params.container, digest);
29
+ const root = params.identity.mode === 'task-bound'
30
+ ? path.join(resolveTaskRuntimeRoot(params.identity.taskId, { repoRoot: params.repoRoot }), 'sandbox-control')
31
+ : (() => {
32
+ const digest = createHash('sha256').update('branch-only').digest('hex').slice(0, 16);
33
+ return path.resolve(params.base, params.project, params.container, digest);
34
+ })();
33
35
  return {
34
36
  root,
35
37
  channelDir: path.join(root, 'channel'),
@@ -155,7 +157,7 @@ export function materializeSandboxWorkspaceView(params) {
155
157
  }
156
158
  export function materializeSandboxControl(params) {
157
159
  const { root, channelDir, statusDir, processingDir, runtimeDir, manifestPath } = sandboxControlPaths(params);
158
- assertSafeDirectory(root, path.resolve(params.base));
160
+ assertSafeDirectory(root, params.identity.mode === 'task-bound' ? path.dirname(root) : path.resolve(params.base));
159
161
  if (fs.existsSync(root)) {
160
162
  if (!params.replacementLease || path.resolve(params.replacementLease.root) !== root) {
161
163
  throw new Error('SANDBOX_CONTROL_REPLACEMENT_REQUIRED');
@@ -168,18 +170,7 @@ export function materializeSandboxControl(params) {
168
170
  fs.mkdirSync(consumedDir, { recursive: true, mode: 0o700 });
169
171
  for (const directory of [statusDir, processingDir, runtimeDir]) {
170
172
  assertSafeDirectory(directory, root);
171
- if (directory === runtimeDir)
172
- fs.rmSync(directory, { recursive: true, force: true });
173
- fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
174
- }
175
- for (const directory of [
176
- path.join(runtimeDir, 'clients'),
177
- path.join(runtimeDir, 'clients', 'codex', 'capabilities'),
178
- path.join(runtimeDir, 'clients', 'codex', 'lifecycle')
179
- ]) {
180
- assertSafeDirectory(directory, runtimeDir);
181
173
  fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
182
- fs.chmodSync(directory, 0o700);
183
174
  }
184
175
  for (const queue of ['requests', 'responses']) {
185
176
  const directory = path.join(channelDir, queue);