@fitlab-ai/agent-infra 0.11.10 → 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 (219) hide show
  1. package/dist/bin/internal-cli.js +2 -2
  2. package/dist/lib/agent-clients/adapter.js +27 -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-sandbox.js +13 -0
  17. package/dist/lib/agent-clients/adapters/codex.js +44 -1
  18. package/dist/lib/agent-clients/adapters/index.js +2 -10
  19. package/dist/lib/agent-clients/tokens.js +3 -2
  20. package/dist/lib/internal/agent-client.js +132 -72
  21. package/dist/lib/internal/cli-route-inventory.js +2 -2
  22. package/dist/lib/internal/manual-validation.js +2 -2
  23. package/dist/lib/internal/platform-pr.js +4 -5
  24. package/dist/lib/internal/task-artifact.js +1 -1
  25. package/dist/lib/internal/task-create.js +15 -1
  26. package/dist/lib/internal/task-finalization.js +1 -3
  27. package/dist/lib/internal/task-operation-registry.js +5 -139
  28. package/dist/lib/internal/task-validate.js +1 -1
  29. package/dist/lib/platform/issues.js +18 -6
  30. package/dist/lib/platform/pr-summary.js +45 -8
  31. package/dist/lib/platform/pull-requests.js +79 -14
  32. package/dist/lib/platform/verification-sync.js +17 -14
  33. package/dist/lib/runtime/agent-runtime.js +6 -29
  34. package/dist/lib/sandbox/commands/control-status.js +1 -0
  35. package/dist/lib/sandbox/commands/create.js +19 -2
  36. package/dist/lib/sandbox/commands/enter.js +47 -33
  37. package/dist/lib/sandbox/control/agent-operation.js +34 -0
  38. package/dist/lib/sandbox/control/client.js +68 -161
  39. package/dist/lib/sandbox/control/executor.js +13 -87
  40. package/dist/lib/sandbox/control/host-recovery.js +218 -0
  41. package/dist/lib/sandbox/control/protocol.js +15 -44
  42. package/dist/lib/sandbox/control/server.js +57 -51
  43. package/dist/lib/sandbox/environment.js +150 -0
  44. package/dist/lib/sandbox/mounts.js +2 -1
  45. package/dist/lib/sandbox/recovery.js +11 -1
  46. package/dist/lib/sandbox/removal.js +9 -5
  47. package/dist/lib/sandbox/workspace-identity.js +0 -14
  48. package/dist/lib/sandbox/workspace-view.js +41 -23
  49. package/dist/lib/task/artifact-command.js +7 -2
  50. package/dist/lib/task/artifact-lifecycle.js +2 -1
  51. package/dist/lib/task/capabilities.js +4 -1
  52. package/dist/lib/task/control-authority.js +54 -266
  53. package/dist/lib/task/control-recovery.js +1 -1
  54. package/dist/lib/task/create.js +2 -2
  55. package/dist/lib/task/delegation-receipts.js +74 -161
  56. package/dist/lib/task/events.js +89 -45
  57. package/dist/lib/task/finalization.js +58 -60
  58. package/dist/lib/task/frontmatter.js +24 -1
  59. package/dist/lib/task/ledger-intents.js +2 -1
  60. package/dist/lib/task/lifecycle.js +84 -97
  61. package/dist/lib/task/orchestration.js +182 -129
  62. package/dist/lib/task/platform-operation-journal.js +12 -0
  63. package/dist/lib/task/platform-operation-recovery.js +14 -7
  64. package/dist/lib/task/resolve-ref.js +8 -3
  65. package/dist/lib/task/runtime-paths.js +10 -0
  66. package/dist/lib/task/verification-engine.js +28 -15
  67. package/dist/lib/task/workspace-snapshot.js +1 -1
  68. package/dist/lifecycle-build-manifest.json +3 -3
  69. package/lib/agent-clients/adapter.ts +61 -0
  70. package/lib/agent-clients/adapters/claude-code.ts +5 -1
  71. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
  72. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
  73. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
  74. package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
  75. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
  76. package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
  77. package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
  78. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
  80. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
  81. package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
  82. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
  83. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +272 -223
  84. package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
  85. package/lib/agent-clients/adapters/codex-sandbox.ts +13 -0
  86. package/lib/agent-clients/adapters/codex.ts +48 -1
  87. package/lib/agent-clients/adapters/index.ts +7 -20
  88. package/lib/agent-clients/tokens.ts +4 -3
  89. package/lib/internal/agent-client.ts +142 -73
  90. package/lib/internal/cli-route-inventory.ts +2 -2
  91. package/lib/internal/manual-validation.ts +2 -2
  92. package/lib/internal/platform-pr.ts +3 -4
  93. package/lib/internal/task-artifact.ts +1 -1
  94. package/lib/internal/task-create.ts +15 -2
  95. package/lib/internal/task-finalization.ts +1 -3
  96. package/lib/internal/task-operation-registry.ts +5 -151
  97. package/lib/internal/task-validate.ts +1 -1
  98. package/lib/platform/issues.ts +18 -6
  99. package/lib/platform/pr-summary.ts +43 -7
  100. package/lib/platform/pull-requests.ts +74 -13
  101. package/lib/platform/verification-sync.ts +18 -14
  102. package/lib/runtime/agent-runtime.ts +6 -41
  103. package/lib/sandbox/commands/control-status.ts +1 -0
  104. package/lib/sandbox/commands/create.ts +25 -2
  105. package/lib/sandbox/commands/enter.ts +42 -39
  106. package/lib/sandbox/control/agent-operation.ts +49 -0
  107. package/lib/sandbox/control/client.ts +86 -210
  108. package/lib/sandbox/control/executor.ts +17 -96
  109. package/lib/sandbox/control/host-recovery.ts +195 -0
  110. package/lib/sandbox/control/protocol.ts +24 -55
  111. package/lib/sandbox/control/server.ts +54 -52
  112. package/lib/sandbox/environment.ts +162 -0
  113. package/lib/sandbox/mounts.ts +2 -1
  114. package/lib/sandbox/recovery.ts +13 -1
  115. package/lib/sandbox/removal.ts +12 -5
  116. package/lib/sandbox/tool-types.ts +1 -0
  117. package/lib/sandbox/workspace-identity.ts +0 -26
  118. package/lib/sandbox/workspace-view.ts +42 -24
  119. package/lib/task/artifact-command.ts +9 -2
  120. package/lib/task/artifact-lifecycle.ts +2 -1
  121. package/lib/task/capabilities.ts +5 -2
  122. package/lib/task/control-authority.ts +65 -382
  123. package/lib/task/control-recovery.ts +2 -2
  124. package/lib/task/create.ts +3 -3
  125. package/lib/task/delegation-receipts.ts +87 -231
  126. package/lib/task/events.ts +101 -43
  127. package/lib/task/finalization.ts +57 -58
  128. package/lib/task/frontmatter.ts +25 -1
  129. package/lib/task/ledger-intents.ts +2 -1
  130. package/lib/task/lifecycle.ts +82 -87
  131. package/lib/task/orchestration.ts +237 -176
  132. package/lib/task/platform-operation-journal.ts +29 -2
  133. package/lib/task/platform-operation-recovery.ts +12 -6
  134. package/lib/task/resolve-ref.ts +8 -3
  135. package/lib/task/runtime-paths.ts +12 -0
  136. package/lib/task/verification-engine.ts +27 -16
  137. package/lib/task/workspace-snapshot.ts +1 -1
  138. package/package.json +1 -1
  139. package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
  140. package/templates/.agents/rules/README.en.md +2 -2
  141. package/templates/.agents/rules/README.zh-CN.md +2 -2
  142. package/templates/.agents/rules/issue-sync.en.md +1 -1
  143. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  144. package/templates/.agents/rules/next-step-output.en.md +6 -19
  145. package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
  146. package/templates/.agents/rules/no-mid-flow-questions.en.md +19 -0
  147. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +19 -0
  148. package/templates/.agents/rules/review-handshake.en.md +3 -0
  149. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -0
  150. package/templates/.agents/rules/task-management.en.md +1 -1
  151. package/templates/.agents/rules/task-management.zh-CN.md +1 -1
  152. package/templates/.agents/skills/analyze-task/SKILL.en.md +2 -2
  153. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
  154. package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
  155. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  156. package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
  157. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
  158. package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
  159. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
  160. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -0
  161. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -0
  162. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -0
  163. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -0
  164. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  165. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  166. package/templates/.agents/skills/code-task/reference/branch-management.en.md +1 -1
  167. package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +1 -1
  168. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +2 -0
  169. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +2 -0
  170. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +1 -1
  171. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +1 -1
  172. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -2
  173. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -2
  174. package/templates/.agents/skills/complete-task/SKILL.en.md +0 -3
  175. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +0 -3
  176. package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
  177. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  178. package/templates/.agents/skills/create-pr/reference/branch-strategy.en.md +2 -2
  179. package/templates/.agents/skills/create-pr/reference/branch-strategy.zh-CN.md +2 -2
  180. package/templates/.agents/skills/create-task/SKILL.en.md +3 -5
  181. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +3 -4
  182. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  183. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  184. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  185. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  186. package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
  187. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  188. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
  189. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
  190. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  191. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  192. package/templates/.agents/skills/review-code/SKILL.en.md +5 -5
  193. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
  194. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  195. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  196. package/templates/.agents/skills/review-pr/SKILL.en.md +1 -1
  197. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -1
  198. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  199. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  200. package/templates/.agents/skills/test/SKILL.en.md +4 -1
  201. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -1
  202. package/templates/.agents/skills/test-integration/SKILL.en.md +5 -2
  203. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +5 -2
  204. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +4 -1
  205. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +4 -1
  206. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +5 -2
  207. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +5 -2
  208. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  209. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  210. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
  211. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
  212. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
  213. package/dist/lib/internal/codex-lifecycle.js +0 -385
  214. package/dist/lib/task/codex-orchestration.js +0 -206
  215. package/dist/lib/task/finalization-handoff.js +0 -93
  216. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
  217. package/lib/internal/codex-lifecycle.ts +0 -390
  218. package/lib/task/codex-orchestration.ts +0 -276
  219. package/lib/task/finalization-handoff.ts +0 -105
@@ -0,0 +1,34 @@
1
+ import { getAgentClientAdapter } from '../../agent-clients/registry.js';
2
+ function operationFor(request) {
3
+ const adapter = getAgentClientAdapter(request.agentClient);
4
+ const operation = adapter.sandboxControlOperation;
5
+ if (!operation)
6
+ throw new Error('SANDBOX_CONTROL_AGENT_OPERATION_UNSUPPORTED');
7
+ return operation;
8
+ }
9
+ function validateAgentClientOperation(request, manifest) {
10
+ operationFor(request).validate({
11
+ operation: request.operation,
12
+ payload: request.payload,
13
+ manifest
14
+ });
15
+ }
16
+ function executeAgentClientOperation(request, manifest, manifestPath, override) {
17
+ return (override ?? operationFor(request)).execute({
18
+ operation: request.operation,
19
+ payload: request.payload,
20
+ manifest,
21
+ manifestPath
22
+ });
23
+ }
24
+ function recoverAgentClientOperation(params) {
25
+ const operation = operationFor(params.request);
26
+ return operation.recover?.({
27
+ operation: params.request.operation,
28
+ manifest: params.manifest,
29
+ manifestPath: params.manifestPath,
30
+ stdout: params.stdout
31
+ }) ?? { consistent: false };
32
+ }
33
+ export { executeAgentClientOperation, recoverAgentClientOperation, validateAgentClientOperation };
34
+ //# sourceMappingURL=agent-operation.js.map
@@ -1,13 +1,16 @@
1
1
  import fs from 'node:fs';
2
- import { hasTaskBoundMarker } from '../../internal/task-operation-registry.js';
3
2
  import path from 'node:path';
4
3
  import { randomUUID } from 'node:crypto';
5
4
  import { SANDBOX_CONTROL_ADMISSION_WINDOW_MS, SANDBOX_CONTROL_MAX_BYTES, SANDBOX_CONTROL_STATUS_STALE_MS, isSandboxControlFamily } from './protocol.js';
5
+ import { isAgentClientId } from '../../agent-clients/types.js';
6
6
  import { normalizeAgentToken } from '../../agent-clients/tokens.js';
7
7
  import { readSandboxControlPayload, readSandboxControlStatus } from './state.js';
8
8
  import { accessSandboxTaskView, taskViewFromStatus } from './task-view.js';
9
9
  import { readSandboxControlIdentitySentinel } from './identity-sentinel.js';
10
10
  import { configuredShortIdLength, resolveShortIdReadOnly } from '../../task/short-id.js';
11
+ import { hasTaskBoundMarker, isSandbox } from '../environment.js';
12
+ import { loadConfig } from '../config.js';
13
+ import { recoverSandboxControlFromHost } from './host-recovery.js';
11
14
  const SANDBOX_CONTROL_RESPONSE_SETTLE_MS = 250;
12
15
  const SANDBOX_TASK_FINALIZATION_RECOVERY_BUDGET_MS = 5 * 60_000;
13
16
  export class SandboxControlClientError extends Error {
@@ -98,7 +101,7 @@ function parseResponse(raw, id, accepted = false) {
98
101
  }
99
102
  return response;
100
103
  }
101
- function readPublishedResponse(raw, id, channelDir, accepted = false) {
104
+ function readPublishedResponse(raw, id, channelDir, accepted, generation) {
102
105
  const response = parseResponse(raw, id, accepted);
103
106
  if (response.outputState !== 'available')
104
107
  return response;
@@ -112,6 +115,7 @@ function readPublishedResponse(raw, id, channelDir, accepted = false) {
112
115
  }
113
116
  if (!response.payload || response.payload.version !== payload.version
114
117
  || response.payload.id !== payload.id || response.payload.generation !== payload.generation
118
+ || payload.generation !== generation
115
119
  || response.payload.stdoutBytes !== payload.stdoutBytes || response.payload.stderrBytes !== payload.stderrBytes
116
120
  || response.payload.stdoutSha256 !== payload.stdoutSha256 || response.payload.stderrSha256 !== payload.stderrSha256) {
117
121
  clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker payload reference is invalid', false, true, id);
@@ -216,7 +220,7 @@ function exchangeSandboxControl(request, params) {
216
220
  try {
217
221
  // The request-specific terminal proves the broker claimed the request; preserve recovery evidence if marker cleanup won the race.
218
222
  accepted = true;
219
- response = readPublishedResponse(raw, request.id, channelDir, accepted);
223
+ response = readPublishedResponse(raw, request.id, channelDir, accepted, request.generation);
220
224
  malformedResponseRaw = null;
221
225
  }
222
226
  catch (error) {
@@ -264,7 +268,40 @@ export function recoverSandboxControl(requestId, params = {}) {
264
268
  if (!/^[a-f0-9-]{16,64}$/u.test(requestId)) {
265
269
  clientError('SANDBOX_CONTROL_REQUEST_INVALID', 'request id is invalid', false, true);
266
270
  }
267
- const channelDir = params.channelDir ?? process.env.AGENT_INFRA_CONTROL_DIR ?? '/run/agent-infra/control';
271
+ try {
272
+ if (!isSandbox()) {
273
+ const config = loadConfig();
274
+ const response = recoverSandboxControlFromHost(requestId, {
275
+ managedRoot: path.join(config.controlBase, config.project),
276
+ timeoutMs: params.timeoutMs
277
+ });
278
+ if (response.phase === 'rejected' && response.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN') {
279
+ clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'recovery evidence is unavailable; inspect domain state before retrying', false, true, requestId);
280
+ }
281
+ return response;
282
+ }
283
+ const channelDir = process.env.AGENT_INFRA_CONTROL_DIR;
284
+ const statusDir = process.env.AGENT_INFRA_CONTROL_STATUS_DIR;
285
+ const identity = readSandboxControlIdentitySentinel(statusDir);
286
+ return recoverSandboxControlFromChannel(requestId, {
287
+ channelDir, generation: identity.generation, timeoutMs: params.timeoutMs
288
+ });
289
+ }
290
+ catch (error) {
291
+ if (error instanceof SandboxControlClientError)
292
+ throw error;
293
+ const message = error instanceof Error ? error.message : String(error);
294
+ const code = /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'SANDBOX_CONTROL_RECOVERY_INVALID';
295
+ clientError(code, 'sandbox control recovery evidence is invalid or unavailable', false, true, requestId);
296
+ }
297
+ }
298
+ // Low-level channel reader for control integration tests and internal callers
299
+ // that already hold a manifest-bound channel path.
300
+ export function recoverSandboxControlFromChannel(requestId, params) {
301
+ if (!/^[a-f0-9-]{16,64}$/u.test(requestId)) {
302
+ clientError('SANDBOX_CONTROL_REQUEST_INVALID', 'request id is invalid', false, true);
303
+ }
304
+ const { channelDir } = params;
268
305
  const responsePath = path.join(channelDir, 'responses', `${requestId}.json`);
269
306
  const deadline = Date.now() + (params.timeoutMs ?? 30_000);
270
307
  let accepted = false;
@@ -287,7 +324,7 @@ export function recoverSandboxControl(requestId, params = {}) {
287
324
  try {
288
325
  // The request-specific terminal proves the broker claimed the request; preserve recovery evidence if marker cleanup won the race.
289
326
  accepted = true;
290
- response = readPublishedResponse(raw, requestId, channelDir, accepted);
327
+ response = readPublishedResponse(raw, requestId, channelDir, accepted, params.generation);
291
328
  malformedResponseRaw = null;
292
329
  }
293
330
  catch (error) {
@@ -332,17 +369,34 @@ function authority(params) {
332
369
  export function requestSandboxControl(params) {
333
370
  if (!isSandboxControlFamily(params.family))
334
371
  clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' is not allowed`, false);
335
- if (params.family === 'task-create' || params.family === 'codex-controller' || params.family === 'task-finalization') {
372
+ if (params.family === 'task-create' || params.family === 'agent-client' || params.family === 'task-finalization') {
336
373
  clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' requires a typed request`, false);
337
374
  }
338
375
  const auth = authority(params);
339
376
  const issuedAt = Date.now();
340
377
  const request = {
341
- version: 3, id: randomUUID(), ...auth, issuedAt,
378
+ version: 4, id: randomUUID(), ...auth, issuedAt,
379
+ expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
380
+ family: params.family, args: params.args
381
+ };
382
+ return exchangeSandboxControl(request, params);
383
+ }
384
+ export function requestSandboxAgentOperation(params) {
385
+ if (!isAgentClientId(params.agentClient)) {
386
+ clientError('SANDBOX_CONTROL_REQUEST_INVALID', 'agent-client id is invalid', false);
387
+ }
388
+ const auth = authority(params);
389
+ const issuedAt = Date.now();
390
+ const request = {
391
+ version: 4,
392
+ id: randomUUID(),
393
+ ...auth,
394
+ issuedAt,
342
395
  expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
343
- family: params.family, args: params.args,
344
- controllerProcess: null,
345
- controllerProof: null
396
+ family: 'agent-client',
397
+ agentClient: params.agentClient,
398
+ operation: params.operation,
399
+ payload: params.payload
346
400
  };
347
401
  return exchangeSandboxControl(request, params);
348
402
  }
@@ -353,9 +407,8 @@ export function requestSandboxTaskFinalization(params) {
353
407
  const auth = authority(params);
354
408
  const issuedAt = Date.now();
355
409
  const id = randomUUID();
356
- const handoffSha256 = (params.prepareHandoff ?? (() => '0'.repeat(64)))({ generation: auth.generation, requestId: id });
357
410
  const request = {
358
- version: 3,
411
+ version: 4,
359
412
  id,
360
413
  ...auth,
361
414
  issuedAt,
@@ -363,10 +416,7 @@ export function requestSandboxTaskFinalization(params) {
363
416
  family: 'task-finalization',
364
417
  operation: 'complete',
365
418
  agent,
366
- handoffSha256,
367
- args: [],
368
- controllerProcess: null,
369
- controllerProof: null
419
+ args: []
370
420
  };
371
421
  let response;
372
422
  try {
@@ -391,7 +441,7 @@ export function recoverAcceptedTaskFinalization(requestId, params, recover = rec
391
441
  if (timeoutMs <= 0)
392
442
  break;
393
443
  try {
394
- const response = recover(requestId, { channelDir: params.channelDir, timeoutMs });
444
+ const response = recover(requestId, { timeoutMs });
395
445
  if (response.phase === 'rejected' && response.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN')
396
446
  continue;
397
447
  return response;
@@ -406,156 +456,13 @@ export function recoverAcceptedTaskFinalization(requestId, params, recover = rec
406
456
  }
407
457
  clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'accepted finalization request remained unknown after the automatic recovery budget; inspect the receipt and resume by request id', false, true, requestId);
408
458
  }
409
- function exactKeys(value, keys) {
410
- return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
411
- }
412
- function validProcess(value) {
413
- if (!value || typeof value !== 'object' || Array.isArray(value))
414
- return false;
415
- const identity = value;
416
- return exactKeys(identity, ['pid', 'startTime'])
417
- && Number.isSafeInteger(identity.pid) && identity.pid > 0
418
- && Number.isSafeInteger(identity.startTime) && identity.startTime >= 0;
419
- }
420
- function validBuild(value) {
421
- if (!value || typeof value !== 'object' || Array.isArray(value))
422
- return false;
423
- const build = value;
424
- return exactKeys(build, ['internalExecutableBuildHash', 'lifecycleContractHash', 'packageVersion', 'protocolVersion'])
425
- && build.protocolVersion === 3
426
- && typeof build.packageVersion === 'string' && build.packageVersion.length > 0
427
- && typeof build.internalExecutableBuildHash === 'string' && /^[a-f0-9]{64}$/u.test(build.internalExecutableBuildHash)
428
- && typeof build.lifecycleContractHash === 'string' && /^[a-f0-9]{64}$/u.test(build.lifecycleContractHash);
429
- }
430
- export function parseCodexControllerResult(response) {
431
- if (response.phase !== 'completed' || response.error !== null || response.stderr !== '') {
432
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller result outer response is invalid', false, true);
433
- }
434
- let value;
435
- try {
436
- if (!response.stdout.endsWith('\n') || response.stdout.slice(0, -1).includes('\n'))
437
- throw new Error('not canonical');
438
- value = JSON.parse(response.stdout);
439
- }
440
- catch {
441
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller result payload is invalid', false, true);
442
- }
443
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
444
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller result payload is invalid', false, true);
445
- }
446
- const result = value;
447
- const resultKeys = result.status === 'verified'
448
- ? ['binding', 'changed', 'error', 'lease', 'status', 'version']
449
- : ['changed', 'error', 'lease', 'status', 'version'];
450
- if (!exactKeys(result, resultKeys)
451
- || result.version !== 1 || !['opened', 'closed', 'failed', 'verified'].includes(result.status)) {
452
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller result schema is invalid', false, true);
453
- }
454
- if (result.status === 'failed') {
455
- const error = result.error;
456
- if (response.exitCode !== 1 || result.changed !== false || result.lease !== null
457
- || !error || !exactKeys(error, ['code', 'message', 'retryable'])
458
- || typeof error.code !== 'string' || !/^[A-Z][A-Z0-9_]+$/u.test(error.code)
459
- || typeof error.message !== 'string' || error.retryable !== false) {
460
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller failure result is invalid', false, true);
461
- }
462
- clientError(error.code, error.message, false, true);
463
- }
464
- if (response.exitCode !== 0 || result.error !== null) {
465
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller success result is invalid', false, true);
466
- }
467
- if (result.status === 'closed') {
468
- if (typeof result.changed !== 'boolean' || result.lease !== null) {
469
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller close result is invalid', false, true);
470
- }
471
- return result;
472
- }
473
- if (result.status === 'verified') {
474
- const binding = result.binding;
475
- if (result.changed !== false || result.lease !== null || !binding
476
- || !exactKeys(binding, ['controllerInstanceDigest', 'controlGeneration', 'taskId'])
477
- || typeof binding.taskId !== 'string' || binding.taskId.length === 0
478
- || typeof binding.controlGeneration !== 'string' || binding.controlGeneration.length === 0
479
- || typeof binding.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(binding.controllerInstanceDigest)) {
480
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller verify result is invalid', false, true);
481
- }
482
- return result;
483
- }
484
- const lease = result.lease;
485
- if (result.changed !== true || !lease
486
- || !exactKeys(lease, [
487
- 'buildIdentity', 'controlGeneration', 'controllerInstanceDigest', 'controllerProcess',
488
- 'expiresAt', 'issuedAt', 'leaseId', 'leaseSecret', 'taskId', 'version'
489
- ])
490
- || lease.version !== 1
491
- || typeof lease.leaseId !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.leaseId)
492
- || typeof lease.leaseSecret !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.leaseSecret)
493
- || typeof lease.taskId !== 'string' || lease.taskId.length === 0
494
- || typeof lease.controlGeneration !== 'string' || lease.controlGeneration.length === 0
495
- || typeof lease.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.controllerInstanceDigest)
496
- || !validProcess(lease.controllerProcess) || !validBuild(lease.buildIdentity)
497
- || !Number.isSafeInteger(lease.issuedAt) || !Number.isSafeInteger(lease.expiresAt)
498
- || lease.expiresAt <= lease.issuedAt) {
499
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller open result is invalid', false, true);
500
- }
501
- return result;
502
- }
503
- function requestCodexController(params) {
504
- const auth = authority(params);
505
- const issuedAt = Date.now();
506
- const request = {
507
- version: 3,
508
- id: randomUUID(),
509
- ...auth,
510
- issuedAt,
511
- expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
512
- family: 'codex-controller',
513
- command: params.command,
514
- args: [],
515
- controllerProcess: params.controllerProcess,
516
- controllerProof: params.controllerProof
517
- };
518
- const result = parseCodexControllerResult(exchangeSandboxControl(request, params));
519
- if (result.status === 'opened'
520
- && (result.lease.controlGeneration !== auth.generation
521
- || result.lease.controllerProcess.pid !== params.controllerProcess.pid
522
- || result.lease.controllerProcess.startTime !== params.controllerProcess.startTime)) {
523
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller result does not match the request', false, true);
524
- }
525
- return result;
526
- }
527
- export function requestCodexControllerOpen(params) {
528
- const result = requestCodexController({ ...params, command: 'open', controllerProof: null });
529
- if (result.status !== 'opened')
530
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller open returned the wrong result', false, true);
531
- return result;
532
- }
533
- export function requestCodexControllerClose(params) {
534
- const result = requestCodexController({ ...params, command: 'close' });
535
- if (result.status !== 'closed')
536
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller close returned the wrong result', false, true);
537
- return result;
538
- }
539
- export function requestCodexControllerVerify(params) {
540
- const result = requestCodexController({
541
- ...params,
542
- controllerProcess: params.controllerProof.controllerProcess,
543
- command: 'verify'
544
- });
545
- if (result.status !== 'verified') {
546
- clientError('SANDBOX_CONTROL_RESULT_INVALID', 'controller verify returned the wrong result', false, true);
547
- }
548
- return result;
549
- }
550
459
  export function requestSandboxTaskCreate(params) {
551
460
  const auth = authority(params);
552
461
  const issuedAt = Date.now();
553
462
  const request = {
554
- version: 3, id: randomUUID(), ...auth, issuedAt,
463
+ version: 4, id: randomUUID(), ...auth, issuedAt,
555
464
  expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
556
465
  family: 'task-create', candidate: params.candidate,
557
- controllerProcess: null,
558
- controllerProof: null
559
466
  };
560
467
  return exchangeSandboxControl(request, { ...params, timeoutMs: params.timeoutMs ?? 120_000 });
561
468
  }
@@ -2,7 +2,6 @@ import { spawn } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { randomUUID } from 'node:crypto';
5
- import { homedir } from 'node:os';
6
5
  import { getProcessStartTime } from '../../server/process-state.js';
7
6
  import { createTask, projectTaskCreateResult, taskCreateExitCode, taskCreateFailure } from '../../task/create-service.js';
8
7
  import { applyTaskFinalization } from '../../task/finalization.js';
@@ -13,12 +12,7 @@ import { requestAuditFields, resultAuditFields, appendDiagnosticAudit, createSan
13
12
  import { validateSandboxControlIdentity } from './identity-sentinel.js';
14
13
  import { createSandboxExecutorExecutionContext, dispatchTaskControlOperation, parseTaskControlOperation } from '../../task/control-authority.js';
15
14
  import { assertSandboxControlBrokerOwner, readSandboxControlManifest } from './lifecycle.js';
16
- import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.js';
17
- import { closeCodexControllerRegistration, CodexControllerRegistrationError, openCodexControllerRegistration, resolveCodexControllerBinding } from './controller-registration.js';
18
- import { shareBranchDir } from '../constants.js';
19
- function handoffDirectory(manifest) {
20
- return shareBranchDir({ shareBase: path.join(homedir(), '.agent-infra', 'share', manifest.project) }, manifest.branch);
21
- }
15
+ import { executeAgentClientOperation, validateAgentClientOperation } from './agent-operation.js';
22
16
  function sameBrokerOwner(left, right) {
23
17
  return left.version === right.version
24
18
  && left.pid === right.pid
@@ -174,23 +168,6 @@ function waitForGate(nonce, timeoutMs = 2_000) {
174
168
  });
175
169
  });
176
170
  }
177
- function controllerFailure(error) {
178
- const code = error instanceof CodexControllerRegistrationError
179
- ? error.code
180
- : /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
181
- ?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
182
- const message = error instanceof CodexControllerRegistrationError
183
- ? error.message
184
- : `${code}: controller operation failed; inspect the sandbox controller and rebuild the sandbox if needed`;
185
- const payload = {
186
- version: 1,
187
- status: 'failed',
188
- changed: false,
189
- lease: null,
190
- error: { code, message, retryable: false }
191
- };
192
- return { exitCode: 1, stdout: `${JSON.stringify(payload)}\n`, stderr: '' };
193
- }
194
171
  function lifecycleFailure(code, message) {
195
172
  return {
196
173
  exitCode: 1,
@@ -233,7 +210,7 @@ function finalizationResult(result) {
233
210
  async function executeRequestInner(manifest, manifestPath, request, options = {}) {
234
211
  if (request.family === 'task-create') {
235
212
  try {
236
- const result = await createTask(request.candidate, { repoRoot: manifest.repoRoot });
213
+ const result = await (options.createTask ?? createTask)(request.candidate, { repoRoot: manifest.repoRoot });
237
214
  return taskCreateExecutionResult(projectTaskCreateResult(result, {
238
215
  requestId: request.id,
239
216
  accepted: true,
@@ -246,65 +223,15 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
246
223
  return taskCreateExecutionFailure(request.id, code, detail);
247
224
  }
248
225
  }
249
- if (request.family === 'codex-controller') {
250
- try {
251
- if (request.command === 'verify') {
252
- const binding = (options.resolveControllerBinding ?? resolveCodexControllerBinding)({
253
- manifest,
254
- manifestPath,
255
- proof: request.controllerProof,
256
- buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
257
- });
258
- return {
259
- exitCode: 0,
260
- stdout: `${JSON.stringify({
261
- version: 1,
262
- status: 'verified',
263
- changed: false,
264
- lease: null,
265
- binding: {
266
- taskId: manifest.taskId,
267
- controlGeneration: binding.controlGeneration,
268
- controllerInstanceDigest: binding.instanceDigest
269
- },
270
- error: null
271
- })}\n`,
272
- stderr: ''
273
- };
274
- }
275
- const result = request.command === 'open'
276
- ? openCodexControllerRegistration({
277
- manifest,
278
- manifestPath,
279
- controllerProcess: request.controllerProcess,
280
- buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
281
- })
282
- : closeCodexControllerRegistration({
283
- manifest,
284
- manifestPath,
285
- proof: request.controllerProof
286
- });
287
- return { exitCode: 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
288
- }
289
- catch (error) {
290
- const errorCode = error instanceof CodexControllerRegistrationError
291
- ? error.code
292
- : /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
293
- ?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
294
- appendExecutorAudit(manifest, 'controller-operation-failed', {
295
- ...requestAuditFields(manifest, request),
296
- controllerCommand: request.command,
297
- errorCode,
298
- errorType: error instanceof Error ? error.name : typeof error,
299
- });
300
- return controllerFailure(error);
301
- }
226
+ if (request.family === 'agent-client') {
227
+ validateAgentClientOperation(request, manifest);
228
+ return executeAgentClientOperation(request, manifest, manifestPath, options.agentClientOperation);
302
229
  }
303
230
  if (request.family === 'task-finalization') {
304
231
  const parsed = parseTaskControlOperation('task-finalization', [manifest.taskId, 'complete', '--agent', request.agent]);
305
232
  if (parsed.family !== 'task-finalization')
306
233
  throw new Error('SANDBOX_CONTROL_FINALIZATION_OPERATION_INVALID');
307
- const operation = { ...parsed, request: { ...parsed.request, handoffSha256: request.handoffSha256 } };
234
+ const operation = parsed;
308
235
  const result = dispatchTaskControlOperation(createSandboxExecutorExecutionContext({
309
236
  repoRoot: manifest.repoRoot,
310
237
  worktreeRoot: manifest.worktreeRoot,
@@ -312,8 +239,7 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
312
239
  taskId: manifest.taskId,
313
240
  generation: manifest.generation,
314
241
  manifestPath,
315
- requestId: request.id,
316
- handoffDirectory: handoffDirectory(manifest)
242
+ requestId: request.id
317
243
  }), operation);
318
244
  return finalizationResult(await result);
319
245
  }
@@ -334,8 +260,7 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
334
260
  taskId: manifest.taskId,
335
261
  generation: manifest.generation,
336
262
  manifestPath,
337
- requestId: request.id,
338
- handoffDirectory: handoffDirectory(manifest)
263
+ requestId: request.id
339
264
  });
340
265
  const format = (value) => {
341
266
  const result = value;
@@ -353,7 +278,8 @@ export async function executeRequest(manifest, manifestPath, request, options =
353
278
  const fields = requestAuditFields(manifest, request);
354
279
  appendExecutorAudit(manifest, 'executor-request-start', fields);
355
280
  try {
356
- const result = await executeRequestInner(manifest, manifestPath, request, options);
281
+ let result;
282
+ result = await executeRequestInner(manifest, manifestPath, request, options);
357
283
  appendExecutorAudit(manifest, 'executor-request-finished', {
358
284
  requestId: request.id,
359
285
  requestFamily: request.family,
@@ -431,13 +357,13 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
431
357
  return null;
432
358
  }
433
359
  })()
434
- : null;
360
+ : request.family === 'agent-client' ? request.operation : null;
435
361
  const context = createSandboxControlAuditContext(manifest, {
436
362
  requestId: request.id,
437
363
  family: request.family,
438
- operation: operation?.family === 'task-lifecycle'
364
+ operation: operation && typeof operation === 'object' && 'family' in operation && operation.family === 'task-lifecycle'
439
365
  ? operation.request.intent
440
- : request.family === 'task-finalization' ? request.operation : null,
366
+ : request.family === 'task-finalization' || request.family === 'agent-client' ? request.operation : null,
441
367
  phase: 'started-committed',
442
368
  outcome: 'in-progress'
443
369
  });