@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
@@ -17,9 +17,9 @@ const taskWorkflowCommand = command === 'task-artifact'
17
17
  const taskPlatformRecoveryCommand = command === 'task-platform-recovery';
18
18
  const manualValidationWorkflowCommand = command === 'manual-validation';
19
19
  const taskControlMarkers = [
20
- 'AGENT_INFRA_TASK_ID', 'AGENT_INFRA_CONTROL_TOKEN', 'AGENT_INFRA_CONTROL_GENERATION',
20
+ 'AGENT_INFRA_TASK_ID', 'AGENT_INFRA_CONTROL_GENERATION',
21
21
  'AGENT_INFRA_CONTROL_DIR', 'AGENT_INFRA_CONTROL_STATUS_DIR', 'AGENT_INFRA_RUNTIME_DIR',
22
- 'AGENT_INFRA_CONTROL_CONTROLLER_BINDING', 'AGENT_INFRA_EXECUTOR_MANIFEST'
22
+ 'AGENT_INFRA_EXECUTOR_MANIFEST'
23
23
  ];
24
24
  const markerlessHelp = ['--help', '-h'].includes(process.argv[3] ?? '')
25
25
  && taskControlMarkers.every((key) => !process.env[key]);
@@ -115,11 +115,6 @@ if (!controlRouted && !taskViewGuardFailed && internalRouteRegistered)
115
115
  await codexLifecycle(process.argv.slice(3));
116
116
  break;
117
117
  }
118
- case 'codex-sandbox-controller': {
119
- const { codexSandboxController } = await import('../lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js');
120
- await codexSandboxController(process.argv.slice(3));
121
- break;
122
- }
123
118
  case 'git-workflow': {
124
119
  const { gitWorkflow } = await import('../lib/internal/git-workflow.js');
125
120
  gitWorkflow(process.argv.slice(3));
@@ -434,9 +434,6 @@ function defineAgentClientAdapter(candidate) {
434
434
  ...(candidate.orchestrationAdapter === undefined
435
435
  ? {}
436
436
  : { orchestrationAdapter: Object.freeze({ ...candidate.orchestrationAdapter }) }),
437
- ...(candidate.sandboxControlOperation === undefined
438
- ? {}
439
- : { sandboxControlOperation: Object.freeze({ ...candidate.sandboxControlOperation }) }),
440
437
  project: Object.freeze({
441
438
  ownedPathPrefixes: Object.freeze(paths),
442
439
  ...projectAssets,
@@ -114,7 +114,7 @@ function validateCodexLifecycleHookConfig(value) {
114
114
  if (!valid)
115
115
  throw new Error('CODEX_PREFLIGHT_HOOKS_INVALID: lifecycle hooks are invalid');
116
116
  }
117
- function parseCodexHooksList(value, repoRoot, expectedSource = 'direct-host') {
117
+ function parseCodexHooksList(value, repoRoot) {
118
118
  const root = object(value);
119
119
  const entries = Array.isArray(root?.data) ? root.data : [];
120
120
  const entry = entries.map(object).find((candidate) => candidate?.cwd === repoRoot);
@@ -145,10 +145,8 @@ function parseCodexHooksList(value, repoRoot, expectedSource = 'direct-host') {
145
145
  && hook.currentHash.length > 0
146
146
  && hook.sourcePathDigest.length === 64
147
147
  && hook.pluginId === null
148
- && (expectedSource === 'isolated-user'
149
- ? hook.source === 'user' && hook.trustStatus !== 'modified'
150
- : (hook.source === 'project' && hook.trustStatus === 'trusted')
151
- || hook.isManaged && hook.trustStatus === 'managed')));
148
+ && (hook.source === 'project' && hook.trustStatus === 'trusted'
149
+ || hook.isManaged && hook.trustStatus === 'managed')));
152
150
  if (!valid)
153
151
  throw new Error('CODEX_PREFLIGHT_HOOKS_NOT_LOADED: lifecycle hooks are not loaded for this workspace');
154
152
  return Object.freeze(hooks);
@@ -546,7 +544,7 @@ async function discoverLifecycleHooks(repoRoot) {
546
544
  let discoveredHooks;
547
545
  try {
548
546
  await transport.initialize();
549
- discoveredHooks = parseCodexHooksList(await transport.request('hooks/list', { cwds: [repoRoot] }), repoRoot, process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT ? 'isolated-user' : 'direct-host');
547
+ discoveredHooks = parseCodexHooksList(await transport.request('hooks/list', { cwds: [repoRoot] }), repoRoot);
550
548
  }
551
549
  finally {
552
550
  transport.close();
@@ -564,9 +562,7 @@ async function discoverLifecycleHooks(repoRoot) {
564
562
  throw new Error('CODEX_PREFLIGHT_HOOK_SOURCE_AMBIGUOUS: lifecycle hooks do not share one exact source');
565
563
  }
566
564
  const source = [...sources][0];
567
- const hookSource = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT
568
- ? 'isolated-user'
569
- : source === 'managed' ? 'managed' : 'project';
565
+ const hookSource = source === 'managed' ? 'managed' : 'project';
570
566
  return {
571
567
  discoveredHooks,
572
568
  hookProvenance: Object.freeze({
@@ -4,7 +4,6 @@ import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { preflightCodexLifecycleEvidence, resolveCodexTerminal, resolveCodexThread } from './app-server.js';
6
6
  import { createCodexLifecycleStore } from './store.js';
7
- import { verifyCodexSandboxControllerContextWithWarnings } from './sandbox-controller.js';
8
7
  import { resolveTaskRef } from '../../../task/resolve-ref.js';
9
8
  import { resolveTaskContext } from '../../../task/resolve-ref.js';
10
9
  import { hasSealableOrchestrationDelegation, readRun } from '../../../task/orchestration.js';
@@ -78,10 +77,7 @@ function hookDefinitionHash() {
78
77
  return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
79
78
  }
80
79
  function resolveLifecycleTaskContext() {
81
- const controllerPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
82
- const taskId = controllerPath
83
- ? verifyCodexSandboxControllerContextWithWarnings(controllerPath, { repoRoot: process.cwd() }).context.taskId
84
- : process.env.AGENT_INFRA_TASK_ID;
80
+ const taskId = process.env.AGENT_INFRA_TASK_ID;
85
81
  const context = taskId
86
82
  ? resolveTaskRef(taskId, { repoRoot: process.cwd() })
87
83
  : resolveTaskContext(undefined, { repoRoot: process.cwd() });
@@ -136,14 +136,10 @@ function hostEvidenceMatches(record, receipt, host, start) {
136
136
  && host.spawnToolUseId === start.spawnToolUseId
137
137
  && Boolean(host.spawnObservedAt)
138
138
  && host.spawnObservedAt === record.spawnObservedAt
139
- && typeof host.controllerInstanceDigest === 'string'
140
- && typeof host.controlGeneration === 'string'
141
139
  && hostRevisionMatches(record, receipt, host);
142
140
  }
143
141
  function hostRevisionMatches(record, receipt, host) {
144
- return typeof host.controllerInstanceDigest === 'string'
145
- && typeof host.controlGeneration === 'string'
146
- && host.startRevision >= 1
142
+ return host.startRevision >= 1
147
143
  && record.revision > host.startRevision
148
144
  && record.state.child?.childThreadId === receipt.childId
149
145
  && record.state.stop?.childThreadId === receipt.childId;
@@ -215,9 +215,7 @@ async function activateCodexOrchestrationDelegation(childThreadId, options = {})
215
215
  capabilitySessionId: evidence.parentThreadId,
216
216
  capabilityTurnId: evidence.parentTurnId,
217
217
  spawnToolUseId: evidence.spawnToolUseId,
218
- spawnObservedAt: record.spawnObservedAt ?? undefined,
219
- controllerInstanceDigest: null,
220
- controlGeneration: null
218
+ spawnObservedAt: record.spawnObservedAt ?? undefined
221
219
  }
222
220
  }
223
221
  }, coreOptions(options));
@@ -3,7 +3,6 @@ import { hostJoin } from '../../sandbox/engines/wsl2-paths.js';
3
3
  import { codexBeforeContainerCreateHook, codexRecoveryChecks } from './codex-sandbox.js';
4
4
  import { parseCodexLifecycleBinding } from './codex-lifecycle/binding.js';
5
5
  import { isCodexDelegationReceiptEvidence, isCodexLifecycleActivationEvidence } from './codex-lifecycle/evidence.js';
6
- import { codexControllerOperation } from './codex-lifecycle/controller-operation.js';
7
6
  function codexCarrierError(code) {
8
7
  const error = new Error(code);
9
8
  error.name = code;
@@ -72,7 +71,6 @@ const codexAdapter = defineAgentClientAdapter({
72
71
  return recoverStartedLifecycleFromAdapter(...args);
73
72
  }
74
73
  },
75
- sandboxControlOperation: codexControllerOperation,
76
74
  project: {
77
75
  ownedPathPrefixes: ['.codex/'],
78
76
  managed: ['.codex/hooks.json', '.codex/agents/'],
@@ -35,7 +35,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
35
35
  'sandbox-control': ['serve', 'execute', 'recover', 'client'],
36
36
  'agent-client': ['next-steps', 'model-selection', 'launch-carrier'],
37
37
  'codex-lifecycle': ['preflight', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
38
- 'codex-sandbox-controller': ['verify-context', 'run'],
39
38
  'git-workflow': ['inspect', 'preview-tree', 'snapshot', 'compare-trees', 'commit', 'push-rebased'],
40
39
  'task-delivery': ['deliver'],
41
40
  'release-workflow': ['inspect', 'prepare', 'publish', 'post-prepare', 'post-publish'],
@@ -4,7 +4,7 @@ import { applyTaskFinalization } from '../task/finalization.js';
4
4
  import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
5
5
  import { resolveSandboxControlTransport } from './task-operation-registry.js';
6
6
  import { quiesceSandboxControlForTaskCompletion } from '../sandbox/control/lifecycle.js';
7
- import { resolveTaskRuntimeRoot } from '../task/runtime-paths.js';
7
+ import { loadSandboxControlLocation } from '../sandbox/config.js';
8
8
  import { serializeTaskFinalizationEnvelope } from '../task/finalization-envelope.js';
9
9
  import { verifyTaskEvent } from '../task/verification.js';
10
10
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
@@ -71,7 +71,8 @@ async function taskFinalization(args = []) {
71
71
  const taskId = process.env.AGENT_INFRA_TASK_ID;
72
72
  const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
73
73
  const controlRootId = process.env.AGENT_INFRA_CONTROL_ROOT_ID;
74
- if (!statusDir || !taskId || !generation || !controlRootId || taskId !== resolved.taskId) {
74
+ const controlRoot = process.env.AGENT_INFRA_CONTROL_ROOT_DIR;
75
+ if (!statusDir || !taskId || !generation || !controlRootId || !controlRoot || taskId !== resolved.taskId) {
75
76
  const detail = { code: 'TASK_CONTROL_TRANSPORT_INVALID', message: 'sandbox task control identity is incomplete', retryable: false };
76
77
  process.stdout.write(envelope('failed', false, true, null, detail));
77
78
  process.exitCode = 1;
@@ -82,7 +83,7 @@ async function taskFinalization(args = []) {
82
83
  beforeLifecycle: async (currentTaskId) => {
83
84
  if (currentTaskId !== taskId)
84
85
  throw new Error('SANDBOX_CONTROL_IDENTITY_MISMATCH');
85
- await quiesceSandboxControlForTaskCompletion(path.join(resolveTaskRuntimeRoot(taskId, { repoRoot }), 'sandbox-control'), { taskId, generation, controlRootId, statusDir });
86
+ await quiesceSandboxControlForTaskCompletion(controlRoot, { taskId, generation, controlRootId, statusDir });
86
87
  },
87
88
  preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
88
89
  });
@@ -92,7 +93,8 @@ async function taskFinalization(args = []) {
92
93
  process.exitCode = exitCode(status);
93
94
  return;
94
95
  }
95
- const result = await dispatchTaskControlOperation(createDirectHostExecutionContext({ repoRoot }), boundOperation);
96
+ const controlLocation = loadSandboxControlLocation(repoRoot);
97
+ const result = await dispatchTaskControlOperation(createDirectHostExecutionContext({ repoRoot, ...(controlLocation ?? {}) }), boundOperation);
96
98
  if (result.status === 'prepared') {
97
99
  const detail = {
98
100
  code: 'TASK_FINALIZATION_DIRECT_COMMIT_REQUIRED',
@@ -2,6 +2,7 @@ import { createDirectHostExecutionContext, dispatchTaskControlOperation, parseTa
2
2
  import { lifecycleIntentCatalog } from '../task/lifecycle.js';
3
3
  import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
4
4
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
5
+ import { loadSandboxControlLocation } from '../sandbox/config.js';
5
6
  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`;
6
7
  function usageFailure(message) {
7
8
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
@@ -52,7 +53,8 @@ async function taskLifecycle(args = []) {
52
53
  }
53
54
  repoRoot = resolved.repoRoot;
54
55
  }
55
- const result = await dispatchTaskControlOperation(createDirectHostExecutionContext({ repoRoot }), operation);
56
+ const controlLocation = loadSandboxControlLocation(repoRoot);
57
+ const result = await dispatchTaskControlOperation(createDirectHostExecutionContext({ repoRoot, ...(controlLocation ?? {}) }), operation);
56
58
  process.stdout.write(`${JSON.stringify(result)}\n`);
57
59
  if (result.status === 'failed' || result.status === 'owner-unknown' || result.status === 'conflict')
58
60
  process.exitCode = 1;
@@ -25,8 +25,6 @@ function internalTaskRoutes() {
25
25
  descriptor('internal', 'codex-lifecycle', 'resolve-start', 'conditional', 'progress'),
26
26
  descriptor('internal', 'codex-lifecycle', 'resolve-stop', 'conditional', 'progress'),
27
27
  descriptor('internal', 'codex-lifecycle', 'consume', 'conditional', 'progress'),
28
- descriptor('internal', 'codex-sandbox-controller', 'verify-context', 'task-bound', 'diagnostic'),
29
- descriptor('internal', 'codex-sandbox-controller', 'run', 'task-bound', 'progress'),
30
28
  descriptor('internal', 'git-workflow', 'inspect', 'conditional', 'diagnostic'),
31
29
  descriptor('internal', 'git-workflow', 'preview-tree', 'conditional', 'diagnostic'),
32
30
  descriptor('internal', 'git-workflow', 'snapshot', 'conditional', 'diagnostic'),
@@ -175,7 +173,7 @@ export const TASK_OPERATION_DESCRIPTORS = Object.freeze([
175
173
  ...PUBLIC_OPERATION_DESCRIPTORS
176
174
  ]);
177
175
  export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
178
- 'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle', 'codex-sandbox-controller',
176
+ 'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle',
179
177
  'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
180
178
  'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
181
179
  'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
@@ -2,26 +2,15 @@ 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.js';
7
- import { detectEngine } from '../sandbox/engine.js';
8
- import { runEngine } from '../sandbox/shell.js';
9
- import { sandboxBranchLabel, sandboxLabel } from '../sandbox/constants.js';
10
- import { fetchSandboxRows, selectSandboxContainer } from '../sandbox/commands/list-running.js';
11
- import { containerNameCandidates } from '../sandbox/constants.js';
12
6
  import { resolveSandboxTarget } from '../sandbox/workspace-identity.js';
13
- import { sandboxControlPaths } from '../sandbox/workspace-view.js';
14
- import { appendSandboxControlAudit, readActiveLease, readSandboxControlStatus } from '../sandbox/control/state.js';
15
- import { readSandboxControlManifest } from '../sandbox/control/lifecycle.js';
16
- import { SANDBOX_CONTROL_FUTURE_SKEW_MS, SANDBOX_CONTROL_STATUS_STALE_MS } from '../sandbox/control/protocol.js';
17
- import { getProcessStartTime } from '../server/process-state.js';
7
+ import { resolveBranchWorktree } from '../git/branch-worktree.js';
18
8
  import { assertGitWorktreeBinding } from '../git/worktree-identity.js';
19
9
  import { createManualValidationEvidence, manualValidationEvidenceSummary } from '../task/manual-validation-evidence.js';
20
10
  import { writeJsonAtomic } from '../task/manual-validation-shared.js';
21
11
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
22
12
  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...]`;
23
13
  const MAX_TIMEOUT_MS = 60 * 60 * 1000;
24
- const RECOVERY_GRACE_MS = 30_000;
25
14
  export function parseValidateArgs(args) {
26
15
  if (args.includes('--help') || args.includes('-h')) {
27
16
  return { target: '', scope: 'snapshot', timeoutMs: 300_000, format: 'text', evidenceFile: null, command: [], help: true };
@@ -100,21 +89,6 @@ function runValidationCommand(command, cwd, scope, timeoutMs, json) {
100
89
  function git(root, args) {
101
90
  return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
102
91
  }
103
- function waitForState(statusDir, predicate, timeoutMs = 5_000) {
104
- const deadline = Date.now() + timeoutMs;
105
- while (Date.now() < deadline) {
106
- try {
107
- const status = readSandboxControlStatus(statusDir);
108
- if (predicate(status))
109
- return;
110
- }
111
- catch {
112
- // Atomic status publication may be between renames.
113
- }
114
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
115
- }
116
- throw new Error('SANDBOX_VALIDATION_BROKER_STATE_TIMEOUT');
117
- }
118
92
  function snapshotValidation(repoRoot, commit, options) {
119
93
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-validation-'));
120
94
  const worktree = path.join(tempRoot, 'worktree');
@@ -146,117 +120,33 @@ function snapshotValidation(repoRoot, commit, options) {
146
120
  }
147
121
  }
148
122
  function inplaceValidation(config, target, options) {
149
- const engine = detectEngine(config);
150
- const { running, nonRunning } = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
151
- const row = selectSandboxContainer([...running, ...nonRunning], containerNameCandidates(config, target.branch));
152
- if (!row)
153
- throw new Error('SANDBOX_VALIDATION_CONTAINER_NOT_FOUND');
154
- const control = sandboxControlPaths({
155
- base: config.controlBase, repoRoot: config.repoRoot, project: config.project,
156
- container: row.name, identity: target.workspace
157
- });
158
- const manifest = readSandboxControlManifest(control.manifestPath);
159
- const initial = readSandboxControlStatus(control.statusDir);
160
- const now = Date.now();
161
- if (initial.generation !== manifest.generation
162
- || now - initial.updatedAt > SANDBOX_CONTROL_STATUS_STALE_MS
163
- || initial.updatedAt > now + SANDBOX_CONTROL_FUTURE_SKEW_MS) {
164
- throw new Error('SANDBOX_VALIDATION_BROKER_STATUS_STALE');
165
- }
166
- if (initial.state !== 'healthy' || initial.activeRequestId !== null) {
167
- throw new Error('SANDBOX_VALIDATION_BROKER_NOT_IDLE');
168
- }
169
- const startTime = getProcessStartTime(process.pid);
170
- if (!startTime)
171
- throw new Error('SANDBOX_VALIDATION_OWNER_IDENTITY_UNAVAILABLE');
172
- const leasePath = path.join(control.root, 'lease.json');
173
- const nonce = randomUUID();
174
- const issuedAt = Date.now();
175
- const lease = {
176
- version: 2, generation: manifest.generation, nonce,
177
- owner: { pid: process.pid, startTime }, issuedAt,
178
- expiresAt: issuedAt + Math.min(options.timeoutMs + RECOVERY_GRACE_MS, MAX_TIMEOUT_MS + RECOVERY_GRACE_MS),
179
- taskId: target.workspace.mode === 'task-bound' ? target.workspace.taskId : null,
180
- branch: target.branch, reason: 'manual-validation'
181
- };
182
- const originalBranch = git(manifest.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
183
- const originalHead = git(manifest.worktreeRoot, ['rev-parse', 'HEAD']);
184
- fs.writeFileSync(leasePath, `${JSON.stringify(lease)}\n`, { flag: 'wx', mode: 0o600 });
185
- let stopped = false;
123
+ const targetRoot = resolveBranchWorktree(config.repoRoot, target.branch);
124
+ if (!targetRoot)
125
+ throw new Error(`TASK_VALIDATION_WORKTREE_NOT_FOUND: ${target.branch}`);
126
+ const worktree = assertGitWorktreeBinding(config.repoRoot, targetRoot, target.branch);
127
+ const originalBranch = git(worktree.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
128
+ const originalHead = git(worktree.worktreeRoot, ['rev-parse', 'HEAD']);
186
129
  let restored = false;
187
130
  try {
188
- appendSandboxControlAudit(manifest, 'lease-acquire', { ownerPid: process.pid });
189
- waitForState(control.statusDir, (status) => status.state === 'parked' && status.reasonCode === 'SANDBOX_CONTROL_HANDOFF_ACTIVE');
190
- if (row.running) {
191
- runEngine(engine, 'docker', ['stop', row.name]);
192
- stopped = true;
193
- }
194
- const refreshedRows = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
195
- const refreshed = selectSandboxContainer([...refreshedRows.running, ...refreshedRows.nonRunning], containerNameCandidates(config, target.branch));
196
- if (!refreshed || refreshed.running)
197
- throw new Error('SANDBOX_VALIDATION_CONTAINER_STILL_RUNNING');
198
- const activeLease = readActiveLease(manifest);
199
- if (!activeLease || activeLease.nonce !== nonce)
200
- throw new Error('SANDBOX_VALIDATION_LEASE_OWNERSHIP_LOST');
201
- assertGitWorktreeBinding(config.repoRoot, manifest.worktreeRoot, target.branch);
202
- const result = runValidationCommand(options.command, manifest.worktreeRoot, 'inplace', options.timeoutMs, options.format === 'json');
131
+ const result = runValidationCommand(options.command, worktree.worktreeRoot, 'inplace', options.timeoutMs, options.format === 'json');
203
132
  return { ...result, cleanup: () => restored ? 'completed' : 'pending' };
204
133
  }
205
134
  finally {
206
- const recoveryErrors = [];
207
135
  try {
208
- const currentBranch = git(manifest.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
136
+ const currentBranch = git(worktree.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
209
137
  if (currentBranch !== originalBranch)
210
- git(manifest.worktreeRoot, ['switch', originalBranch]);
138
+ git(worktree.worktreeRoot, ['switch', originalBranch]);
211
139
  }
212
140
  catch {
213
141
  try {
214
- git(manifest.worktreeRoot, ['checkout', '--detach', originalHead]);
215
- git(manifest.worktreeRoot, ['switch', originalBranch]);
216
- }
217
- catch (fallbackError) {
218
- recoveryErrors.push(`git binding restore failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
219
- }
220
- }
221
- if (stopped) {
222
- try {
223
- runEngine(engine, 'docker', ['start', row.name]);
224
- }
225
- catch (error) {
226
- recoveryErrors.push(`container start failed: ${error instanceof Error ? error.message : String(error)}`);
227
- }
228
- }
229
- try {
230
- const currentLease = JSON.parse(fs.readFileSync(leasePath, 'utf8'));
231
- if (currentLease.nonce !== nonce)
232
- throw new Error('SANDBOX_VALIDATION_LEASE_OWNERSHIP_LOST');
233
- }
234
- catch (error) {
235
- recoveryErrors.push(error instanceof Error ? error.message : String(error));
236
- }
237
- if (recoveryErrors.length === 0) {
238
- try {
239
- fs.unlinkSync(leasePath);
240
- appendSandboxControlAudit(manifest, 'lease-release', { ownerPid: process.pid });
241
- waitForState(control.statusDir, (status) => status.state === 'healthy');
242
- restored = true;
142
+ git(worktree.worktreeRoot, ['checkout', '--detach', originalHead]);
143
+ git(worktree.worktreeRoot, ['switch', originalBranch]);
243
144
  }
244
145
  catch (error) {
245
- try {
246
- if (!fs.existsSync(leasePath)) {
247
- fs.writeFileSync(leasePath, `${JSON.stringify(lease)}\n`, { flag: 'wx', mode: 0o600 });
248
- }
249
- appendSandboxControlAudit(manifest, 'lease-release-failed', { ownerPid: process.pid });
250
- }
251
- catch (leaseError) {
252
- recoveryErrors.push(`lease preservation failed: ${leaseError instanceof Error ? leaseError.message : String(leaseError)}`);
253
- }
254
- recoveryErrors.push(error instanceof Error ? error.message : String(error));
146
+ throw new Error(`TASK_VALIDATION_WORKTREE_RESTORE_FAILED: ${error instanceof Error ? error.message : String(error)}`);
255
147
  }
256
148
  }
257
- if (recoveryErrors.length > 0) {
258
- throw new Error(`SANDBOX_VALIDATION_RECOVERY_INCOMPLETE: ${recoveryErrors.join('; ')}; run 'ai sandbox start ${target.branch}'`);
259
- }
149
+ restored = true;
260
150
  }
261
151
  }
262
152
  function errorCode(message) {
@@ -106,8 +106,8 @@ function configuredPlatform(cwd) {
106
106
  }
107
107
  async function releaseNoteContext(input, options = {}) {
108
108
  const historyLimit = input.historyLimit ?? 3;
109
- if (!input.fromTag || !input.toTag || !input.branch || !Number.isInteger(historyLimit) || historyLimit < 1 || historyLimit > 20) {
110
- 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 } };
109
+ if (!input.fromTag || !input.toTag || !input.branch || !Number.isInteger(historyLimit) || historyLimit < 1 || historyLimit > 100) {
110
+ 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 } };
111
111
  }
112
112
  const cwd = path.resolve(options.cwd || process.cwd());
113
113
  const platformType = options.platformType ?? configuredPlatform(cwd);
@@ -8,7 +8,6 @@ export function readSandboxControlStatusForRow(config, row) {
8
8
  : { mode: 'branch-only' };
9
9
  const control = sandboxControlPaths({
10
10
  base: config.controlBase,
11
- repoRoot: config.repoRoot,
12
11
  project: config.project,
13
12
  container: row.name,
14
13
  identity
@@ -741,7 +741,6 @@ export async function create(args, { runProjectInitCommand = true, workspace: wo
741
741
  const container = containerName(effectiveConfig, branch);
742
742
  const controlPaths = sandboxControlPaths({
743
743
  base: effectiveConfig.controlBase,
744
- repoRoot: effectiveConfig.repoRoot,
745
744
  project: effectiveConfig.project,
746
745
  container,
747
746
  identity: target.workspace
@@ -1079,6 +1078,7 @@ export async function create(args, { runProjectInitCommand = true, workspace: wo
1079
1078
  shellConfigHostDir: hostShellConfig.hostDir,
1080
1079
  workspaceViewRoot: workspaceView.root,
1081
1080
  controlDir: control.channelDir,
1081
+ ...(target.workspace.mode === 'task-bound' ? { controlRootDir: control.root } : {}),
1082
1082
  controlStatusDir: control.statusDir,
1083
1083
  ...(target.workspace.mode === 'task-bound' ? { runtimeDir: control.runtimeDir } : {}),
1084
1084
  ...(target.workspace.mode === 'task-bound'
@@ -1162,8 +1162,6 @@ export async function create(args, { runProjectInitCommand = true, workspace: wo
1162
1162
  ]
1163
1163
  : []),
1164
1164
  '-e',
1165
- `AGENT_INFRA_CONTROL_TOKEN=${control.token}`,
1166
- '-e',
1167
1165
  `AGENT_INFRA_CONTROL_GENERATION=${control.generation}`,
1168
1166
  '-e',
1169
1167
  'AGENT_INFRA_CONTROL_DIR=/run/agent-infra/control',
@@ -1172,7 +1170,10 @@ export async function create(args, { runProjectInitCommand = true, workspace: wo
1172
1170
  '-e',
1173
1171
  `AGENT_INFRA_CONTROL_ROOT_ID=${control.controlRootId}`,
1174
1172
  ...(target.workspace.mode === 'task-bound'
1175
- ? ['-e', 'AGENT_INFRA_RUNTIME_DIR=/run/agent-infra/runtime']
1173
+ ? [
1174
+ '-e', 'AGENT_INFRA_CONTROL_ROOT_DIR=/run/agent-infra/control-root',
1175
+ '-e', 'AGENT_INFRA_RUNTIME_DIR=/run/agent-infra/runtime'
1176
+ ]
1176
1177
  : []),
1177
1178
  '--label',
1178
1179
  `${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
@@ -153,4 +153,16 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
153
153
  }
154
154
  };
155
155
  }
156
+ export function loadSandboxControlLocation(repoRoot = detectRepoRoot()) {
157
+ const configPath = path.join(repoRoot, '.agents', '.airc.json');
158
+ if (!fs.existsSync(configPath))
159
+ return null;
160
+ const airc = JSON.parse(fs.readFileSync(configPath, 'utf8'));
161
+ if (typeof airc.project !== 'string' || !airc.project)
162
+ return null;
163
+ const home = homedir();
164
+ if (!home)
165
+ throw new Error('sandbox: home directory is required');
166
+ return { controlBase: hostJoin(home, '.agent-infra', 'sandbox-control'), project: airc.project };
167
+ }
156
168
  //# sourceMappingURL=config.js.map
@@ -14,8 +14,7 @@ const SAFE_OPERATION = /^[a-z][a-z0-9.-]{0,96}$/u;
14
14
  const AUDIT_MAX_BYTES = 1024 * 1024;
15
15
  export function requestAuditFields(manifest, request) {
16
16
  return {
17
- requestId: request.id, requestFamily: request.family, sandboxTaskId: manifest.taskId,
18
- requestGeneration: request.generation, requestIssuedAt: request.issuedAt, requestExpiresAt: request.expiresAt
17
+ requestId: request.id, requestFamily: request.family, sandboxTaskId: manifest.taskId
19
18
  };
20
19
  }
21
20
  export function resultAuditFields(result) {