@fitlab-ai/agent-infra 0.11.13 → 0.11.14

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 (89) hide show
  1. package/dist/bin/internal-cli.js +2 -7
  2. package/dist/lib/agent-clients/adapter.js +0 -3
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +5 -9
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +1 -5
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +1 -5
  6. package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -3
  7. package/dist/lib/agent-clients/adapters/codex.js +0 -2
  8. package/dist/lib/internal/cli-route-inventory.js +0 -1
  9. package/dist/lib/internal/task-finalization.js +6 -4
  10. package/dist/lib/internal/task-lifecycle.js +3 -1
  11. package/dist/lib/internal/task-operation-registry.js +1 -3
  12. package/dist/lib/internal/task-validate.js +14 -124
  13. package/dist/lib/platform/release-notes.js +2 -2
  14. package/dist/lib/sandbox/commands/control-status.js +0 -1
  15. package/dist/lib/sandbox/commands/create.js +5 -4
  16. package/dist/lib/sandbox/config.js +12 -0
  17. package/dist/lib/sandbox/control/audit.js +1 -2
  18. package/dist/lib/sandbox/control/client.js +12 -47
  19. package/dist/lib/sandbox/control/executor.js +6 -10
  20. package/dist/lib/sandbox/control/host-recovery.js +89 -27
  21. package/dist/lib/sandbox/control/lifecycle.js +20 -16
  22. package/dist/lib/sandbox/control/protocol.js +8 -36
  23. package/dist/lib/sandbox/control/server.js +2 -21
  24. package/dist/lib/sandbox/environment.js +7 -8
  25. package/dist/lib/sandbox/mounts.js +71 -59
  26. package/dist/lib/sandbox/recovery.js +2 -3
  27. package/dist/lib/sandbox/removal.js +19 -4
  28. package/dist/lib/sandbox/workspace-view.js +41 -4
  29. package/dist/lib/task/control-authority.js +39 -17
  30. package/dist/lib/task/control-recovery.js +1 -2
  31. package/dist/lib/task/finalization.js +13 -2
  32. package/dist/lib/task/lifecycle.js +92 -33
  33. package/dist/lifecycle-build-manifest.json +3 -3
  34. package/lib/agent-clients/adapter.ts +0 -27
  35. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +6 -12
  36. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +1 -5
  37. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -7
  38. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +1 -5
  39. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -4
  40. package/lib/agent-clients/adapters/codex-orchestration.ts +1 -3
  41. package/lib/agent-clients/adapters/codex.ts +0 -2
  42. package/lib/internal/cli-route-inventory.ts +0 -1
  43. package/lib/internal/task-finalization.ts +6 -4
  44. package/lib/internal/task-lifecycle.ts +3 -1
  45. package/lib/internal/task-operation-registry.ts +1 -3
  46. package/lib/internal/task-validate.ts +13 -127
  47. package/lib/platform/release-notes.ts +2 -2
  48. package/lib/sandbox/commands/control-status.ts +0 -1
  49. package/lib/sandbox/commands/create.ts +5 -4
  50. package/lib/sandbox/config.ts +13 -0
  51. package/lib/sandbox/control/audit.ts +1 -2
  52. package/lib/sandbox/control/client.ts +13 -61
  53. package/lib/sandbox/control/executor.ts +7 -13
  54. package/lib/sandbox/control/host-recovery.ts +94 -21
  55. package/lib/sandbox/control/lifecycle.ts +18 -16
  56. package/lib/sandbox/control/protocol.ts +9 -45
  57. package/lib/sandbox/control/server.ts +2 -17
  58. package/lib/sandbox/environment.ts +7 -8
  59. package/lib/sandbox/mounts.ts +92 -75
  60. package/lib/sandbox/recovery.ts +2 -3
  61. package/lib/sandbox/removal.ts +16 -4
  62. package/lib/sandbox/workspace-view.ts +40 -5
  63. package/lib/task/control-authority.ts +40 -26
  64. package/lib/task/control-recovery.ts +2 -3
  65. package/lib/task/finalization.ts +15 -2
  66. package/lib/task/lifecycle.ts +103 -31
  67. package/package.json +1 -1
  68. package/templates/.agents/rules/lifecycle-orchestration.en.md +3 -4
  69. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +3 -4
  70. package/templates/.agents/rules/task-short-id.en.md +18 -12
  71. package/templates/.agents/rules/task-short-id.zh-CN.md +12 -7
  72. package/templates/.agents/skills/create-release-note/SKILL.en.md +3 -1
  73. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +3 -1
  74. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  75. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  76. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +0 -130
  77. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +0 -127
  78. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +0 -131
  79. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-registration.js +0 -275
  80. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js +0 -106
  81. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +0 -345
  82. package/dist/lib/sandbox/control/agent-operation.js +0 -34
  83. package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +0 -158
  84. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +0 -163
  85. package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +0 -151
  86. package/lib/agent-clients/adapters/codex-lifecycle/controller-registration.ts +0 -352
  87. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts +0 -109
  88. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +0 -437
  89. package/lib/sandbox/control/agent-operation.ts +0 -49
@@ -23,28 +23,6 @@ import type {
23
23
  SandboxTool,
24
24
  SandboxToolContext
25
25
  } from '../sandbox/tool-types.ts';
26
- import type { SandboxControlManifest } from '../sandbox/control/protocol.ts';
27
-
28
- type AgentClientSandboxControlOperation = Readonly<{
29
- validate: (params: Readonly<{
30
- operation: string;
31
- payload: Record<string, unknown>;
32
- manifest: SandboxControlManifest;
33
- }>) => void;
34
- execute: (params: Readonly<{
35
- operation: string;
36
- payload: Record<string, unknown>;
37
- manifest: SandboxControlManifest;
38
- manifestPath: string;
39
- }>) => Promise<Readonly<{ exitCode: number; stdout: string; stderr: string }>>;
40
- recover?: (params: Readonly<{
41
- operation: string;
42
- manifest: SandboxControlManifest;
43
- manifestPath: string;
44
- stdout: string | null;
45
- }>) => Readonly<Record<string, unknown>>;
46
- }>;
47
-
48
26
  type AgentClientCapabilities = AgentClientCapabilityMap;
49
27
 
50
28
  type AgentClientModelSelectionContext =
@@ -147,7 +125,6 @@ type AgentClientAdapter = Readonly<{
147
125
  modelSelection: AgentClientModelSelectionContext;
148
126
  delegationEvidence: AgentClientDelegationEvidence;
149
127
  orchestrationAdapter?: AgentClientOrchestrationAdapter;
150
- sandboxControlOperation?: AgentClientSandboxControlOperation;
151
128
  project: AgentClientProjectDescriptor;
152
129
  sandbox: AgentClientSandboxDescriptor;
153
130
  }>;
@@ -728,9 +705,6 @@ function defineAgentClientAdapter(
728
705
  ...(candidate.orchestrationAdapter === undefined
729
706
  ? {}
730
707
  : { orchestrationAdapter: Object.freeze({ ...candidate.orchestrationAdapter }) }),
731
- ...(candidate.sandboxControlOperation === undefined
732
- ? {}
733
- : { sandboxControlOperation: Object.freeze({ ...candidate.sandboxControlOperation }) }),
734
708
  project: Object.freeze({
735
709
  ownedPathPrefixes: Object.freeze(paths),
736
710
  ...projectAssets,
@@ -753,7 +727,6 @@ export type {
753
727
  AgentClientCapabilities,
754
728
  AgentClientCustomCommandDescriptor,
755
729
  AgentClientDelegationEvidence,
756
- AgentClientSandboxControlOperation,
757
730
  AgentClientLifecycleRecoveryRequest,
758
731
  AgentClientLifecycleRecoveryResult,
759
732
  AgentClientOrchestrationAdapter,
@@ -170,8 +170,7 @@ function validateCodexLifecycleHookConfig(value: unknown): void {
170
170
 
171
171
  function parseCodexHooksList(
172
172
  value: unknown,
173
- repoRoot: string,
174
- expectedSource: 'direct-host' | 'isolated-user' = 'direct-host'
173
+ repoRoot: string
175
174
  ): readonly DiscoveredHook[] {
176
175
  const root = object(value);
177
176
  const entries = Array.isArray(root?.data) ? root.data : [];
@@ -203,10 +202,8 @@ function parseCodexHooksList(
203
202
  && hook.currentHash.length > 0
204
203
  && hook.sourcePathDigest.length === 64
205
204
  && hook.pluginId === null
206
- && (expectedSource === 'isolated-user'
207
- ? hook.source === 'user' && hook.trustStatus !== 'modified'
208
- : (hook.source === 'project' && hook.trustStatus === 'trusted')
209
- || hook.isManaged && hook.trustStatus === 'managed')
205
+ && (hook.source === 'project' && hook.trustStatus === 'trusted'
206
+ || hook.isManaged && hook.trustStatus === 'managed')
210
207
  ));
211
208
  if (!valid) throw new Error('CODEX_PREFLIGHT_HOOKS_NOT_LOADED: lifecycle hooks are not loaded for this workspace');
212
209
  return Object.freeze(hooks);
@@ -628,7 +625,7 @@ function validateStaticCodexPreflight(repoRoot: string): Readonly<{ cliVersion:
628
625
  async function discoverLifecycleHooks(repoRoot: string): Promise<Readonly<{
629
626
  discoveredHooks: readonly DiscoveredHook[];
630
627
  hookProvenance: Readonly<{
631
- hookSource: 'isolated-user' | 'managed' | 'project';
628
+ hookSource: 'managed' | 'project';
632
629
  hookSourcePathDigest: string;
633
630
  hookSourceHash: string;
634
631
  }>;
@@ -641,8 +638,7 @@ async function discoverLifecycleHooks(repoRoot: string): Promise<Readonly<{
641
638
  await transport.initialize();
642
639
  discoveredHooks = parseCodexHooksList(
643
640
  await transport.request('hooks/list', { cwds: [repoRoot] }),
644
- repoRoot,
645
- process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT ? 'isolated-user' : 'direct-host'
641
+ repoRoot
646
642
  );
647
643
  } finally { transport.close(); }
648
644
  const lifecycleHooks = LIFECYCLE_HOOKS.map(({ event, matcher, phase }) => discoveredHooks.filter((hook) =>
@@ -660,9 +656,7 @@ async function discoverLifecycleHooks(repoRoot: string): Promise<Readonly<{
660
656
  throw new Error('CODEX_PREFLIGHT_HOOK_SOURCE_AMBIGUOUS: lifecycle hooks do not share one exact source');
661
657
  }
662
658
  const source = [...sources][0];
663
- const hookSource = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT
664
- ? 'isolated-user'
665
- : source === 'managed' ? 'managed' : 'project';
659
+ const hookSource = source === 'managed' ? 'managed' : 'project';
666
660
  return {
667
661
  discoveredHooks,
668
662
  hookProvenance: Object.freeze({
@@ -9,7 +9,6 @@ import {
9
9
  resolveCodexThread
10
10
  } from './app-server.ts';
11
11
  import { createCodexLifecycleStore } from './store.ts';
12
- import { verifyCodexSandboxControllerContextWithWarnings } from './sandbox-controller.ts';
13
12
  import type { CodexLifecycleEvent } from './evidence.ts';
14
13
  import { resolveTaskRef } from '../../../task/resolve-ref.ts';
15
14
  import { resolveTaskContext } from '../../../task/resolve-ref.ts';
@@ -103,10 +102,7 @@ function hookDefinitionHash(): string {
103
102
  }
104
103
 
105
104
  function resolveLifecycleTaskContext() {
106
- const controllerPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
107
- const taskId = controllerPath
108
- ? verifyCodexSandboxControllerContextWithWarnings(controllerPath, { repoRoot: process.cwd() }).context.taskId
109
- : process.env.AGENT_INFRA_TASK_ID;
105
+ const taskId = process.env.AGENT_INFRA_TASK_ID;
110
106
  const context = taskId
111
107
  ? resolveTaskRef(taskId, { repoRoot: process.cwd() })
112
108
  : resolveTaskContext(undefined, { repoRoot: process.cwd() });
@@ -2,25 +2,18 @@
2
2
  "executableFiles": [
3
3
  "bin/internal-cli.ts",
4
4
  "lib/agent-clients/adapters/codex-lifecycle/cli.ts",
5
- "lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts",
6
5
  "lib/internal/sandbox-control.ts",
7
6
  "lib/internal/task-orchestration.ts",
8
7
  "lib/agent-clients/adapters/codex-orchestration.ts",
9
8
  "lib/task/delegation-receipts.ts",
10
9
  "lib/task/orchestration.ts",
11
10
  "lib/agent-clients/adapters/codex-lifecycle/build-identity.ts",
12
- "lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts",
13
- "lib/agent-clients/adapters/codex-lifecycle/controller-client.ts",
14
- "lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts",
15
- "lib/agent-clients/adapters/codex-lifecycle/controller-context.ts",
16
11
  "lib/agent-clients/adapters/codex-lifecycle/app-server.ts",
17
12
  "lib/agent-clients/adapters/codex-lifecycle/binding.ts",
18
13
  "lib/agent-clients/adapters/codex-lifecycle/evidence.ts",
19
14
  "lib/agent-clients/adapters/codex-lifecycle/store.ts",
20
15
  "lib/agent-clients/adapters/codex-lifecycle/manifest-files.json",
21
- "lib/agent-clients/adapters/codex-lifecycle/controller-registration.ts",
22
16
  "lib/sandbox/control/protocol.ts",
23
- "lib/sandbox/control/agent-operation.ts",
24
17
  "lib/sandbox/control/client.ts",
25
18
  "lib/sandbox/control/executor.ts",
26
19
  "lib/server/process-state.ts",
@@ -216,15 +216,11 @@ function hostEvidenceMatches(
216
216
  && host.spawnToolUseId === start.spawnToolUseId
217
217
  && Boolean(host.spawnObservedAt)
218
218
  && host.spawnObservedAt === record.spawnObservedAt
219
- && typeof host.controllerInstanceDigest === 'string'
220
- && typeof host.controlGeneration === 'string'
221
219
  && hostRevisionMatches(record, receipt, host);
222
220
  }
223
221
 
224
222
  function hostRevisionMatches(record: StoredCodexLifecycle, receipt: DelegationReceipt, host: CodexLifecycleActivationEvidence): boolean {
225
- return typeof host.controllerInstanceDigest === 'string'
226
- && typeof host.controlGeneration === 'string'
227
- && host.startRevision >= 1
223
+ return host.startRevision >= 1
228
224
  && record.revision > host.startRevision
229
225
  && record.state.child?.childThreadId === receipt.childId
230
226
  && record.state.stop?.childThreadId === receipt.childId;
@@ -35,16 +35,13 @@ type CodexLifecycleActivationEvidence = Readonly<{
35
35
  packageVersion?: string;
36
36
  internalExecutableBuildHash?: string;
37
37
  lifecycleContractHash?: string;
38
- hookSource?: 'project' | 'managed' | 'isolated-user';
38
+ hookSource?: 'project' | 'managed';
39
39
  hookSourcePathDigest?: string;
40
40
  hookSourceHash?: string;
41
41
  capabilitySessionId?: string;
42
42
  capabilityTurnId?: string;
43
- capabilityToolUseId?: string;
44
43
  spawnToolUseId?: string;
45
44
  spawnObservedAt?: string;
46
- controllerInstanceDigest?: string | null;
47
- controlGeneration?: string | null;
48
45
  }>;
49
46
 
50
47
  type CodexAdapterState = Readonly<{
@@ -327,9 +327,7 @@ async function activateCodexOrchestrationDelegation(
327
327
  capabilitySessionId: evidence.parentThreadId,
328
328
  capabilityTurnId: evidence.parentTurnId,
329
329
  spawnToolUseId: evidence.spawnToolUseId,
330
- spawnObservedAt: record.spawnObservedAt ?? undefined,
331
- controllerInstanceDigest: null,
332
- controlGeneration: null
330
+ spawnObservedAt: record.spawnObservedAt ?? undefined
333
331
  }
334
332
  }
335
333
  }, coreOptions(options));
@@ -6,7 +6,6 @@ import {
6
6
  } from './codex-sandbox.ts';
7
7
  import { parseCodexLifecycleBinding } from './codex-lifecycle/binding.ts';
8
8
  import { isCodexDelegationReceiptEvidence, isCodexLifecycleActivationEvidence } from './codex-lifecycle/evidence.ts';
9
- import { codexControllerOperation } from './codex-lifecycle/controller-operation.ts';
10
9
 
11
10
  function codexCarrierError(code: string): Error {
12
11
  const error = new Error(code);
@@ -80,7 +79,6 @@ const codexAdapter = defineAgentClientAdapter({
80
79
  return recoverStartedLifecycleFromAdapter(...args);
81
80
  }
82
81
  },
83
- sandboxControlOperation: codexControllerOperation,
84
82
  project: {
85
83
  ownedPathPrefixes: ['.codex/'],
86
84
  managed: ['.codex/hooks.json', '.codex/agents/'],
@@ -38,7 +38,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
38
38
  'sandbox-control': ['serve', 'execute', 'recover', 'client'],
39
39
  'agent-client': ['next-steps', 'model-selection', 'launch-carrier'],
40
40
  'codex-lifecycle': ['preflight', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
41
- 'codex-sandbox-controller': ['verify-context', 'run'],
42
41
  'git-workflow': ['inspect', 'preview-tree', 'snapshot', 'compare-trees', 'commit', 'push-rebased'],
43
42
  'task-delivery': ['deliver'],
44
43
  'release-workflow': ['inspect', 'prepare', 'publish', 'post-prepare', 'post-publish'],
@@ -8,7 +8,7 @@ import { applyTaskFinalization } from '../task/finalization.ts';
8
8
  import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
9
9
  import { resolveSandboxControlTransport } from './task-operation-registry.ts';
10
10
  import { quiesceSandboxControlForTaskCompletion } from '../sandbox/control/lifecycle.ts';
11
- import { resolveTaskRuntimeRoot } from '../task/runtime-paths.ts';
11
+ import { loadSandboxControlLocation } from '../sandbox/config.ts';
12
12
  import { serializeTaskFinalizationEnvelope } from '../task/finalization-envelope.ts';
13
13
  import { verifyTaskEvent } from '../task/verification.ts';
14
14
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
@@ -86,7 +86,8 @@ async function taskFinalization(args: string[] = []): Promise<void> {
86
86
  const taskId = process.env.AGENT_INFRA_TASK_ID;
87
87
  const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
88
88
  const controlRootId = process.env.AGENT_INFRA_CONTROL_ROOT_ID;
89
- if (!statusDir || !taskId || !generation || !controlRootId || taskId !== resolved.taskId) {
89
+ const controlRoot = process.env.AGENT_INFRA_CONTROL_ROOT_DIR;
90
+ if (!statusDir || !taskId || !generation || !controlRootId || !controlRoot || taskId !== resolved.taskId) {
90
91
  const detail = { code: 'TASK_CONTROL_TRANSPORT_INVALID', message: 'sandbox task control identity is incomplete', retryable: false };
91
92
  process.stdout.write(envelope('failed', false, true, null, detail));
92
93
  process.exitCode = 1;
@@ -97,7 +98,7 @@ async function taskFinalization(args: string[] = []): Promise<void> {
97
98
  beforeLifecycle: async (currentTaskId) => {
98
99
  if (currentTaskId !== taskId) throw new Error('SANDBOX_CONTROL_IDENTITY_MISMATCH');
99
100
  await quiesceSandboxControlForTaskCompletion(
100
- path.join(resolveTaskRuntimeRoot(taskId, { repoRoot }), 'sandbox-control'),
101
+ controlRoot,
101
102
  { taskId, generation, controlRootId, statusDir }
102
103
  );
103
104
  },
@@ -111,8 +112,9 @@ async function taskFinalization(args: string[] = []): Promise<void> {
111
112
  process.exitCode = exitCode(status);
112
113
  return;
113
114
  }
115
+ const controlLocation = loadSandboxControlLocation(repoRoot);
114
116
  const result = await dispatchTaskControlOperation(
115
- createDirectHostExecutionContext({ repoRoot }),
117
+ createDirectHostExecutionContext({ repoRoot, ...(controlLocation ?? {}) }),
116
118
  boundOperation
117
119
  );
118
120
  if (result.status === 'prepared') {
@@ -9,6 +9,7 @@ import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
9
9
  import type { TaskLifecycleResult } from '../task/lifecycle.ts';
10
10
  import type { AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult } from '../agent-clients/adapter.ts';
11
11
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
12
+ import { loadSandboxControlLocation } from '../sandbox/config.ts';
12
13
 
13
14
  const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.filter((intent) => intent !== 'complete').join(', ')}\nrecover-started: --auto\n`;
14
15
 
@@ -59,8 +60,9 @@ async function taskLifecycle(args: string[] = []): Promise<void> {
59
60
  repoRoot = resolved.repoRoot;
60
61
  }
61
62
 
63
+ const controlLocation = loadSandboxControlLocation(repoRoot);
62
64
  const result = await dispatchTaskControlOperation(
63
- createDirectHostExecutionContext({ repoRoot }),
65
+ createDirectHostExecutionContext({ repoRoot, ...(controlLocation ?? {}) }),
64
66
  operation
65
67
  ) as TaskLifecycleResult | LifecycleRecoveryResult;
66
68
  process.stdout.write(`${JSON.stringify(result)}\n`);
@@ -60,8 +60,6 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
60
60
  descriptor('internal', 'codex-lifecycle', 'resolve-start', 'conditional', 'progress'),
61
61
  descriptor('internal', 'codex-lifecycle', 'resolve-stop', 'conditional', 'progress'),
62
62
  descriptor('internal', 'codex-lifecycle', 'consume', 'conditional', 'progress'),
63
- descriptor('internal', 'codex-sandbox-controller', 'verify-context', 'task-bound', 'diagnostic'),
64
- descriptor('internal', 'codex-sandbox-controller', 'run', 'task-bound', 'progress'),
65
63
  descriptor('internal', 'git-workflow', 'inspect', 'conditional', 'diagnostic'),
66
64
  descriptor('internal', 'git-workflow', 'preview-tree', 'conditional', 'diagnostic'),
67
65
  descriptor('internal', 'git-workflow', 'snapshot', 'conditional', 'diagnostic'),
@@ -213,7 +211,7 @@ export const TASK_OPERATION_DESCRIPTORS = Object.freeze([
213
211
  ]);
214
212
 
215
213
  export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
216
- 'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle', 'codex-sandbox-controller',
214
+ 'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle',
217
215
  'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
218
216
  'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
219
217
  'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
@@ -2,27 +2,9 @@ import { execFileSync, spawnSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
- import { randomUUID } from 'node:crypto';
6
5
  import { loadConfig } from '../sandbox/config.ts';
7
- import { detectEngine } from '../sandbox/engine.ts';
8
- import { runEngine } from '../sandbox/shell.ts';
9
- import { sandboxBranchLabel, sandboxLabel } from '../sandbox/constants.ts';
10
- import { fetchSandboxRows, selectSandboxContainer } from '../sandbox/commands/list-running.ts';
11
- import { containerNameCandidates } from '../sandbox/constants.ts';
12
6
  import { resolveSandboxTarget } from '../sandbox/workspace-identity.ts';
13
- import { sandboxControlPaths } from '../sandbox/workspace-view.ts';
14
- import {
15
- appendSandboxControlAudit,
16
- readActiveLease,
17
- readSandboxControlStatus
18
- } from '../sandbox/control/state.ts';
19
- import type { SandboxControlLease } from '../sandbox/control/protocol.ts';
20
- import { readSandboxControlManifest } from '../sandbox/control/lifecycle.ts';
21
- import {
22
- SANDBOX_CONTROL_FUTURE_SKEW_MS,
23
- SANDBOX_CONTROL_STATUS_STALE_MS
24
- } from '../sandbox/control/protocol.ts';
25
- import { getProcessStartTime } from '../server/process-state.ts';
7
+ import { resolveBranchWorktree } from '../git/branch-worktree.ts';
26
8
  import { assertGitWorktreeBinding } from '../git/worktree-identity.ts';
27
9
  import { createManualValidationEvidence, manualValidationEvidenceSummary } from '../task/manual-validation-evidence.ts';
28
10
  import type { ManualValidationCleanup } from '../task/manual-validation-evidence.ts';
@@ -31,7 +13,6 @@ import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-in
31
13
 
32
14
  const USAGE = `Usage: agent-infra-internal task-validate <branch | TASK-id | N> [--scope snapshot|inplace] [--timeout <ms>] [--format text|json] [--evidence-file <path>] -- <command> [args...]`;
33
15
  const MAX_TIMEOUT_MS = 60 * 60 * 1000;
34
- const RECOVERY_GRACE_MS = 30_000;
35
16
 
36
17
  type ValidateOptions = {
37
18
  target: string;
@@ -108,20 +89,6 @@ function git(root: string, args: string[]): string {
108
89
  return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
109
90
  }
110
91
 
111
- function waitForState(statusDir: string, predicate: (state: ReturnType<typeof readSandboxControlStatus>) => boolean, timeoutMs = 5_000): void {
112
- const deadline = Date.now() + timeoutMs;
113
- while (Date.now() < deadline) {
114
- try {
115
- const status = readSandboxControlStatus(statusDir);
116
- if (predicate(status)) return;
117
- } catch {
118
- // Atomic status publication may be between renames.
119
- }
120
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
121
- }
122
- throw new Error('SANDBOX_VALIDATION_BROKER_STATE_TIMEOUT');
123
- }
124
-
125
92
  function snapshotValidation(repoRoot: string, commit: string, options: ValidateOptions) {
126
93
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-validation-'));
127
94
  const worktree = path.join(tempRoot, 'worktree');
@@ -150,109 +117,28 @@ function snapshotValidation(repoRoot: string, commit: string, options: ValidateO
150
117
  }
151
118
 
152
119
  function inplaceValidation(config: ReturnType<typeof loadConfig>, target: ReturnType<typeof resolveSandboxTarget>, options: ValidateOptions) {
153
- const engine = detectEngine(config);
154
- const { running, nonRunning } = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
155
- const row = selectSandboxContainer([...running, ...nonRunning], containerNameCandidates(config, target.branch));
156
- if (!row) throw new Error('SANDBOX_VALIDATION_CONTAINER_NOT_FOUND');
157
- const control = sandboxControlPaths({
158
- base: config.controlBase, repoRoot: config.repoRoot, project: config.project,
159
- container: row.name, identity: target.workspace
160
- });
161
- const manifest = readSandboxControlManifest(control.manifestPath);
162
- const initial = readSandboxControlStatus(control.statusDir);
163
- const now = Date.now();
164
- if (initial.generation !== manifest.generation
165
- || now - initial.updatedAt > SANDBOX_CONTROL_STATUS_STALE_MS
166
- || initial.updatedAt > now + SANDBOX_CONTROL_FUTURE_SKEW_MS) {
167
- throw new Error('SANDBOX_VALIDATION_BROKER_STATUS_STALE');
168
- }
169
- if (initial.state !== 'healthy' || initial.activeRequestId !== null) {
170
- throw new Error('SANDBOX_VALIDATION_BROKER_NOT_IDLE');
171
- }
172
- const startTime = getProcessStartTime(process.pid);
173
- if (!startTime) throw new Error('SANDBOX_VALIDATION_OWNER_IDENTITY_UNAVAILABLE');
174
- const leasePath = path.join(control.root, 'lease.json');
175
- const nonce = randomUUID();
176
- const issuedAt = Date.now();
177
- const lease: SandboxControlLease = {
178
- version: 2, generation: manifest.generation, nonce,
179
- owner: { pid: process.pid, startTime }, issuedAt,
180
- expiresAt: issuedAt + Math.min(options.timeoutMs + RECOVERY_GRACE_MS, MAX_TIMEOUT_MS + RECOVERY_GRACE_MS),
181
- taskId: target.workspace.mode === 'task-bound' ? target.workspace.taskId : null,
182
- branch: target.branch, reason: 'manual-validation'
183
- };
184
- const originalBranch = git(manifest.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
185
- const originalHead = git(manifest.worktreeRoot, ['rev-parse', 'HEAD']);
186
- fs.writeFileSync(leasePath, `${JSON.stringify(lease)}\n`, { flag: 'wx', mode: 0o600 });
187
- let stopped = false;
120
+ const targetRoot = resolveBranchWorktree(config.repoRoot, target.branch);
121
+ if (!targetRoot) throw new Error(`TASK_VALIDATION_WORKTREE_NOT_FOUND: ${target.branch}`);
122
+ const worktree = assertGitWorktreeBinding(config.repoRoot, targetRoot, target.branch);
123
+ const originalBranch = git(worktree.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
124
+ const originalHead = git(worktree.worktreeRoot, ['rev-parse', 'HEAD']);
188
125
  let restored = false;
189
126
  try {
190
- appendSandboxControlAudit(manifest, 'lease-acquire', { ownerPid: process.pid });
191
- waitForState(control.statusDir, (status) => status.state === 'parked' && status.reasonCode === 'SANDBOX_CONTROL_HANDOFF_ACTIVE');
192
- if (row.running) {
193
- runEngine(engine, 'docker', ['stop', row.name]);
194
- stopped = true;
195
- }
196
- const refreshedRows = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
197
- const refreshed = selectSandboxContainer(
198
- [...refreshedRows.running, ...refreshedRows.nonRunning],
199
- containerNameCandidates(config, target.branch)
200
- );
201
- if (!refreshed || refreshed.running) throw new Error('SANDBOX_VALIDATION_CONTAINER_STILL_RUNNING');
202
- const activeLease = readActiveLease(manifest);
203
- if (!activeLease || activeLease.nonce !== nonce) throw new Error('SANDBOX_VALIDATION_LEASE_OWNERSHIP_LOST');
204
- assertGitWorktreeBinding(config.repoRoot, manifest.worktreeRoot, target.branch);
205
- const result = runValidationCommand(options.command, manifest.worktreeRoot, 'inplace', options.timeoutMs, options.format === 'json');
127
+ const result = runValidationCommand(options.command, worktree.worktreeRoot, 'inplace', options.timeoutMs, options.format === 'json');
206
128
  return { ...result, cleanup: () => restored ? 'completed' : 'pending' };
207
129
  } finally {
208
- const recoveryErrors: string[] = [];
209
130
  try {
210
- const currentBranch = git(manifest.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
211
- if (currentBranch !== originalBranch) git(manifest.worktreeRoot, ['switch', originalBranch]);
131
+ const currentBranch = git(worktree.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
132
+ if (currentBranch !== originalBranch) git(worktree.worktreeRoot, ['switch', originalBranch]);
212
133
  } catch {
213
134
  try {
214
- git(manifest.worktreeRoot, ['checkout', '--detach', originalHead]);
215
- git(manifest.worktreeRoot, ['switch', originalBranch]);
216
- } catch (fallbackError) {
217
- recoveryErrors.push(`git binding restore failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
218
- }
219
- }
220
- if (stopped) {
221
- try {
222
- runEngine(engine, 'docker', ['start', row.name]);
223
- } catch (error) {
224
- recoveryErrors.push(`container start failed: ${error instanceof Error ? error.message : String(error)}`);
225
- }
226
- }
227
- try {
228
- const currentLease = JSON.parse(fs.readFileSync(leasePath, 'utf8')) as { nonce?: unknown };
229
- if (currentLease.nonce !== nonce) throw new Error('SANDBOX_VALIDATION_LEASE_OWNERSHIP_LOST');
230
- } catch (error) {
231
- recoveryErrors.push(error instanceof Error ? error.message : String(error));
232
- }
233
- if (recoveryErrors.length === 0) {
234
- try {
235
- fs.unlinkSync(leasePath);
236
- appendSandboxControlAudit(manifest, 'lease-release', { ownerPid: process.pid });
237
- waitForState(control.statusDir, (status) => status.state === 'healthy');
238
- restored = true;
135
+ git(worktree.worktreeRoot, ['checkout', '--detach', originalHead]);
136
+ git(worktree.worktreeRoot, ['switch', originalBranch]);
239
137
  } catch (error) {
240
- try {
241
- if (!fs.existsSync(leasePath)) {
242
- fs.writeFileSync(leasePath, `${JSON.stringify(lease)}\n`, { flag: 'wx', mode: 0o600 });
243
- }
244
- appendSandboxControlAudit(manifest, 'lease-release-failed', { ownerPid: process.pid });
245
- } catch (leaseError) {
246
- recoveryErrors.push(`lease preservation failed: ${leaseError instanceof Error ? leaseError.message : String(leaseError)}`);
247
- }
248
- recoveryErrors.push(error instanceof Error ? error.message : String(error));
138
+ throw new Error(`TASK_VALIDATION_WORKTREE_RESTORE_FAILED: ${error instanceof Error ? error.message : String(error)}`);
249
139
  }
250
140
  }
251
- if (recoveryErrors.length > 0) {
252
- throw new Error(
253
- `SANDBOX_VALIDATION_RECOVERY_INCOMPLETE: ${recoveryErrors.join('; ')}; run 'ai sandbox start ${target.branch}'`
254
- );
255
- }
141
+ restored = true;
256
142
  }
257
143
  }
258
144
 
@@ -136,8 +136,8 @@ async function releaseNoteContext(
136
136
  options: ReleaseNoteOptions = {}
137
137
  ) {
138
138
  const historyLimit = input.historyLimit ?? 3;
139
- if (!input.fromTag || !input.toTag || !input.branch || !Number.isInteger(historyLimit) || historyLimit < 1 || historyLimit > 20) {
140
- return { ...baseResult('failed'), error: { code: 'RELEASE_NOTES_INPUT_INVALID', message: 'from-tag, to-tag, branch, and a history limit from 1 to 20 are required', retryable: false } };
139
+ if (!input.fromTag || !input.toTag || !input.branch || !Number.isInteger(historyLimit) || historyLimit < 1 || historyLimit > 100) {
140
+ return { ...baseResult('failed'), error: { code: 'RELEASE_NOTES_INPUT_INVALID', message: 'from-tag, to-tag, branch, and a history limit from 1 to 100 are required', retryable: false } };
141
141
  }
142
142
  const cwd = path.resolve(options.cwd || process.cwd());
143
143
  const platformType = options.platformType ?? configuredPlatform(cwd);
@@ -14,7 +14,6 @@ export function readSandboxControlStatusForRow(
14
14
  : { mode: 'branch-only' as const };
15
15
  const control = sandboxControlPaths({
16
16
  base: config.controlBase,
17
- repoRoot: config.repoRoot,
18
17
  project: config.project,
19
18
  container: row.name,
20
19
  identity
@@ -1108,7 +1108,6 @@ export async function create(
1108
1108
  const container = containerName(effectiveConfig, branch);
1109
1109
  const controlPaths = sandboxControlPaths({
1110
1110
  base: effectiveConfig.controlBase,
1111
- repoRoot: effectiveConfig.repoRoot,
1112
1111
  project: effectiveConfig.project,
1113
1112
  container,
1114
1113
  identity: target.workspace
@@ -1493,6 +1492,7 @@ export async function create(
1493
1492
  shellConfigHostDir: hostShellConfig.hostDir,
1494
1493
  workspaceViewRoot: workspaceView.root,
1495
1494
  controlDir: control.channelDir,
1495
+ ...(target.workspace.mode === 'task-bound' ? { controlRootDir: control.root } : {}),
1496
1496
  controlStatusDir: control.statusDir,
1497
1497
  ...(target.workspace.mode === 'task-bound' ? { runtimeDir: control.runtimeDir } : {}),
1498
1498
  ...(target.workspace.mode === 'task-bound'
@@ -1588,8 +1588,6 @@ export async function create(
1588
1588
  ]
1589
1589
  : []),
1590
1590
  '-e',
1591
- `AGENT_INFRA_CONTROL_TOKEN=${control.token}`,
1592
- '-e',
1593
1591
  `AGENT_INFRA_CONTROL_GENERATION=${control.generation}`,
1594
1592
  '-e',
1595
1593
  'AGENT_INFRA_CONTROL_DIR=/run/agent-infra/control',
@@ -1598,7 +1596,10 @@ export async function create(
1598
1596
  '-e',
1599
1597
  `AGENT_INFRA_CONTROL_ROOT_ID=${control.controlRootId}`,
1600
1598
  ...(target.workspace.mode === 'task-bound'
1601
- ? ['-e', 'AGENT_INFRA_RUNTIME_DIR=/run/agent-infra/runtime']
1599
+ ? [
1600
+ '-e', 'AGENT_INFRA_CONTROL_ROOT_DIR=/run/agent-infra/control-root',
1601
+ '-e', 'AGENT_INFRA_RUNTIME_DIR=/run/agent-infra/runtime'
1602
+ ]
1602
1603
  : []),
1603
1604
  '--label',
1604
1605
  `${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
@@ -237,3 +237,16 @@ export function loadConfig({
237
237
  }
238
238
  };
239
239
  }
240
+
241
+ export function loadSandboxControlLocation(repoRoot = detectRepoRoot()): Readonly<{
242
+ controlBase: string;
243
+ project: string;
244
+ }> | null {
245
+ const configPath = path.join(repoRoot, '.agents', '.airc.json');
246
+ if (!fs.existsSync(configPath)) return null;
247
+ const airc = JSON.parse(fs.readFileSync(configPath, 'utf8')) as AircConfig;
248
+ if (typeof airc.project !== 'string' || !airc.project) return null;
249
+ const home = homedir();
250
+ if (!home) throw new Error('sandbox: home directory is required');
251
+ return { controlBase: hostJoin(home, '.agent-infra', 'sandbox-control'), project: airc.project };
252
+ }
@@ -41,8 +41,7 @@ const AUDIT_MAX_BYTES = 1024 * 1024;
41
41
 
42
42
  export function requestAuditFields(manifest: SandboxControlManifest, request: SandboxControlRequest) {
43
43
  return {
44
- requestId: request.id, requestFamily: request.family, sandboxTaskId: manifest.taskId,
45
- requestGeneration: request.generation, requestIssuedAt: request.issuedAt, requestExpiresAt: request.expiresAt
44
+ requestId: request.id, requestFamily: request.family, sandboxTaskId: manifest.taskId
46
45
  };
47
46
  }
48
47