@fitlab-ai/agent-infra 0.9.1 → 0.9.2

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 (283) hide show
  1. package/README.md +16 -4
  2. package/README.zh-CN.md +16 -4
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +15 -0
  6. package/dist/lib/agent-client.js +163 -0
  7. package/dist/lib/agent-clients/adapter.js +294 -0
  8. package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
  9. package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
  10. package/dist/lib/agent-clients/adapters/codex.js +54 -0
  11. package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
  12. package/dist/lib/agent-clients/adapters/index.js +20 -0
  13. package/dist/lib/agent-clients/adapters/opencode.js +64 -0
  14. package/dist/lib/agent-clients/config.js +177 -0
  15. package/dist/lib/agent-clients/custom-tuis.js +99 -0
  16. package/dist/lib/agent-clients/invocation.js +9 -0
  17. package/dist/lib/agent-clients/next-steps.js +38 -0
  18. package/dist/lib/agent-clients/project-assets.js +45 -0
  19. package/dist/lib/agent-clients/reconcile.js +235 -0
  20. package/dist/lib/agent-clients/registry.js +72 -0
  21. package/dist/lib/agent-clients/schema.js +57 -0
  22. package/dist/lib/agent-clients/types.js +29 -0
  23. package/dist/lib/builtin-tuis.js +9 -14
  24. package/dist/lib/defaults.json +85 -73
  25. package/dist/lib/git/review-snapshot.js +25 -10
  26. package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
  27. package/dist/lib/init.js +47 -91
  28. package/dist/lib/internal/agent-client.js +117 -0
  29. package/dist/lib/internal/git-workflow.js +1 -1
  30. package/dist/lib/internal/release-workflow.js +71 -16
  31. package/dist/lib/internal/task-orchestration.js +151 -0
  32. package/dist/lib/internal/task-review.js +65 -0
  33. package/dist/lib/merge.js +2 -1
  34. package/dist/lib/platform/adapters.js +38 -1
  35. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  36. package/dist/lib/platform/merged-pr-equivalence.js +41 -11
  37. package/dist/lib/platform/pr-checks.js +38 -10
  38. package/dist/lib/platform/pull-requests.js +110 -6
  39. package/dist/lib/platform/releases.js +1 -1
  40. package/dist/lib/platform/verification-required.js +8 -10
  41. package/dist/lib/prompt.js +15 -3
  42. package/dist/lib/release/channels.js +4 -1
  43. package/dist/lib/release/workflow.js +4 -2
  44. package/dist/lib/remove-dir.js +25 -0
  45. package/dist/lib/run/tui.js +8 -2
  46. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  47. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  48. package/dist/lib/sandbox/commands/create.js +76 -180
  49. package/dist/lib/sandbox/commands/enter.js +17 -1
  50. package/dist/lib/sandbox/commands/prune.js +2 -2
  51. package/dist/lib/sandbox/commands/rm.js +3 -2
  52. package/dist/lib/sandbox/config.js +21 -4
  53. package/dist/lib/sandbox/constants.js +3 -0
  54. package/dist/lib/sandbox/dockerfile.js +2 -1
  55. package/dist/lib/sandbox/managed-fs.js +10 -5
  56. package/dist/lib/sandbox/recovery.js +52 -36
  57. package/dist/lib/sandbox/shell.js +1 -1
  58. package/dist/lib/sandbox/tool-types.js +8 -0
  59. package/dist/lib/sandbox/tools.js +2 -126
  60. package/dist/lib/task/artifact-lifecycle.js +1 -49
  61. package/dist/lib/task/commands/decisions.js +50 -20
  62. package/dist/lib/task/delegation-receipts.js +107 -0
  63. package/dist/lib/task/events.js +100 -1
  64. package/dist/lib/task/orchestration.js +420 -0
  65. package/dist/lib/task/review-artifacts.js +143 -59
  66. package/dist/lib/task/review-finalization.js +166 -0
  67. package/dist/lib/task/review-fingerprint.js +4 -0
  68. package/dist/lib/task/verification-checks.js +2 -1
  69. package/dist/lib/task/verification-engine.js +156 -28
  70. package/dist/lib/task/verification-review.js +1 -1
  71. package/dist/lib/task/verification.js +3 -1
  72. package/dist/lib/task/workspace-snapshot.js +34 -0
  73. package/dist/lib/update.js +42 -161
  74. package/lib/agent-client.ts +214 -0
  75. package/lib/agent-clients/adapter.ts +461 -0
  76. package/lib/agent-clients/adapters/claude-code.ts +70 -0
  77. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  78. package/lib/agent-clients/adapters/codex.ts +58 -0
  79. package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
  80. package/lib/agent-clients/adapters/index.ts +27 -0
  81. package/lib/agent-clients/adapters/opencode.ts +66 -0
  82. package/lib/agent-clients/config.ts +278 -0
  83. package/lib/agent-clients/custom-tuis.ts +149 -0
  84. package/lib/agent-clients/invocation.ts +19 -0
  85. package/lib/agent-clients/next-steps.ts +72 -0
  86. package/lib/agent-clients/project-assets.ts +96 -0
  87. package/lib/agent-clients/reconcile.ts +324 -0
  88. package/lib/agent-clients/registry.ts +144 -0
  89. package/lib/agent-clients/schema.ts +62 -0
  90. package/lib/agent-clients/types.ts +78 -0
  91. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  92. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  93. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  94. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  95. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  96. package/lib/builtin-tuis.ts +16 -15
  97. package/lib/defaults.json +24 -12
  98. package/lib/git/review-snapshot.ts +39 -9
  99. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  100. package/lib/init.ts +53 -121
  101. package/lib/internal/agent-client.ts +131 -0
  102. package/lib/internal/git-workflow.ts +1 -1
  103. package/lib/internal/release-workflow.ts +68 -16
  104. package/lib/internal/task-orchestration.ts +132 -0
  105. package/lib/internal/task-review.ts +65 -0
  106. package/lib/merge.ts +2 -1
  107. package/lib/platform/adapters.ts +142 -3
  108. package/lib/platform/change-request-git-evidence.ts +159 -0
  109. package/lib/platform/merged-pr-equivalence.ts +51 -14
  110. package/lib/platform/pr-checks.ts +50 -22
  111. package/lib/platform/pull-requests.ts +134 -25
  112. package/lib/platform/releases.ts +1 -1
  113. package/lib/platform/verification-required.ts +12 -10
  114. package/lib/prompt.ts +16 -3
  115. package/lib/release/channels.ts +4 -1
  116. package/lib/release/workflow.ts +4 -3
  117. package/lib/remove-dir.ts +28 -0
  118. package/lib/run/tui.ts +9 -1
  119. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  120. package/lib/sandbox/clipboard/linux.ts +2 -1
  121. package/lib/sandbox/commands/create.ts +111 -192
  122. package/lib/sandbox/commands/enter.ts +23 -1
  123. package/lib/sandbox/commands/prune.ts +2 -2
  124. package/lib/sandbox/commands/rm.ts +3 -2
  125. package/lib/sandbox/config.ts +31 -4
  126. package/lib/sandbox/constants.ts +6 -0
  127. package/lib/sandbox/dockerfile.ts +2 -1
  128. package/lib/sandbox/managed-fs.ts +18 -5
  129. package/lib/sandbox/recovery.ts +98 -67
  130. package/lib/sandbox/shell.ts +1 -1
  131. package/lib/sandbox/tool-types.ts +143 -0
  132. package/lib/sandbox/tools.ts +5 -157
  133. package/lib/task/artifact-lifecycle.ts +1 -38
  134. package/lib/task/commands/decisions.ts +53 -20
  135. package/lib/task/delegation-receipts.ts +170 -0
  136. package/lib/task/events.ts +110 -2
  137. package/lib/task/orchestration.ts +543 -0
  138. package/lib/task/review-artifacts.ts +201 -69
  139. package/lib/task/review-finalization.ts +275 -0
  140. package/lib/task/review-fingerprint.ts +5 -0
  141. package/lib/task/verification-checks.ts +2 -1
  142. package/lib/task/verification-engine.ts +177 -28
  143. package/lib/task/verification-review.ts +1 -0
  144. package/lib/task/verification.ts +5 -2
  145. package/lib/task/workspace-snapshot.ts +36 -0
  146. package/lib/update.ts +48 -184
  147. package/package.json +2 -2
  148. package/templates/.agents/README.en.md +21 -20
  149. package/templates/.agents/README.zh-CN.md +21 -20
  150. package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
  151. package/templates/.agents/rules/README.en.md +2 -0
  152. package/templates/.agents/rules/README.zh-CN.md +2 -0
  153. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  154. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  155. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  156. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  157. package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
  158. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
  159. package/templates/.agents/rules/next-step-output.en.md +16 -0
  160. package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
  161. package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
  162. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
  163. package/templates/.agents/rules/release-commands.github.en.md +11 -5
  164. package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -5
  165. package/templates/.agents/rules/review-handshake.en.md +4 -4
  166. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  167. package/templates/.agents/rules/review-method.en.md +113 -0
  168. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  169. package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
  170. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
  171. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  172. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
  173. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
  174. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
  175. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  176. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  177. package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
  178. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
  179. package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
  180. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
  181. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  182. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  183. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  184. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  185. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  186. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  187. package/templates/.agents/skills/commit/SKILL.en.md +11 -1
  188. package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
  189. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  190. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  191. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  192. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  193. package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
  194. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
  195. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  196. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  197. package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
  198. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
  199. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  200. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  201. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  202. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  203. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  204. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  205. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  206. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  207. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  208. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  209. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  210. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  211. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  212. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  213. package/templates/.agents/skills/release/SKILL.en.md +1 -1
  214. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
  215. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
  216. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  217. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  218. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  219. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  220. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  221. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  222. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  223. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  224. package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
  225. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
  226. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  227. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  228. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  229. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  230. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  231. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  232. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  233. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  234. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  235. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  236. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  237. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  238. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  239. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  240. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  241. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  242. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  243. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  244. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
  245. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
  246. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  247. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  248. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  249. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  250. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  251. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  252. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  253. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  254. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  255. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  256. package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
  257. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
  258. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  259. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  260. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  261. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  262. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  263. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  264. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
  265. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  266. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  267. package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
  268. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
  269. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  270. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  271. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  272. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  273. package/templates/.claude/commands/run-task.en.md +8 -0
  274. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  275. package/templates/.claude/settings.json +24 -0
  276. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  277. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  278. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  279. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  280. package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
  281. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
  282. package/templates/.opencode/commands/run-task.en.md +11 -0
  283. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
@@ -6,14 +6,15 @@ import { execFileSync } from 'node:child_process';
6
6
  import { parseArgs } from 'node:util';
7
7
  import * as p from '@clack/prompts';
8
8
  import pc from 'picocolors';
9
- import * as toml from 'smol-toml';
9
+ import { listAgentClientAdapters } from '../../agent-clients/registry.js';
10
10
  import { loadConfig } from '../config.js';
11
- import { assertValidBranchName, containerName, containerNameCandidates, parsePositiveIntegerOption, sandboxBranchLabel, sandboxImageConfigLabel, sandboxImageRefreshLabel, sandboxLabel, shareBranchDir, shareCommonDir, shellConfigDir, worktreeDirCandidates } from '../constants.js';
11
+ import { assertValidBranchName, containerName, containerNameCandidates, parsePositiveIntegerOption, sandboxBranchLabel, sandboxImageConfigLabel, sandboxImageRefreshLabel, sandboxLabel, sandboxRuntimeCapabilityLabel, shareBranchDir, shareCommonDir, shellConfigDir, worktreeDirCandidates } from '../constants.js';
12
12
  import { prepareDockerfile } from '../dockerfile.js';
13
13
  import { assertBuildProxyCompatibility, buildProxyFailureHint, prepareBuildProxy, redactBuildProxyValues } from '../build-proxy.js';
14
14
  import { detectEngine, ensureDocker } from '../engine.js';
15
15
  import { commandForEngine, execEngine, run, runEngine, runOk, runOkEngine, runSafe, runSafeEngine, runVerboseEngine } from '../shell.js';
16
16
  import { resolveTaskBranch } from '../task-resolver.js';
17
+ import { createSandboxCapabilityPlan, runSandboxHooks, runBoundedSandboxHookCommand } from '../agent-client-reconciler.js';
17
18
  import { resolveTools, tmpfsSeedStagingPath, tmpfsSeedTargetPath, toolConfigDirCandidates } from '../tools.js';
18
19
  import { assertFreshSandboxReady, hydrateTmpfsSeedEntries, prepareTmpfsMounts } from '../recovery.js';
19
20
  import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.js';
@@ -22,32 +23,13 @@ import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.
22
23
  import { validateSelinuxDisableEnv } from '../engines/selinux.js';
23
24
  import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.js';
24
25
  import { ensureSandboxDiscoveryReadmes } from '../readme-scaffold.js';
26
+ import { removeDirRecursive } from '../../remove-dir.js';
25
27
  import { prepareClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from '../credentials.js';
26
28
  import { detectHostTimezone } from '../host-timezone.js';
27
29
  import { buildImageSignature, buildSandboxImageArgs, isRefreshDisabled, isRefreshDue, parseImageLabels, parseRefreshTimestamp } from '../image-build.js';
28
- const OPENCODE_YOLO_PERMISSION = '{"*":"allow","read":"allow","bash":"allow","edit":"allow","webfetch":"allow","external_directory":"allow","doom_loop":"allow"}';
30
+ export { ensureCodexModelInheritance, ensureCodexWorkspaceTrust } from '../../agent-clients/adapters/codex-sandbox.js';
29
31
  const SANDBOX_ALIAS_BLOCK_BEGIN = '# >>> agent-infra managed aliases >>>';
30
32
  const SANDBOX_ALIAS_BLOCK_END = '# <<< agent-infra managed aliases <<<';
31
- const SANDBOX_ALIAS_NAMES = [
32
- 'claude-yolo',
33
- 'opencode-yolo',
34
- 'codex-yolo',
35
- 'gemini-yolo',
36
- 'cy',
37
- 'oy',
38
- 'xy',
39
- 'gy'
40
- ];
41
- const DEFAULT_SANDBOX_ALIASES = `alias claude-yolo='claude --dangerously-skip-permissions; tput ed'
42
- alias opencode-yolo='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
43
- alias codex-yolo='codex --yolo; tput ed'
44
- alias gemini-yolo='gemini --yolo; tput ed'
45
-
46
- alias cy='claude --dangerously-skip-permissions; tput ed'
47
- alias oy='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
48
- alias xy='codex --yolo; tput ed'
49
- alias gy='gemini --yolo; tput ed'
50
- `;
51
33
  const CONTAINER_HOME = '/home/devuser';
52
34
  const CONTAINER_SHELL_CONFIG_MOUNT = `${CONTAINER_HOME}/.host-shell-config`;
53
35
  const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh] [--inherit-proxy|-P] [--inherit-build-proxy|-B]
@@ -268,7 +250,7 @@ export function ensureShellConfigSymlinks(engine, container, execFn = execEngine
268
250
  }
269
251
  export function prepareHostShellConfig({ home, project, branch, repoRoot }) {
270
252
  const hostDir = hostShellConfigDir(home, project, branch);
271
- fs.rmSync(hostDir, { recursive: true, force: true });
253
+ removeDirRecursive(hostDir);
272
254
  fs.mkdirSync(hostDir, { recursive: true });
273
255
  writeSanitizedGitconfig({
274
256
  home,
@@ -306,11 +288,19 @@ function normalizeWorktreePath(worktreePath) {
306
288
  if (!worktreePath) {
307
289
  return '';
308
290
  }
291
+ const wslMount = process.platform === 'win32'
292
+ ? worktreePath.match(/^\/mnt\/([A-Za-z])(?:\/(.*))?$/)
293
+ : null;
294
+ const hostPath = wslMount
295
+ ? `${wslMount[1].toUpperCase()}:\\${(wslMount[2] ?? '').replace(/\//g, '\\')}`
296
+ : worktreePath;
309
297
  try {
310
- return fs.existsSync(worktreePath) ? fs.realpathSync(worktreePath) : path.resolve(worktreePath);
298
+ const normalized = fs.existsSync(hostPath) ? fs.realpathSync(hostPath) : path.resolve(hostPath);
299
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
311
300
  }
312
301
  catch {
313
- return path.resolve(worktreePath);
302
+ const normalized = path.resolve(hostPath);
303
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
314
304
  }
315
305
  }
316
306
  export function getGitSigningKey({ home, repoPath = null, execFn = execFileSync } = {}) {
@@ -481,7 +471,7 @@ export function collectHostProxyEntries(env) {
481
471
  });
482
472
  }
483
473
  export function buildContainerEnvFile(resolvedTools, engine, runSafeEngineFn = runSafeEngine, options = {}) {
484
- const { additionalEntries = [], mkdtempFn = fs.mkdtempSync, writeFileFn = fs.writeFileSync, chmodFn = fs.chmodSync, rmFn = fs.rmSync, tmpDir = os.tmpdir(), runSafeFn = runSafe } = options;
474
+ const { additionalEntries = [], mkdtempFn = fs.mkdtempSync, writeFileFn = fs.writeFileSync, chmodFn = fs.chmodSync, rmFn = removeDirRecursive, tmpDir = os.tmpdir(), runSafeFn = runSafe } = options;
485
475
  const entries = resolvedTools.flatMap(({ tool }) => Object.entries(tool.envVars ?? {}));
486
476
  entries.push(...additionalEntries);
487
477
  let ghToken = runSafeEngineFn(engine, 'gh', ['auth', 'token']);
@@ -705,141 +695,6 @@ export function ensureClaudeSettings(toolDir, hostHomeDir) {
705
695
  fs.writeFileSync(settingsPath, JSON.stringify(data, null, 4), 'utf8');
706
696
  }
707
697
  }
708
- function resolveHostCatalogPath(value, hostHomeDir) {
709
- if (typeof value !== 'string' || value === '') {
710
- return null;
711
- }
712
- let resolved;
713
- if (value === '~' || value.startsWith('~/') || value.startsWith('~\\')) {
714
- resolved = path.join(hostHomeDir, value.slice(1).replace(/^[/\\]+/, ''));
715
- }
716
- else if (path.isAbsolute(value)) {
717
- resolved = value;
718
- }
719
- else {
720
- resolved = path.join(hostHomeDir, '.codex', value);
721
- }
722
- try {
723
- if (!fs.statSync(resolved).isFile()) {
724
- return null;
725
- }
726
- fs.accessSync(resolved, fs.constants.R_OK);
727
- return resolved;
728
- }
729
- catch {
730
- return null;
731
- }
732
- }
733
- const CODEX_DISABLED_FEATURE_FLAGS = ['apps', 'enable_mcp_apps'];
734
- function removeCodexMcpServers(sandboxParsed) {
735
- if (!Object.hasOwn(sandboxParsed, 'mcp_servers')) {
736
- return false;
737
- }
738
- delete sandboxParsed.mcp_servers;
739
- return true;
740
- }
741
- function inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) {
742
- if (!isJsonObjectRecord(hostParsed.features)) {
743
- return false;
744
- }
745
- if (Object.hasOwn(sandboxParsed, 'features') && !isJsonObjectRecord(sandboxParsed.features)) {
746
- return false;
747
- }
748
- let sandboxFeatures = sandboxParsed.features;
749
- let changed = false;
750
- for (const key of CODEX_DISABLED_FEATURE_FLAGS) {
751
- if (hostParsed.features[key] !== false) {
752
- continue;
753
- }
754
- if (!sandboxFeatures) {
755
- sandboxFeatures = {};
756
- sandboxParsed.features = sandboxFeatures;
757
- }
758
- if (sandboxFeatures[key] !== false) {
759
- sandboxFeatures[key] = false;
760
- changed = true;
761
- }
762
- }
763
- return changed;
764
- }
765
- export function ensureCodexModelInheritance(toolDir, hostHomeDir, containerCodexDir = '/home/devuser/.codex') {
766
- if (!hostHomeDir) {
767
- return;
768
- }
769
- const hostConfigPath = path.join(hostHomeDir, '.codex', 'config.toml');
770
- if (!fs.existsSync(hostConfigPath)) {
771
- return;
772
- }
773
- let hostParsed;
774
- try {
775
- hostParsed = toml.parse(fs.readFileSync(hostConfigPath, 'utf8'));
776
- }
777
- catch {
778
- return;
779
- }
780
- const sandboxConfigPath = path.join(toolDir, 'config.toml');
781
- // This rewrites sandbox-side TOML and drops comments; the host config stays untouched.
782
- let sandboxParsed = {};
783
- if (fs.existsSync(sandboxConfigPath)) {
784
- try {
785
- sandboxParsed = toml.parse(fs.readFileSync(sandboxConfigPath, 'utf8'));
786
- }
787
- catch {
788
- return;
789
- }
790
- }
791
- let changed = removeCodexMcpServers(sandboxParsed);
792
- changed = inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) || changed;
793
- const inheritSpecs = [
794
- ['model', 'string'],
795
- ['model_reasoning_effort', 'string'],
796
- ['model_auto_compact_token_limit', 'number']
797
- ];
798
- for (const [key, type] of inheritSpecs) {
799
- if (Object.hasOwn(sandboxParsed, key)) {
800
- continue;
801
- }
802
- const value = hostParsed[key];
803
- if (type === 'string' && (typeof value !== 'string' || value === '')) {
804
- continue;
805
- }
806
- if (type === 'number' && (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
807
- continue;
808
- }
809
- sandboxParsed[key] = value;
810
- changed = true;
811
- }
812
- if (!Object.hasOwn(sandboxParsed, 'model_catalog_json')) {
813
- const hostCatalogPath = resolveHostCatalogPath(hostParsed['model_catalog_json'], hostHomeDir);
814
- if (hostCatalogPath) {
815
- try {
816
- const basename = path.basename(hostCatalogPath);
817
- const destDir = path.join(toolDir, 'model-catalogs');
818
- fs.mkdirSync(destDir, { recursive: true });
819
- fs.copyFileSync(hostCatalogPath, path.join(destDir, basename));
820
- sandboxParsed['model_catalog_json'] = path.posix.join(containerCodexDir, 'model-catalogs', basename);
821
- changed = true;
822
- }
823
- catch {
824
- // Copy failed (e.g. permissions): skip catalog, keep scalar inheritance intact.
825
- }
826
- }
827
- }
828
- if (changed) {
829
- fs.writeFileSync(sandboxConfigPath, `${toml.stringify(sandboxParsed)}\n`, 'utf8');
830
- }
831
- }
832
- export function ensureCodexWorkspaceTrust(toolDir) {
833
- const configPath = path.join(toolDir, 'config.toml');
834
- let content = '';
835
- if (fs.existsSync(configPath)) {
836
- content = fs.readFileSync(configPath, 'utf8');
837
- }
838
- if (!content.includes('[projects."/workspace"]')) {
839
- const entry = '\n[projects."/workspace"]\ntrust_level = "trusted"\n';
840
- fs.writeFileSync(configPath, content + entry, 'utf8');
841
- }
842
- }
843
698
  export function ensureOpenCodeModelInheritance(toolDir, hostHomeDir) {
844
699
  if (!hostHomeDir) {
845
700
  return;
@@ -900,7 +755,7 @@ function stripManagedSandboxAliasBlocks(content) {
900
755
  const blockPattern = new RegExp(`${escapeRegExp(SANDBOX_ALIAS_BLOCK_BEGIN)}[\\s\\S]*?${escapeRegExp(SANDBOX_ALIAS_BLOCK_END)}\\n?`, 'g');
901
756
  return content.replace(blockPattern, '').trimEnd();
902
757
  }
903
- function isLegacyManagedSandboxAliasFile(content) {
758
+ function isLegacyManagedSandboxAliasFile(content, aliases) {
904
759
  const lines = content
905
760
  .split(/\r?\n/)
906
761
  .map((line) => line.trim())
@@ -908,19 +763,25 @@ function isLegacyManagedSandboxAliasFile(content) {
908
763
  if (lines.length === 0) {
909
764
  return false;
910
765
  }
911
- const aliasPattern = new RegExp(`^alias (${SANDBOX_ALIAS_NAMES.map(escapeRegExp).join('|')})=`);
766
+ const aliasPattern = new RegExp(`^alias (${aliases.map((alias) => escapeRegExp(alias.name)).join('|')})=`);
912
767
  return lines.every((line) => aliasPattern.test(line));
913
768
  }
914
- export function ensureSandboxAliasesFile(home) {
769
+ function registrySandboxAliases() {
770
+ return listAgentClientAdapters().flatMap((adapter) => adapter.sandbox.aliases);
771
+ }
772
+ function renderSandboxAliases(aliases) {
773
+ return `${aliases.map((alias) => `alias ${alias.name}='${alias.command.replaceAll("'", "'\\''")}'`).join('\n')}\n`;
774
+ }
775
+ export function ensureSandboxAliasesFile(home, aliases = registrySandboxAliases()) {
915
776
  const aliasesPath = sandboxAliasesPath(home);
916
- const managedBlock = `${SANDBOX_ALIAS_BLOCK_BEGIN}\n${DEFAULT_SANDBOX_ALIASES}${SANDBOX_ALIAS_BLOCK_END}\n`;
777
+ const managedBlock = `${SANDBOX_ALIAS_BLOCK_BEGIN}\n${renderSandboxAliases(aliases)}${SANDBOX_ALIAS_BLOCK_END}\n`;
917
778
  fs.mkdirSync(path.dirname(aliasesPath), { recursive: true });
918
779
  const created = !fs.existsSync(aliasesPath);
919
780
  let existing = '';
920
781
  if (!created) {
921
782
  existing = fs.readFileSync(aliasesPath, 'utf8');
922
783
  }
923
- const userContent = isLegacyManagedSandboxAliasFile(existing)
784
+ const userContent = isLegacyManagedSandboxAliasFile(existing, aliases)
924
785
  ? ''
925
786
  : stripManagedSandboxAliasBlocks(existing);
926
787
  const nextContent = userContent
@@ -1018,15 +879,14 @@ export async function create(args) {
1018
879
  };
1019
880
  const worktreeCandidates = worktreeDirCandidates(effectiveConfig, branch);
1020
881
  assertBranchAvailable(config.repoRoot, branch, { allowedWorktrees: worktreeCandidates });
1021
- const tools = resolveTools(effectiveConfig);
882
+ const capabilityPlan = createSandboxCapabilityPlan(effectiveConfig);
883
+ const tools = [...capabilityPlan.tools];
1022
884
  const resolvedTools = resolveToolDirs(effectiveConfig, tools, branch);
1023
885
  // Fatal credential states still fail before filesystem/docker side effects.
1024
- // A genuinely missing Claude Code credential only removes Claude Code's
1025
- // sandbox config and credential mounts for this create run.
886
+ // Missing credentials leave the selected Claude Code capability installed
887
+ // and mounted; only the absent live credential file is omitted.
1026
888
  const credentialOutcome = prepareClaudeCredentials(effectiveConfig.home, effectiveConfig.project, resolvedTools);
1027
- const effectiveResolvedTools = credentialOutcome.status === 'SKIPPED'
1028
- ? resolvedTools.filter(({ tool }) => tool.id !== 'claude-code')
1029
- : resolvedTools;
889
+ const effectiveResolvedTools = resolvedTools;
1030
890
  const container = containerName(effectiveConfig, branch);
1031
891
  const worktree = worktreeCandidates.find((candidate) => fs.existsSync(candidate)) ?? worktreeCandidates[0] ?? '';
1032
892
  const shareCommon = shareCommonDir(effectiveConfig);
@@ -1045,6 +905,16 @@ export async function create(args) {
1045
905
  }
1046
906
  try {
1047
907
  p.log.step('Checking container engine...');
908
+ const prepareHookResults = await runSandboxHooks({
909
+ hooks: capabilityPlan.hooksByPhase.prepare,
910
+ phase: 'prepare',
911
+ context: { config: effectiveConfig, plan: capabilityPlan },
912
+ runCommand: runBoundedSandboxHookCommand
913
+ });
914
+ const prepareFailure = prepareHookResults.find((result) => result.status === 'fatal');
915
+ if (prepareFailure) {
916
+ throw new Error(prepareFailure.message ?? `Sandbox hook '${prepareFailure.hookId}' failed.`);
917
+ }
1048
918
  await ensureDocker(effectiveConfig, (detail) => {
1049
919
  p.log.info(` ${detail}`);
1050
920
  });
@@ -1111,7 +981,7 @@ export async function create(args) {
1111
981
  if (fs.readdirSync(worktree).length > 0) {
1112
982
  return `Worktree exists at ${worktree}`;
1113
983
  }
1114
- fs.rmSync(worktree, { recursive: true, force: true });
984
+ removeDirRecursive(worktree);
1115
985
  }
1116
986
  const branchExists = runOk('git', [
1117
987
  '-C',
@@ -1200,7 +1070,7 @@ export async function create(args) {
1200
1070
  runSafeEngine(engine, 'docker', ['rm', name]);
1201
1071
  }
1202
1072
  }
1203
- const aliasesFile = ensureSandboxAliasesFile(effectiveConfig.home);
1073
+ const aliasesFile = ensureSandboxAliasesFile(effectiveConfig.home, capabilityPlan.aliases);
1204
1074
  if (aliasesFile.created) {
1205
1075
  message(`Created default sandbox aliases at ${aliasesFile.path}`);
1206
1076
  }
@@ -1229,6 +1099,24 @@ export async function create(args) {
1229
1099
  let hostShellConfig;
1230
1100
  let tmpfsSeedPlan = [];
1231
1101
  try {
1102
+ const beforeCreateResults = await runSandboxHooks({
1103
+ hooks: capabilityPlan.hooksByPhase['before-container-create'],
1104
+ phase: 'before-container-create',
1105
+ context: {
1106
+ config: effectiveConfig,
1107
+ plan: capabilityPlan,
1108
+ create: {
1109
+ hostHome: effectiveConfig.home,
1110
+ resolvedTools: effectiveResolvedTools
1111
+ }
1112
+ },
1113
+ runCommand: runBoundedSandboxHookCommand
1114
+ });
1115
+ const beforeCreateFailure = beforeCreateResults.find((result) => result.status === 'fatal');
1116
+ if (beforeCreateFailure) {
1117
+ throw new Error(beforeCreateFailure.message
1118
+ ?? `Sandbox hook '${beforeCreateFailure.hookId}' failed.`);
1119
+ }
1232
1120
  const claudeCodeEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'claude-code');
1233
1121
  if (claudeCodeEntry) {
1234
1122
  ensureClaudeOnboarding(claudeCodeEntry.dir, effectiveConfig.home);
@@ -1237,11 +1125,6 @@ export async function create(args) {
1237
1125
  // provider/API settings are enough for Claude Code. If no usable
1238
1126
  // auth was present, the claude-code entry was removed above.
1239
1127
  }
1240
- const codexEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'codex');
1241
- if (codexEntry) {
1242
- ensureCodexModelInheritance(codexEntry.dir, effectiveConfig.home, codexEntry.tool.containerMount);
1243
- ensureCodexWorkspaceTrust(codexEntry.dir);
1244
- }
1245
1128
  const geminiEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'gemini-cli');
1246
1129
  if (geminiEntry) {
1247
1130
  ensureGeminiWorkspaceTrust(geminiEntry.dir);
@@ -1315,6 +1198,8 @@ export async function create(args) {
1315
1198
  sandboxLabel(effectiveConfig),
1316
1199
  '--label',
1317
1200
  `${sandboxBranchLabel(effectiveConfig)}=${branch}`,
1201
+ '--label',
1202
+ `${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
1318
1203
  ...coreVolumes,
1319
1204
  ...buildClipboardVolumeArgs(engine, effectiveConfig.home),
1320
1205
  '-v',
@@ -1392,6 +1277,17 @@ export async function create(args) {
1392
1277
  runSafeEngine(engine, 'docker', ['exec', container, 'bash', '-lc', command]);
1393
1278
  }
1394
1279
  }
1280
+ const afterStartResults = await runSandboxHooks({
1281
+ hooks: capabilityPlan.hooksByPhase['after-container-start'],
1282
+ phase: 'after-container-start',
1283
+ context: { config: effectiveConfig, plan: capabilityPlan },
1284
+ runCommand: runBoundedSandboxHookCommand
1285
+ });
1286
+ const afterStartFailure = afterStartResults.find((result) => result.status === 'fatal');
1287
+ if (afterStartFailure) {
1288
+ throw new Error(afterStartFailure.message
1289
+ ?? `Sandbox hook '${afterStartFailure.hookId}' failed.`);
1290
+ }
1395
1291
  return 'Container started';
1396
1292
  }
1397
1293
  }
@@ -2,6 +2,7 @@ import { loadConfig } from '../config.js';
2
2
  import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel } from '../constants.js';
3
3
  import { detectEngine } from '../engine.js';
4
4
  import { ensureSandboxReady } from '../recovery.js';
5
+ import { createSandboxCapabilityPlan, runBoundedSandboxHookCommand, runSandboxHooks } from '../agent-client-reconciler.js';
5
6
  import { formatCredentialWarnings, formatRemaining, hasClaudeProviderAuth, reconcileClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from '../credentials.js';
6
7
  import { runInteractiveEngine } from '../shell.js';
7
8
  import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.js';
@@ -123,7 +124,22 @@ export async function enter(args) {
123
124
  });
124
125
  const container = ready.container;
125
126
  const cmd = parsed.command;
126
- if (config.tools.includes('claude-code')) {
127
+ const capabilityPlan = createSandboxCapabilityPlan(config);
128
+ const enterHookResults = await runSandboxHooks({
129
+ hooks: capabilityPlan.hooksByPhase['before-enter'],
130
+ phase: 'before-enter',
131
+ context: { config, plan: capabilityPlan },
132
+ runCommand: runBoundedSandboxHookCommand
133
+ });
134
+ for (const result of enterHookResults) {
135
+ if (result.status === 'fatal') {
136
+ throw new Error(result.message ?? `Sandbox hook '${result.hookId}' failed.`);
137
+ }
138
+ if (result.status === 'warning') {
139
+ process.stderr.write(`Warning: ${result.message ?? `Sandbox hook '${result.hookId}' did not complete.`}\n`);
140
+ }
141
+ }
142
+ if (capabilityPlan.selectedAgentClients.some((adapter) => adapter.id === 'claude-code')) {
127
143
  try {
128
144
  // Scan all projects so a refresh from a neighbouring sandbox can still flow back to the host.
129
145
  const providerAuthAvailable = hasClaudeProviderAuth(config.home);
@@ -10,7 +10,7 @@ import { hostJoin } from '../engines/wsl2-paths.js';
10
10
  import { removeManagedDir, removeWorktreeDir } from '../managed-fs.js';
11
11
  import { parseLabels } from './ls.js';
12
12
  import { runEngine, runSafe } from '../shell.js';
13
- import { resolveTools } from '../tools.js';
13
+ import { createSandboxCapabilityPlan } from '../agent-client-reconciler.js';
14
14
  const USAGE = `Usage: ai sandbox prune [--dry-run]`;
15
15
  function listChildDirs(base) {
16
16
  if (!fs.existsSync(base)) {
@@ -129,7 +129,7 @@ export async function prune(args) {
129
129
  return;
130
130
  }
131
131
  const config = loadConfig();
132
- const tools = resolveTools(config);
132
+ const tools = [...createSandboxCapabilityPlan(config).cleanupInventory];
133
133
  const engine = detectEngine(config);
134
134
  const psArgs = [
135
135
  'ps',
@@ -10,7 +10,8 @@ import { pruneSandboxDanglingImages } from '../image-prune.js';
10
10
  import { removeManagedDir, removeWorktreeDir } from '../managed-fs.js';
11
11
  import { runOk, runSafe, runSafeEngine } from '../shell.js';
12
12
  import { resolveTaskBranch } from '../task-resolver.js';
13
- import { resolveTools, toolConfigDirCandidates, toolProjectDirCandidates } from '../tools.js';
13
+ import { toolConfigDirCandidates, toolProjectDirCandidates } from '../tools.js';
14
+ import { createSandboxCapabilityPlan } from '../agent-client-reconciler.js';
14
15
  import { fetchSandboxRows } from './list-running.js';
15
16
  import { lookupShortIdByBranch } from '../../task/short-id.js';
16
17
  const USAGE = `Usage:
@@ -284,7 +285,7 @@ export async function rm(args) {
284
285
  throw new Error(USAGE);
285
286
  }
286
287
  const config = loadConfig();
287
- const tools = resolveTools(config);
288
+ const tools = [...createSandboxCapabilityPlan(config).cleanupInventory];
288
289
  if (values.purge) {
289
290
  await rmPurge(config, tools);
290
291
  return;
@@ -3,6 +3,8 @@ import path from 'node:path';
3
3
  import { homedir, platform } from 'node:os';
4
4
  import { execFileSync } from 'node:child_process';
5
5
  import pc from 'picocolors';
6
+ import { normalizeAgentClients } from '../agent-clients/config.js';
7
+ import { isAgentClientId } from '../agent-clients/types.js';
6
8
  import { validateSandboxEngine } from './engine.js';
7
9
  import { hostJoin } from './engines/wsl2-paths.js';
8
10
  import { findRuntimeEngineMismatches } from './runtime-engines.js';
@@ -10,7 +12,7 @@ import { parseCustomTools } from './tools.js';
10
12
  const DEFAULTS = Object.freeze({
11
13
  engine: null,
12
14
  runtimes: ['node22'],
13
- tools: ['agent-infra', 'claude-code', 'codex', 'gemini-cli', 'opencode'],
15
+ tools: ['agent-infra'],
14
16
  refreshIntervalDays: 7,
15
17
  dockerfile: null,
16
18
  vm: {
@@ -46,6 +48,18 @@ function cloneDefaults() {
46
48
  vm: { ...DEFAULTS.vm }
47
49
  };
48
50
  }
51
+ function resolveSandboxToolIds(sandbox, agentClients, defaults) {
52
+ if (Array.isArray(sandbox.tools)) {
53
+ const tools = sandbox.tools.filter((tool) => typeof tool === 'string');
54
+ return agentClients.source === 'canonical'
55
+ ? tools.filter((tool) => !isAgentClientId(tool))
56
+ : tools;
57
+ }
58
+ if (agentClients.remainingSandboxTools !== undefined) {
59
+ return [...agentClients.remainingSandboxTools];
60
+ }
61
+ return [...defaults];
62
+ }
49
63
  export function loadConfig({ platformFn = platform, writeStderr = (chunk) => process.stderr.write(chunk) } = {}) {
50
64
  const repoRoot = detectRepoRoot();
51
65
  const home = homedir();
@@ -57,6 +71,8 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
57
71
  throw new Error('No .agents/.airc.json found. Run "ai init" first.');
58
72
  }
59
73
  const airc = JSON.parse(fs.readFileSync(configPath, 'utf8'));
74
+ const sandboxInput = airc.sandbox;
75
+ const agentClients = normalizeAgentClients(airc);
60
76
  const defaults = cloneDefaults();
61
77
  const sandbox = airc.sandbox ?? {};
62
78
  const engine = validateSandboxEngine(sandbox.engine ?? defaults.engine, { platformFn });
@@ -83,6 +99,7 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
83
99
  }
84
100
  }
85
101
  const customTools = parseCustomTools(sandbox.customTools, { home });
102
+ const tools = resolveSandboxToolIds(sandbox, agentClients, defaults.tools);
86
103
  return {
87
104
  repoRoot,
88
105
  configPath,
@@ -97,10 +114,10 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
97
114
  dotfilesDir: hostJoin(home, '.agent-infra', 'dotfiles'),
98
115
  engine,
99
116
  runtimes,
100
- tools: Array.isArray(sandbox.tools) && sandbox.tools.length > 0
101
- ? [...sandbox.tools]
102
- : defaults.tools,
117
+ tools,
103
118
  customTools,
119
+ agentClientState: agentClients.state,
120
+ agentClientSource: agentClients.source,
104
121
  refreshIntervalDays: asNonNegativeIntegerOrDefault(sandbox.refreshIntervalDays, defaults.refreshIntervalDays),
105
122
  dockerfile,
106
123
  vm: {
@@ -75,6 +75,9 @@ export function sandboxImageConfigLabel(config) {
75
75
  export function sandboxImageRefreshLabel(config) {
76
76
  return `${sandboxLabel(config)}.last-refresh`;
77
77
  }
78
+ export function sandboxRuntimeCapabilityLabel(config) {
79
+ return `agent-infra.sandbox.runtime-capability.${config.project}`;
80
+ }
78
81
  export function parsePositiveIntegerOption(value, optionName) {
79
82
  if (value === undefined || value === null) {
80
83
  return undefined;
@@ -3,6 +3,7 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { createHash } from 'node:crypto';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { removeDirRecursive } from '../remove-dir.js';
6
7
  const RUNTIMES_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), 'runtimes');
7
8
  function listRuntimeFragments() {
8
9
  return fs.readdirSync(RUNTIMES_DIR)
@@ -63,7 +64,7 @@ export function prepareDockerfile(config) {
63
64
  path: tempPath,
64
65
  signature: dockerfileSignature(config),
65
66
  cleanup() {
66
- fs.rmSync(tempDir, { recursive: true, force: true });
67
+ removeDirRecursive(tempDir);
67
68
  }
68
69
  };
69
70
  }
@@ -1,6 +1,6 @@
1
- import fs from 'node:fs';
2
1
  import path from 'node:path';
3
- import { run } from './shell.js';
2
+ import { run, runSafe } from './shell.js';
3
+ import { removeDirRecursive } from '../remove-dir.js';
4
4
  export function assertManagedPath(root, target) {
5
5
  const resolvedRoot = path.resolve(root);
6
6
  const resolvedTarget = path.resolve(target);
@@ -12,14 +12,19 @@ export function assertManagedPath(root, target) {
12
12
  }
13
13
  export function removeManagedDir(root, dir) {
14
14
  assertManagedPath(root, dir);
15
- fs.rmSync(dir, { recursive: true, force: true });
15
+ removeDirRecursive(dir);
16
16
  }
17
- export function removeWorktreeDir(repoRoot, worktreeBase, dir) {
17
+ export function removeWorktreeDir(repoRoot, worktreeBase, dir, { runFn = run, runSafeFn = runSafe } = {}) {
18
18
  try {
19
- run('git', ['-C', repoRoot, 'worktree', 'remove', dir, '--force']);
19
+ runFn('git', ['-C', repoRoot, 'worktree', 'remove', dir, '--force']);
20
20
  }
21
21
  catch {
22
+ // On WSL2 the worktree is registered under its `/mnt/<drive>/...` path, so
23
+ // `git worktree remove <windows-path>` cannot match it. Delete the managed
24
+ // directory directly, then prune the now-dangling worktree metadata so the
25
+ // branch is no longer reported as checked out.
22
26
  removeManagedDir(worktreeBase, dir);
27
+ runSafeFn('git', ['-C', repoRoot, 'worktree', 'prune']);
23
28
  }
24
29
  }
25
30
  //# sourceMappingURL=managed-fs.js.map