@fitlab-ai/agent-infra 0.9.6 → 0.9.8

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 (178) hide show
  1. package/dist/bin/internal-cli.js +10 -0
  2. package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
  8. package/dist/lib/decide.js +68 -14
  9. package/dist/lib/defaults.json +10 -2
  10. package/dist/lib/git/workflow.js +30 -3
  11. package/dist/lib/init.js +1 -1
  12. package/dist/lib/internal/codex-lifecycle.js +69 -1
  13. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  14. package/dist/lib/internal/release-workflow.js +1 -3
  15. package/dist/lib/internal/task-activity.js +57 -7
  16. package/dist/lib/internal/task-event.js +57 -4
  17. package/dist/lib/internal/task-ledger.js +59 -5
  18. package/dist/lib/internal/task-lifecycle.js +59 -8
  19. package/dist/lib/internal/task-orchestration.js +48 -8
  20. package/dist/lib/internal/task-override.js +131 -0
  21. package/dist/lib/internal/task-review.js +64 -6
  22. package/dist/lib/internal/task-verify.js +65 -3
  23. package/dist/lib/internal/task-warning.js +57 -4
  24. package/dist/lib/platform/context.js +6 -4
  25. package/dist/lib/platform/github-client.js +55 -2
  26. package/dist/lib/platform/pr-summary.js +5 -3
  27. package/dist/lib/process-data/checkpoint.js +319 -0
  28. package/dist/lib/process-data/index.js +128 -60
  29. package/dist/lib/process-data/materialize.js +84 -0
  30. package/dist/lib/process-data/normalize.js +34 -1
  31. package/dist/lib/process-data/reconcile.js +55 -1
  32. package/dist/lib/process-data/sources.js +422 -1
  33. package/dist/lib/process-data/store.js +341 -11
  34. package/dist/lib/runtime/agent-runtime.js +56 -0
  35. package/dist/lib/sandbox/commands/create.js +394 -264
  36. package/dist/lib/sandbox/commands/rm.js +64 -15
  37. package/dist/lib/sandbox/commands/start.js +4 -3
  38. package/dist/lib/sandbox/control/container-identity.js +64 -0
  39. package/dist/lib/sandbox/control/executor.js +23 -9
  40. package/dist/lib/sandbox/control/lifecycle.js +590 -61
  41. package/dist/lib/sandbox/control/protocol.js +7 -0
  42. package/dist/lib/sandbox/control/server.js +202 -26
  43. package/dist/lib/sandbox/control/state.js +51 -25
  44. package/dist/lib/sandbox/control/timing.js +8 -0
  45. package/dist/lib/sandbox/engine.js +14 -7
  46. package/dist/lib/sandbox/mounts.js +7 -0
  47. package/dist/lib/sandbox/recovery.js +135 -50
  48. package/dist/lib/sandbox/workspace-view.js +59 -20
  49. package/dist/lib/server/daemon.js +1 -1
  50. package/dist/lib/server/process-control.js +11 -1
  51. package/dist/lib/server/process-state.js +107 -20
  52. package/dist/lib/task/activity-intent.js +4 -1
  53. package/dist/lib/task/artifact-lifecycle.js +82 -11
  54. package/dist/lib/task/artifact-receipts.js +128 -0
  55. package/dist/lib/task/codex-orchestration.js +81 -6
  56. package/dist/lib/task/commands/validate.js +1 -1
  57. package/dist/lib/task/create.js +10 -2
  58. package/dist/lib/task/decision-intents.js +4 -2
  59. package/dist/lib/task/delegation-receipts.js +120 -17
  60. package/dist/lib/task/events.js +148 -7
  61. package/dist/lib/task/guard-override.js +31 -0
  62. package/dist/lib/task/human-override.js +1240 -0
  63. package/dist/lib/task/ledger-intents.js +6 -3
  64. package/dist/lib/task/ledger.js +13 -1
  65. package/dist/lib/task/lifecycle.js +69 -14
  66. package/dist/lib/task/orchestration.js +293 -18
  67. package/dist/lib/task/review-finalization.js +3 -2
  68. package/dist/lib/task/sections.js +5 -1
  69. package/dist/lib/task/task-execution-lock.js +5 -4
  70. package/dist/lib/task/verification-engine.js +80 -22
  71. package/dist/lib/task/verification.js +13 -1
  72. package/dist/lib/task/workflow-warning-intents.js +4 -2
  73. package/dist/lib/task/write.js +2 -1
  74. package/dist/lifecycle-build-manifest.json +6 -0
  75. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  76. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
  77. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  78. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  80. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
  81. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
  82. package/lib/decide.ts +59 -14
  83. package/lib/defaults.json +10 -2
  84. package/lib/git/workflow.ts +25 -2
  85. package/lib/init.ts +1 -1
  86. package/lib/internal/codex-lifecycle.ts +68 -1
  87. package/lib/internal/codex-sandbox-controller.ts +109 -0
  88. package/lib/internal/release-workflow.ts +1 -3
  89. package/lib/internal/task-activity.ts +46 -6
  90. package/lib/internal/task-event.ts +53 -4
  91. package/lib/internal/task-ledger.ts +48 -4
  92. package/lib/internal/task-lifecycle.ts +56 -12
  93. package/lib/internal/task-orchestration.ts +43 -8
  94. package/lib/internal/task-override.ts +138 -0
  95. package/lib/internal/task-review.ts +51 -6
  96. package/lib/internal/task-verify.ts +52 -3
  97. package/lib/internal/task-warning.ts +44 -3
  98. package/lib/platform/context.ts +8 -5
  99. package/lib/platform/github-client.ts +61 -3
  100. package/lib/platform/pr-summary.ts +10 -3
  101. package/lib/process-data/checkpoint.ts +335 -0
  102. package/lib/process-data/index.ts +112 -60
  103. package/lib/process-data/materialize.ts +83 -0
  104. package/lib/process-data/normalize.ts +29 -1
  105. package/lib/process-data/reconcile.ts +64 -2
  106. package/lib/process-data/sources.ts +449 -2
  107. package/lib/process-data/store.ts +304 -11
  108. package/lib/process-data/types.ts +124 -2
  109. package/lib/runtime/agent-runtime.ts +76 -0
  110. package/lib/sandbox/commands/create.ts +139 -6
  111. package/lib/sandbox/commands/rm.ts +64 -14
  112. package/lib/sandbox/commands/start.ts +4 -3
  113. package/lib/sandbox/config.ts +2 -1
  114. package/lib/sandbox/control/container-identity.ts +93 -0
  115. package/lib/sandbox/control/executor.ts +24 -12
  116. package/lib/sandbox/control/lifecycle.ts +685 -67
  117. package/lib/sandbox/control/protocol.ts +35 -6
  118. package/lib/sandbox/control/server.ts +184 -24
  119. package/lib/sandbox/control/state.ts +67 -33
  120. package/lib/sandbox/control/timing.ts +12 -0
  121. package/lib/sandbox/engine.ts +20 -9
  122. package/lib/sandbox/mounts.ts +8 -0
  123. package/lib/sandbox/recovery.ts +159 -49
  124. package/lib/sandbox/workspace-view.ts +73 -22
  125. package/lib/server/daemon.ts +1 -1
  126. package/lib/server/process-control.ts +13 -3
  127. package/lib/server/process-state.ts +129 -27
  128. package/lib/task/activity-intent.ts +4 -2
  129. package/lib/task/artifact-lifecycle.ts +73 -10
  130. package/lib/task/artifact-receipts.ts +161 -0
  131. package/lib/task/codex-orchestration.ts +91 -5
  132. package/lib/task/commands/validate.ts +1 -1
  133. package/lib/task/create.ts +11 -2
  134. package/lib/task/decision-intents.ts +4 -2
  135. package/lib/task/delegation-receipts.ts +205 -25
  136. package/lib/task/events.ts +132 -6
  137. package/lib/task/guard-override.ts +50 -0
  138. package/lib/task/human-override.ts +1503 -0
  139. package/lib/task/ledger-intents.ts +6 -3
  140. package/lib/task/ledger.ts +16 -1
  141. package/lib/task/lifecycle.ts +71 -14
  142. package/lib/task/orchestration.ts +443 -22
  143. package/lib/task/review-finalization.ts +6 -2
  144. package/lib/task/sections.ts +6 -2
  145. package/lib/task/task-execution-lock.ts +11 -9
  146. package/lib/task/verification-engine.ts +93 -32
  147. package/lib/task/verification.ts +15 -1
  148. package/lib/task/workflow-warning-intents.ts +4 -2
  149. package/lib/task/write.ts +4 -1
  150. package/package.json +1 -1
  151. package/templates/.agents/README.en.md +7 -0
  152. package/templates/.agents/README.zh-CN.md +5 -0
  153. package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
  154. package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
  155. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
  156. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  157. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  158. package/templates/.agents/rules/review-handshake.en.md +3 -1
  159. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  160. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  161. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  162. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  163. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  164. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
  165. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
  166. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
  167. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
  168. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  169. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  170. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
  171. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
  172. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  173. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  174. package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
  175. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
  176. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  177. package/templates/.agents/workspace/README.en.md +0 -26
  178. package/templates/.agents/workspace/README.zh-CN.md +0 -26
@@ -11,7 +11,8 @@ import { assertManagedPath, removeManagedDir, removeWorktreeDir } from '../manag
11
11
  import { runEngine, runOk, runOkEngine, runSafe, runSafeEngine } from '../shell.js';
12
12
  import { resolveSandboxTarget } from '../workspace-identity.js';
13
13
  import { sandboxControlPaths, sandboxWorkspaceViewPaths } from '../workspace-view.js';
14
- import { quiesceSandboxControlRoot, readSandboxControlManifest } from '../control/lifecycle.js';
14
+ import { removeSandboxControlRoot, readSandboxControlManifest } from '../control/lifecycle.js';
15
+ import { inspectSandboxControlContainer } from '../control/container-identity.js';
15
16
  import { toolConfigDirCandidates, toolProjectDirCandidates } from '../tools.js';
16
17
  import { createSandboxCapabilityPlan } from '../agent-client-reconciler.js';
17
18
  import { fetchSandboxRows } from './list-running.js';
@@ -118,10 +119,39 @@ function removeEmptyManagedParent(base, directory) {
118
119
  }
119
120
  assertRemoved(parent, 'Empty sandbox container directory');
120
121
  }
121
- async function quiesceProjectControlRoots(config) {
122
+ async function removeExactSandboxContainer(engine, manifest, timeoutMs) {
123
+ const deadlineAt = Date.now() + timeoutMs;
124
+ const remaining = () => Math.max(1, deadlineAt - Date.now());
125
+ const inspect = () => inspectSandboxControlContainer(manifest, { timeoutMs: remaining() });
126
+ const before = await inspect();
127
+ if (before.state === 'unknown')
128
+ throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${before.reason}`);
129
+ if (before.state === 'absent')
130
+ return;
131
+ if (before.running && !runOkEngine(engine, 'docker', ['stop', manifest.containerIdentity.id], { timeout: remaining() })) {
132
+ const afterStop = await inspect();
133
+ if (afterStop.state !== 'absent' && afterStop.state !== 'found') {
134
+ throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${afterStop.reason}`);
135
+ }
136
+ if (afterStop.state === 'found' && afterStop.running) {
137
+ throw new Error(`Failed to stop sandbox container: ${manifest.containerIdentity.id}`);
138
+ }
139
+ }
140
+ const afterStop = await inspect();
141
+ if (afterStop.state === 'unknown')
142
+ throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${afterStop.reason}`);
143
+ if (afterStop.state === 'found' && !runOkEngine(engine, 'docker', ['rm', manifest.containerIdentity.id], { timeout: remaining() })) {
144
+ const afterRemove = await inspect();
145
+ if (afterRemove.state !== 'absent') {
146
+ throw new Error(`Failed to remove sandbox container: ${manifest.containerIdentity.id}`);
147
+ }
148
+ }
149
+ }
150
+ async function removeProjectControlRoots(config, engine) {
151
+ const containers = new Set();
122
152
  const projectRoot = path.join(config.controlBase, config.project);
123
153
  if (!fs.existsSync(projectRoot))
124
- return;
154
+ return containers;
125
155
  assertManagedPath(config.controlBase, projectRoot);
126
156
  const projectStat = fs.lstatSync(projectRoot);
127
157
  if (!projectStat.isDirectory() || projectStat.isSymbolicLink())
@@ -133,9 +163,19 @@ async function quiesceProjectControlRoots(config) {
133
163
  for (const identity of fs.readdirSync(containerRoot, { withFileTypes: true })) {
134
164
  if (!identity.isDirectory() || identity.isSymbolicLink())
135
165
  continue;
136
- await quiesceSandboxControlRoot(path.join(containerRoot, identity.name));
166
+ const root = path.join(containerRoot, identity.name);
167
+ const manifestPath = path.join(root, 'manifest.json');
168
+ if (!fs.existsSync(manifestPath))
169
+ throw new Error(`SANDBOX_CONTROL_TARGET_EVIDENCE_MISSING: ${root}`);
170
+ const manifest = readSandboxControlManifest(manifestPath);
171
+ containers.add(manifest.container);
172
+ await removeSandboxControlRoot(root, {
173
+ inspectContainer: (timeoutMs) => inspectSandboxControlContainer(manifest, { timeoutMs }),
174
+ removeContainer: (timeoutMs) => removeExactSandboxContainer(engine, manifest, timeoutMs)
175
+ });
137
176
  }
138
177
  }
178
+ return containers;
139
179
  }
140
180
  function inspectionBlockers(inspections) {
141
181
  return inspections.filter((inspection) => inspection.status !== 'clean');
@@ -211,25 +251,32 @@ async function rmOne(config, tools, branch, options = {}) {
211
251
  p.outro('Cancelled');
212
252
  return;
213
253
  }
254
+ const coordinatedContainers = new Set();
214
255
  for (const root of controlRoots.filter((candidate) => fs.existsSync(candidate))) {
215
256
  assertLegacyCandidateEvidence(root, config.controlBase, config, effectiveBranch, matchedContainers);
216
257
  assertControlRootMatchesTarget(root, effectiveBranch, workspace);
217
- await quiesceSandboxControlRoot(root);
258
+ const manifest = readSandboxControlManifest(path.join(root, 'manifest.json'));
259
+ coordinatedContainers.add(manifest.container);
260
+ await removeSandboxControlRoot(root, {
261
+ inspectContainer: (timeoutMs) => inspectSandboxControlContainer(manifest, { timeoutMs }),
262
+ removeContainer: (timeoutMs) => removeExactSandboxContainer(engine, manifest, timeoutMs)
263
+ });
218
264
  }
219
- if (matchedContainers.length > 0) {
265
+ const uncoordinatedContainers = matchedContainers.filter((name) => !coordinatedContainers.has(name));
266
+ if (uncoordinatedContainers.length > 0) {
220
267
  const spinner = p.spinner();
221
- spinner.start(`Stopping container(s): ${matchedContainers.join(', ')}`);
222
- for (const name of matchedContainers) {
268
+ spinner.start(`Stopping container(s): ${uncoordinatedContainers.join(', ')}`);
269
+ for (const name of uncoordinatedContainers) {
223
270
  if (!runOkEngine(engine, 'docker', ['stop', name]))
224
271
  throw new Error(`Failed to stop sandbox container: ${name}`);
225
272
  if (!runOkEngine(engine, 'docker', ['rm', name]))
226
273
  throw new Error(`Failed to remove sandbox container: ${name}`);
227
274
  }
228
275
  const remaining = runEngine(engine, 'docker', ['ps', '-a', '--format', '{{.Names}}']).split('\n').filter(Boolean);
229
- const leftovers = matchedContainers.filter((name) => remaining.includes(name));
276
+ const leftovers = uncoordinatedContainers.filter((name) => remaining.includes(name));
230
277
  if (leftovers.length > 0)
231
278
  throw new Error(`Sandbox container(s) still exist after removal: ${leftovers.join(', ')}`);
232
- spinner.stop(pc.green(`Removed container(s): ${matchedContainers.join(', ')}`));
279
+ spinner.stop(pc.green(`Removed container(s): ${uncoordinatedContainers.join(', ')}`));
233
280
  }
234
281
  else {
235
282
  p.log.warn(`No sandbox container found for '${branch}'`);
@@ -330,7 +377,7 @@ async function rmPurge(config, tools, prompt = {}) {
330
377
  throw new Error(`Refusing to purge because worktree preflight found blocker(s):\n${blockerMessage(blockers)}`);
331
378
  }
332
379
  const permits = cleanPermits(inspections);
333
- await quiesceProjectControlRoots(config);
380
+ const coordinatedContainers = await removeProjectControlRoots(config, engine);
334
381
  const containers = runEngine(engine, 'docker', [
335
382
  'ps',
336
383
  '-a',
@@ -339,10 +386,11 @@ async function rmPurge(config, tools, prompt = {}) {
339
386
  '--format',
340
387
  '{{.Names}}'
341
388
  ]);
342
- if (containers) {
389
+ const uncoordinatedContainers = containers.split('\n').filter((name) => name && !coordinatedContainers.has(name));
390
+ if (uncoordinatedContainers.length > 0) {
343
391
  const spinner = p.spinner();
344
392
  spinner.start('Stopping project sandbox containers...');
345
- for (const name of containers.split('\n').filter(Boolean)) {
393
+ for (const name of uncoordinatedContainers) {
346
394
  if (!runOkEngine(engine, 'docker', ['stop', name]))
347
395
  throw new Error(`Failed to stop sandbox container: ${name}`);
348
396
  if (!runOkEngine(engine, 'docker', ['rm', name]))
@@ -351,8 +399,9 @@ async function rmPurge(config, tools, prompt = {}) {
351
399
  const remaining = runEngine(engine, 'docker', [
352
400
  'ps', '-a', '--filter', `label=${sandboxLabel(config)}`, '--format', '{{.Names}}'
353
401
  ]);
354
- if (remaining)
355
- throw new Error(`Project sandbox container(s) still exist after removal: ${remaining.replaceAll('\n', ', ')}`);
402
+ const leftovers = remaining.split('\n').filter((name) => name && !coordinatedContainers.has(name));
403
+ if (leftovers.length > 0)
404
+ throw new Error(`Project sandbox container(s) still exist after removal: ${leftovers.join(', ')}`);
356
405
  spinner.stop(pc.green('Project sandbox containers removed'));
357
406
  }
358
407
  else {
@@ -8,9 +8,9 @@ const USAGE = `Usage: ai sandbox start [--recreate] <branch | TASK-id | N>
8
8
 
9
9
  Start an existing sandbox container that has stopped (for example after the
10
10
  Docker daemon was restarted or replaced). The container must already exist:
11
- if none is found, run 'ai sandbox create <branch>' first. A container that is
12
- already running is checked without modifying healthy runtime state. --recreate
13
- allows container-only replacement after in-place recovery fails.`;
11
+ if none is found, run 'ai sandbox create <branch>' first. Without --recreate, a
12
+ container that is already running is checked without modifying healthy runtime
13
+ state. --recreate replaces the existing container even when it is healthy.`;
14
14
  export function parseStartArgs(args) {
15
15
  let recreate = false;
16
16
  let help = false;
@@ -62,6 +62,7 @@ export async function start(args) {
62
62
  workspace: target.workspace,
63
63
  row: found,
64
64
  allowRecreate: parsed.recreate,
65
+ forceRecreate: parsed.recreate,
65
66
  recreate: async () => {
66
67
  const { create } = await import('./create.js');
67
68
  await create([target.requestedRef, '--no-refresh']);
@@ -0,0 +1,64 @@
1
+ import { commandForEngine, runEngine, runProbe } from '../shell.js';
2
+ function isAuthoritativeNotFound(error) {
3
+ const message = error instanceof Error ? error.message : String(error);
4
+ return /(?:no such (?:container|object)|container .* not found|cannot find container)/i.test(message);
5
+ }
6
+ export function classifySandboxContainerInspection(expected, inspection) {
7
+ if ('notFound' in inspection)
8
+ return { state: 'absent', id: expected.id };
9
+ if ('error' in inspection) {
10
+ return isAuthoritativeNotFound(inspection.error)
11
+ ? { state: 'absent', id: expected.id }
12
+ : { state: 'unknown', reason: `container inspection failed: ${inspection.error instanceof Error ? inspection.error.message : String(inspection.error)}` };
13
+ }
14
+ const response = inspection;
15
+ if (typeof response.id !== 'string' || response.id !== expected.id
16
+ || typeof response.running !== 'boolean' || !response.labels
17
+ || typeof response.labels !== 'object' || Array.isArray(response.labels)) {
18
+ return { state: 'unknown', reason: 'container inspection response is incomplete' };
19
+ }
20
+ const labels = response.labels;
21
+ for (const [key, value] of Object.entries(expected.labels)) {
22
+ if (labels[key] !== value)
23
+ return { state: 'unknown', reason: 'container labels do not match the manifest' };
24
+ }
25
+ return {
26
+ state: 'found',
27
+ id: response.id,
28
+ running: response.running,
29
+ labels: Object.fromEntries(Object.entries(labels).flatMap(([key, value]) => typeof value === 'string' ? [[key, value]] : []))
30
+ };
31
+ }
32
+ export async function inspectSandboxControlContainer(manifest, options = {}) {
33
+ try {
34
+ const args = ['inspect', '--format', '{{json .}}', manifest.containerIdentity.id];
35
+ let raw;
36
+ if (options.run) {
37
+ raw = options.run(manifest.engine, 'docker', args, options.timeoutMs === undefined ? {} : { timeout: options.timeoutMs });
38
+ }
39
+ else {
40
+ const command = commandForEngine(manifest.engine, 'docker', args);
41
+ const result = (options.probe ?? runProbe)(command.cmd, command.args, options.timeoutMs === undefined ? {} : { timeout: options.timeoutMs });
42
+ if (result.status !== 0) {
43
+ const stderr = typeof result.stderr === 'string' ? result.stderr : result.stderr?.toString('utf8') ?? '';
44
+ return classifySandboxContainerInspection(manifest.containerIdentity, {
45
+ error: new Error(stderr || `container inspection exited with ${result.status ?? 'unknown'}`)
46
+ });
47
+ }
48
+ raw = typeof result.stdout === 'string' ? result.stdout : result.stdout?.toString('utf8') ?? '';
49
+ }
50
+ const parsed = JSON.parse(raw);
51
+ const value = Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
52
+ if (!value || Array.isArray(value))
53
+ return { state: 'unknown', reason: 'container inspection response is incomplete' };
54
+ return classifySandboxContainerInspection(manifest.containerIdentity, {
55
+ id: value.Id,
56
+ running: value.State?.Running,
57
+ labels: value.Config?.Labels
58
+ });
59
+ }
60
+ catch (error) {
61
+ return classifySandboxContainerInspection(manifest.containerIdentity, { error });
62
+ }
63
+ }
64
+ //# sourceMappingURL=container-identity.js.map
@@ -18,7 +18,7 @@ async function waitForStartTime(pid, timeoutMs = 2_000) {
18
18
  const deadline = Date.now() + timeoutMs;
19
19
  while (Date.now() < deadline) {
20
20
  const startTime = getProcessStartTime(pid);
21
- if (startTime)
21
+ if (startTime !== null)
22
22
  return startTime;
23
23
  await new Promise((resolve) => setTimeout(resolve, 10));
24
24
  }
@@ -32,7 +32,11 @@ export async function prepareSandboxControlExecution(params) {
32
32
  cwd: params.manifest.repoRoot,
33
33
  detached: process.platform !== 'win32',
34
34
  stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
35
- env: { ...safeEnv(process.env), AGENT_INFRA_EXECUTOR_MANIFEST: params.manifestPath }
35
+ env: {
36
+ ...safeEnv(process.env),
37
+ AGENT_INFRA_EXECUTOR_MANIFEST: params.manifestPath,
38
+ AGENT_INFRA_RUNTIME_DIR: params.manifest.runtimeDir
39
+ }
36
40
  });
37
41
  if (typeof child.pid !== 'number')
38
42
  throw new Error('SANDBOX_CONTROL_EXECUTOR_SPAWN_FAILED');
@@ -48,7 +52,7 @@ export async function prepareSandboxControlExecution(params) {
48
52
  });
49
53
  const startTime = await waitForStartTime(child.pid);
50
54
  const execution = {
51
- version: 1,
55
+ version: 2,
52
56
  generation: params.manifest.generation,
53
57
  requestId: params.request.id,
54
58
  nonce,
@@ -63,17 +67,23 @@ export async function prepareSandboxControlExecution(params) {
63
67
  atomicWriteJson(executionPath(params.manifest, params.request.id), execution);
64
68
  return {
65
69
  execution,
66
- start() {
70
+ start(canWrite = () => true) {
67
71
  if (!child.connected)
68
72
  throw new Error('SANDBOX_CONTROL_EXECUTOR_GATE_CLOSED');
73
+ if (!canWrite())
74
+ throw new Error('SANDBOX_CONTROL_OWNER_LOST');
69
75
  atomicWriteJson(executionPath(params.manifest, params.request.id), { ...execution, phase: 'running', updatedAt: Date.now() });
76
+ if (!canWrite())
77
+ throw new Error('SANDBOX_CONTROL_OWNER_LOST');
70
78
  child.send({ version: 1, nonce });
71
79
  },
72
80
  completion,
73
- terminate() {
74
- atomicWriteJson(executionPath(params.manifest, params.request.id), {
75
- ...execution, phase: 'terminating', updatedAt: Date.now()
76
- });
81
+ terminate(updateState = true) {
82
+ if (updateState) {
83
+ atomicWriteJson(executionPath(params.manifest, params.request.id), {
84
+ ...execution, phase: 'terminating', updatedAt: Date.now()
85
+ });
86
+ }
77
87
  return terminateSandboxControlExecution(execution);
78
88
  }
79
89
  };
@@ -110,7 +120,11 @@ function executeRequest(manifest, request) {
110
120
  const boundArgs = bindSandboxControlTask(request, manifest.taskId);
111
121
  if (request.family === 'task-orchestration')
112
122
  boundArgs.push('--git-worktree-root', manifest.worktreeRoot);
113
- const result = spawnSync(process.execPath, nodeEntryArgs(process.argv[1], [request.family, ...boundArgs]), { cwd: manifest.repoRoot, encoding: 'utf8', env: safeEnv(process.env) });
123
+ const result = spawnSync(process.execPath, nodeEntryArgs(process.argv[1], [request.family, ...boundArgs]), {
124
+ cwd: manifest.repoRoot,
125
+ encoding: 'utf8',
126
+ env: { ...safeEnv(process.env), AGENT_INFRA_RUNTIME_DIR: manifest.runtimeDir }
127
+ });
114
128
  return { exitCode: result.status ?? 1, stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
115
129
  }
116
130
  export async function runSandboxControlExecutor(requestPath, nonce) {