@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
@@ -1,6 +1,6 @@
1
- import fs from 'node:fs';
2
1
  import path from 'node:path';
3
- import { run } from './shell.ts';
2
+ import { run, runSafe } from './shell.ts';
3
+ import { removeDirRecursive } from '../remove-dir.ts';
4
4
 
5
5
  export function assertManagedPath(root: string, target: string): void {
6
6
  const resolvedRoot = path.resolve(root);
@@ -15,13 +15,26 @@ export function assertManagedPath(root: string, target: string): void {
15
15
 
16
16
  export function removeManagedDir(root: string, dir: string): void {
17
17
  assertManagedPath(root, dir);
18
- fs.rmSync(dir, { recursive: true, force: true });
18
+ removeDirRecursive(dir);
19
19
  }
20
20
 
21
- export function removeWorktreeDir(repoRoot: string, worktreeBase: string, dir: string): void {
21
+ export function removeWorktreeDir(
22
+ repoRoot: string,
23
+ worktreeBase: string,
24
+ dir: string,
25
+ { runFn = run, runSafeFn = runSafe }: {
26
+ runFn?: typeof run;
27
+ runSafeFn?: typeof runSafe;
28
+ } = {}
29
+ ): void {
22
30
  try {
23
- run('git', ['-C', repoRoot, 'worktree', 'remove', dir, '--force']);
31
+ runFn('git', ['-C', repoRoot, 'worktree', 'remove', dir, '--force']);
24
32
  } catch {
33
+ // On WSL2 the worktree is registered under its `/mnt/<drive>/...` path, so
34
+ // `git worktree remove <windows-path>` cannot match it. Delete the managed
35
+ // directory directly, then prune the now-dangling worktree metadata so the
36
+ // branch is no longer reported as checked out.
25
37
  removeManagedDir(worktreeBase, dir);
38
+ runSafeFn('git', ['-C', repoRoot, 'worktree', 'prune']);
26
39
  }
27
40
  }
@@ -1,6 +1,19 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { containerNameCandidates, sandboxBranchLabel, sandboxLabel } from './constants.ts';
3
+ import {
4
+ containerNameCandidates,
5
+ sandboxBranchLabel,
6
+ sandboxLabel,
7
+ sandboxRuntimeCapabilityLabel
8
+ } from './constants.ts';
9
+ import { isAgentClientId } from '../agent-clients/types.ts';
10
+ import {
11
+ createSandboxCapabilityPlan
12
+ } from './agent-client-reconciler.ts';
13
+ import type {
14
+ SandboxRecoveryFindingDescriptor,
15
+ SandboxRecoveryRepair
16
+ } from './tool-types.ts';
4
17
  import type { SandboxConfig } from './config.ts';
5
18
  import { toEnginePath } from './engines/wsl2-paths.ts';
6
19
  import { sandboxCoreBindMounts } from './mounts.ts';
@@ -9,6 +22,7 @@ import {
9
22
  declaredTmpfsSeedEntries,
10
23
  resolveTools,
11
24
  toolConfigDirCandidates,
25
+ type SandboxTool,
12
26
  type TmpfsSeedEntry
13
27
  } from './tools.ts';
14
28
  import {
@@ -23,10 +37,22 @@ export type SandboxRecoveryFinding = {
23
37
  message: string;
24
38
  path?: string;
25
39
  seed?: TmpfsSeedEntry;
40
+ repair?: SandboxRecoveryRepair;
41
+ };
42
+
43
+ type SandboxAgentClientCheckSnapshot = {
44
+ adapterId: string;
45
+ checkId: string;
46
+ applicable: boolean;
47
+ healthy: boolean;
48
+ finding: SandboxRecoveryFindingDescriptor;
49
+ repair?: SandboxRecoveryRepair;
26
50
  };
27
51
 
28
52
  export type SandboxRecoverySnapshot = {
29
53
  identityOk: boolean;
54
+ runtimeCapabilityOk?: boolean;
55
+ unexpectedCapabilityMounts?: string[];
30
56
  mounts: Array<{
31
57
  path: string;
32
58
  expectedType: string;
@@ -48,12 +74,7 @@ export type SandboxRecoverySnapshot = {
48
74
  targetState: 'ok' | 'missing' | 'wrong-type' | 'inaccessible';
49
75
  }>;
50
76
  aliasesReadable: boolean;
51
- codex?: {
52
- commandAvailable: boolean;
53
- stateWritable: boolean;
54
- promptsSourceExists: boolean;
55
- promptsValid: boolean;
56
- };
77
+ agentClientChecks: SandboxAgentClientCheckSnapshot[];
57
78
  };
58
79
 
59
80
  export type SandboxReadyResult = {
@@ -114,6 +135,19 @@ export function classifySandboxRecovery(snapshot: SandboxRecoverySnapshot): Sand
114
135
  message: 'Container identity does not match the requested sandbox branch.'
115
136
  });
116
137
  }
138
+ if (snapshot.runtimeCapabilityOk === false) {
139
+ findings.push({
140
+ repairKind: 'hard-failure',
141
+ message: 'Container runtime capability signature does not match the current sandbox plan.'
142
+ });
143
+ }
144
+ for (const mountPath of snapshot.unexpectedCapabilityMounts ?? []) {
145
+ findings.push({
146
+ repairKind: 'hard-failure',
147
+ message: `Disabled Agent Client capability remains mounted at ${mountPath}.`,
148
+ path: mountPath
149
+ });
150
+ }
117
151
 
118
152
  for (const mount of snapshot.mounts) {
119
153
  if (
@@ -177,25 +211,11 @@ export function classifySandboxRecovery(snapshot: SandboxRecoverySnapshot): Sand
177
211
  });
178
212
  }
179
213
 
180
- if (snapshot.codex) {
181
- if (!snapshot.codex.commandAvailable) {
214
+ for (const check of snapshot.agentClientChecks) {
215
+ if (check.applicable && !check.healthy) {
182
216
  findings.push({
183
- repairKind: 'hard-failure',
184
- message: 'Codex is not available on PATH inside the sandbox.'
185
- });
186
- }
187
- if (!snapshot.codex.stateWritable) {
188
- findings.push({
189
- repairKind: 'permissions',
190
- message: 'Codex state directory is not writable by devuser.',
191
- path: '/home/devuser/.codex'
192
- });
193
- }
194
- if (snapshot.codex.promptsSourceExists && !snapshot.codex.promptsValid) {
195
- findings.push({
196
- repairKind: 'builtin-link',
197
- message: 'Codex prompts link does not point to the workspace commands directory.',
198
- path: '/home/devuser/.codex/prompts'
217
+ ...check.finding,
218
+ ...(check.repair === undefined ? {} : { repair: check.repair })
199
219
  });
200
220
  }
201
221
  }
@@ -277,11 +297,10 @@ function hostSourceAccessible(hostPath: string, writable: boolean): boolean {
277
297
  function expectedMounts(params: {
278
298
  config: SandboxConfig;
279
299
  branch: string;
280
- engine: string;
300
+ tools: readonly SandboxTool[];
281
301
  actualMounts: Map<string, DockerMount>;
282
302
  }): ExpectedMount[] {
283
- const { config, branch, actualMounts } = params;
284
- const tools = resolveTools(config);
303
+ const { config, branch, tools, actualMounts } = params;
285
304
  const core = sandboxCoreBindMounts(config, branch).map((mount) => ({
286
305
  path: mount.containerPath,
287
306
  expectedType: 'bind' as const,
@@ -301,7 +320,13 @@ function expectedMounts(params: {
301
320
  })
302
321
  );
303
322
  const persistentTools = tools
304
- .filter((tool) => !tool.tmpfs && actualMounts.has(tool.containerMount))
323
+ .filter((tool) =>
324
+ !tool.tmpfs
325
+ && (
326
+ config.agentClientSource === 'canonical'
327
+ || actualMounts.has(tool.containerMount)
328
+ )
329
+ )
305
330
  .map((tool) => ({
306
331
  path: tool.containerMount,
307
332
  expectedType: 'bind' as const,
@@ -401,7 +426,8 @@ export function collectSandboxRecoverySnapshot(params: {
401
426
  });
402
427
  }
403
428
  }
404
- const tools = resolveTools(params.config);
429
+ const capabilityPlan = createSandboxCapabilityPlan(params.config);
430
+ const tools = [...capabilityPlan.tools];
405
431
  const seeds = declaredTmpfsSeedEntries(tools).map((seed) => {
406
432
  const mounted = mountsByDestination.get(seed.stagingPath)?.Type === 'bind';
407
433
  return {
@@ -434,11 +460,18 @@ export function collectSandboxRecoverySnapshot(params: {
434
460
  }));
435
461
  const labels = inspection.Config?.Labels ?? {};
436
462
  const branchLabel = labels[sandboxBranchLabel(params.config)];
437
- const hasCodex = tools.some((tool) => tool.id === 'codex');
463
+ const selectedToolIds = new Set(tools.map((tool) => tool.id));
464
+ const enforceCanonicalPlan = params.config.agentClientSource === 'canonical';
465
+ const unexpectedCapabilityMounts = enforceCanonicalPlan
466
+ ? capabilityPlan.cleanupInventory
467
+ .filter((tool) => isAgentClientId(tool.id) && !selectedToolIds.has(tool.id))
468
+ .map((tool) => tool.containerMount)
469
+ .filter((mountPath) => mountsByDestination.has(mountPath))
470
+ : [];
438
471
  const mountSnapshots = expectedMounts({
439
472
  config: params.config,
440
473
  branch: params.branch,
441
- engine: params.engine,
474
+ tools,
442
475
  actualMounts: mountsByDestination
443
476
  }).map((expected) => {
444
477
  const actual = mountsByDestination.get(expected.path);
@@ -478,6 +511,10 @@ export function collectSandboxRecoverySnapshot(params: {
478
511
  identityOk: typeof inspection.Id === 'string'
479
512
  && inspection.Id.length > 0
480
513
  && branchLabel === params.branch,
514
+ runtimeCapabilityOk: enforceCanonicalPlan
515
+ ? labels[sandboxRuntimeCapabilityLabel(params.config)] === capabilityPlan.runtimeSignature
516
+ : undefined,
517
+ unexpectedCapabilityMounts,
481
518
  mounts: mountSnapshots,
482
519
  tmpfs,
483
520
  seeds,
@@ -488,38 +525,31 @@ export function collectSandboxRecoverySnapshot(params: {
488
525
  ['/home/devuser/.bash_aliases'],
489
526
  runOkFn
490
527
  ),
491
- codex: hasCodex
492
- ? {
493
- commandAvailable: probe(
494
- params.engine,
495
- params.container,
496
- 'command -v codex',
497
- [],
498
- runOkFn
499
- ),
500
- stateWritable: probe(
501
- params.engine,
502
- params.container,
503
- 'probe="$1/.agent-infra-codex-state-$$"; trap \'rm -f -- "$probe"\' EXIT; : > "$probe"',
504
- ['/home/devuser/.codex'],
505
- runOkFn
506
- ),
507
- promptsSourceExists: probe(
508
- params.engine,
509
- params.container,
510
- 'test -d "$1"',
511
- ['/workspace/.codex/commands'],
512
- runOkFn
513
- ),
514
- promptsValid: probe(
515
- params.engine,
516
- params.container,
517
- 'test "$(readlink -- "$1")" = "$2"',
518
- ['/home/devuser/.codex/prompts', '/workspace/.codex/commands'],
519
- runOkFn
520
- )
521
- }
522
- : undefined
528
+ agentClientChecks: capabilityPlan.recoveryChecks.map(({ adapterId, check }) => {
529
+ const applicable = check.when === undefined || probe(
530
+ params.engine,
531
+ params.container,
532
+ check.when.script,
533
+ [...check.when.args],
534
+ runOkFn,
535
+ check.when.user
536
+ );
537
+ return {
538
+ adapterId,
539
+ checkId: check.id,
540
+ applicable,
541
+ healthy: !applicable || probe(
542
+ params.engine,
543
+ params.container,
544
+ check.probe.script,
545
+ [...check.probe.args],
546
+ runOkFn,
547
+ check.probe.user
548
+ ),
549
+ finding: check.finding,
550
+ ...(check.repair === undefined ? {} : { repair: check.repair })
551
+ };
552
+ })
523
553
  };
524
554
  }
525
555
 
@@ -626,10 +656,11 @@ function repairFindings(params: {
626
656
  deps: params.deps
627
657
  });
628
658
  const runVerboseFn = params.deps?.runVerbose ?? runVerboseEngine;
629
- if (params.findings.some((finding) => finding.repairKind === 'builtin-link')) {
659
+ for (const repair of params.findings.flatMap((finding) =>
660
+ finding.repair === undefined ? [] : [finding.repair]
661
+ )) {
630
662
  runVerboseFn(params.engine, 'docker', [
631
- 'exec', '--user', 'devuser', params.container, 'ln', '-sfn',
632
- '/workspace/.codex/commands', '/home/devuser/.codex/prompts'
663
+ 'exec', '--user', repair.user, params.container, repair.command, ...repair.args
633
664
  ]);
634
665
  }
635
666
  }
@@ -143,7 +143,7 @@ export function runSafe(cmd: string, args: string[], opts: CommandOptions = {}):
143
143
  export function commandForEngine(engine: string, cmd: string, args: string[] = []): { cmd: string; args: string[] } {
144
144
  if (engine === 'wsl2') {
145
145
  const resolvedWrapper = resolveCommand('wsl.exe');
146
- return { cmd: resolvedWrapper, args: ['--', cmd, ...args] };
146
+ return { cmd: resolvedWrapper, args: ['--exec', cmd, ...args] };
147
147
  }
148
148
 
149
149
  if (cmd === 'docker') {
@@ -0,0 +1,143 @@
1
+ type SandboxToolInstall =
2
+ | Readonly<{ type: 'npm'; cmd: string }>
3
+ | Readonly<{ type: 'shell'; cmd: string }>;
4
+
5
+ type SandboxTool = {
6
+ id: string;
7
+ name: string;
8
+ install: SandboxToolInstall;
9
+ sandboxBase: string;
10
+ containerMount: string;
11
+ versionCmd: string;
12
+ setupHint: string;
13
+ envVars?: Record<string, string>;
14
+ hostPreSeedFiles?: Array<{ hostPath: string; sandboxName: string }>;
15
+ hostPreSeedDirs?: Array<{ hostDir: string; sandboxSubdir: string }>;
16
+ pathRewriteFiles?: string[];
17
+ hostLiveMounts?: Array<{ hostPath: string; containerSubpath: string }>;
18
+ postSetupCmds?: string[];
19
+ tmpfs?: { size?: string; seed?: string[] };
20
+ };
21
+
22
+ type SandboxAlias = Readonly<{
23
+ name: string;
24
+ command: string;
25
+ }>;
26
+
27
+ const SANDBOX_HOOK_PHASES = [
28
+ 'prepare',
29
+ 'before-container-create',
30
+ 'after-container-start',
31
+ 'before-enter'
32
+ ] as const;
33
+
34
+ type SandboxHookPhase = (typeof SANDBOX_HOOK_PHASES)[number];
35
+
36
+ type SandboxHookStatus =
37
+ | 'ready'
38
+ | 'skip-client-runtime'
39
+ | 'warning'
40
+ | 'fatal'
41
+ | 'healthy'
42
+ | 'hard-failure';
43
+
44
+ type SandboxHookCommand = Readonly<{
45
+ command: string;
46
+ args?: readonly string[];
47
+ cwd?: string;
48
+ env?: NodeJS.ProcessEnv;
49
+ }>;
50
+
51
+ type SandboxHookCommandResult = Readonly<{
52
+ stdout: string;
53
+ }>;
54
+
55
+ type AgentClientSandboxHookResult = Readonly<{
56
+ status: SandboxHookStatus;
57
+ message?: string;
58
+ }>;
59
+
60
+ type SandboxResolvedToolState = Readonly<{
61
+ tool: SandboxTool;
62
+ dir: string;
63
+ }>;
64
+
65
+ type SandboxHookCreateContext = Readonly<{
66
+ hostHome: string;
67
+ resolvedTools: readonly SandboxResolvedToolState[];
68
+ }>;
69
+
70
+ type AgentClientSandboxHookContext = Readonly<{
71
+ signal: AbortSignal;
72
+ runCommand(command: SandboxHookCommand): Promise<SandboxHookCommandResult>;
73
+ config?: Readonly<Record<string, unknown>>;
74
+ plan?: Readonly<Record<string, unknown>>;
75
+ create?: SandboxHookCreateContext;
76
+ }>;
77
+
78
+ type AgentClientSandboxHook = Readonly<{
79
+ id: string;
80
+ phase: SandboxHookPhase;
81
+ timeoutMs?: number;
82
+ run(context: AgentClientSandboxHookContext): Promise<AgentClientSandboxHookResult>;
83
+ }>;
84
+
85
+ type SandboxToolContext = Readonly<{
86
+ home: string;
87
+ project: string;
88
+ }>;
89
+
90
+ type SandboxRecoveryProbe = Readonly<{
91
+ script: string;
92
+ args: readonly string[];
93
+ user?: string;
94
+ }>;
95
+
96
+ type SandboxRecoveryFindingDescriptor = Readonly<{
97
+ repairKind: 'permissions' | 'builtin-link' | 'hard-failure';
98
+ message: string;
99
+ path?: string;
100
+ }>;
101
+
102
+ type SandboxRecoveryRepair = Readonly<{
103
+ user: string;
104
+ command: string;
105
+ args: readonly string[];
106
+ }>;
107
+
108
+ type AgentClientSandboxRecoveryCheck = Readonly<{
109
+ id: string;
110
+ when?: SandboxRecoveryProbe;
111
+ probe: SandboxRecoveryProbe;
112
+ finding: SandboxRecoveryFindingDescriptor;
113
+ repair?: SandboxRecoveryRepair;
114
+ }>;
115
+
116
+ type AgentClientSandboxDescriptor = Readonly<{
117
+ createTool(context: SandboxToolContext): SandboxTool;
118
+ aliases: readonly SandboxAlias[];
119
+ hooks: readonly AgentClientSandboxHook[];
120
+ recoveryChecks?: readonly AgentClientSandboxRecoveryCheck[];
121
+ }>;
122
+
123
+ export { SANDBOX_HOOK_PHASES };
124
+ export type {
125
+ AgentClientSandboxDescriptor,
126
+ AgentClientSandboxHook,
127
+ AgentClientSandboxHookContext,
128
+ AgentClientSandboxHookResult,
129
+ AgentClientSandboxRecoveryCheck,
130
+ SandboxAlias,
131
+ SandboxHookCommand,
132
+ SandboxHookCommandResult,
133
+ SandboxHookPhase,
134
+ SandboxHookStatus,
135
+ SandboxHookCreateContext,
136
+ SandboxRecoveryFindingDescriptor,
137
+ SandboxRecoveryProbe,
138
+ SandboxRecoveryRepair,
139
+ SandboxResolvedToolState,
140
+ SandboxTool,
141
+ SandboxToolContext,
142
+ SandboxToolInstall
143
+ };
@@ -1,33 +1,10 @@
1
1
  import path from 'node:path';
2
+ import { createSandboxCapabilityPlan } from './agent-client-reconciler.ts';
2
3
  import { safeNameCandidates, sanitizeBranchName } from './constants.ts';
3
4
  import { hostJoin } from './engines/wsl2-paths.ts';
5
+ import type { SandboxTool, SandboxToolInstall } from './tool-types.ts';
4
6
 
5
- export type SandboxToolInstall =
6
- | { type: 'npm'; cmd: string }
7
- | { type: 'shell'; cmd: string };
8
-
9
- export type SandboxTool = {
10
- id: string;
11
- name: string;
12
- install: SandboxToolInstall;
13
- sandboxBase: string;
14
- containerMount: string;
15
- versionCmd: string;
16
- setupHint: string;
17
- envVars?: Record<string, string>;
18
- hostPreSeedFiles?: Array<{ hostPath: string; sandboxName: string }>;
19
- hostPreSeedDirs?: Array<{ hostDir: string; sandboxSubdir: string }>;
20
- pathRewriteFiles?: string[];
21
- hostLiveMounts?: Array<{ hostPath: string; containerSubpath: string }>;
22
- postSetupCmds?: string[];
23
- // When set, containerMount is mounted as an in-container tmpfs (RAM) instead
24
- // of bind-mounting the host config dir, keeping high-churn tool logs off the
25
- // host disk. `seed` lists the host-dir entries (relative to the tool's config
26
- // dir) to copy into the tmpfs when the container starts. It is an explicit
27
- // allowlist so runtime files (e.g. logs_2.sqlite, sessions) left in the host
28
- // dir stay out of the container and seeded config cannot write back.
29
- tmpfs?: { size?: string; seed?: string[] };
30
- };
7
+ export type { SandboxTool, SandboxToolInstall } from './tool-types.ts';
31
8
 
32
9
  export type TmpfsSeedEntry = {
33
10
  toolId: string;
@@ -64,114 +41,11 @@ type ToolsConfig = {
64
41
  project: string;
65
42
  tools: string[];
66
43
  customTools?: SandboxTool[];
44
+ agentClientState?: import('../agent-clients/types.ts').AgentClientState;
67
45
  };
68
46
 
69
47
  const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
70
48
 
71
- function createBuiltinTools(home: string, project: string): Record<string, SandboxTool> {
72
- return {
73
- 'claude-code': {
74
- id: 'claude-code',
75
- name: 'Claude Code',
76
- install: { type: 'npm', cmd: '@anthropic-ai/claude-code@latest' },
77
- sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'claude-code'),
78
- containerMount: '/home/devuser/.claude',
79
- versionCmd: 'claude --version',
80
- setupHint: 'Authenticates via host credentials live-mounted at ~/.claude/.credentials.json',
81
- // Claude Code stores user data (.claude.json — onboarding state, theme,
82
- // workspace trust) at $HOME/.claude.json by default, which sits OUTSIDE
83
- // the bind-mounted /home/devuser/.claude tree, so our preseeded
84
- // .claude.json never gets read and the theme picker re-runs on every
85
- // container start. Pinning CLAUDE_CONFIG_DIR to the tool mount relocates
86
- // .claude.json into the same directory as .credentials.json/settings.json,
87
- // letting ensureClaudeOnboarding actually take effect.
88
- envVars: { CLAUDE_CONFIG_DIR: '/home/devuser/.claude' },
89
- hostPreSeedDirs: [
90
- { hostDir: hostJoin(home, '.claude', 'plugins'), sandboxSubdir: 'plugins' }
91
- ],
92
- pathRewriteFiles: [
93
- 'plugins/installed_plugins.json',
94
- 'plugins/known_marketplaces.json'
95
- ],
96
- hostLiveMounts: [
97
- {
98
- hostPath: hostJoin(home, '.agent-infra', 'credentials', project, 'claude-code', '.credentials.json'),
99
- containerSubpath: '.credentials.json'
100
- }
101
- ]
102
- },
103
- codex: {
104
- id: 'codex',
105
- name: 'Codex',
106
- install: { type: 'npm', cmd: '@openai/codex' },
107
- sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'codex'),
108
- containerMount: '/home/devuser/.codex',
109
- versionCmd: 'codex --version',
110
- setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
111
- // codex churns ~/.codex/logs_2.sqlite heavily (upstream openai/codex#24275);
112
- // a bind-mount would write-amplify onto the host SSD via virtiofs. Mount the
113
- // codex home as tmpfs so those logs stay in RAM and die with the container.
114
- // Seeded config (config.toml, model-catalogs) is copied into the tmpfs at
115
- // startup; runtime files like logs_2.sqlite stay in RAM.
116
- tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'] },
117
- hostLiveMounts: [
118
- { hostPath: hostJoin(home, '.codex', 'auth.json'), containerSubpath: 'auth.json' }
119
- ],
120
- postSetupCmds: [
121
- 'test -d /workspace/.codex/commands && ln -sfn /workspace/.codex/commands /home/devuser/.codex/prompts || true'
122
- ]
123
- },
124
- opencode: {
125
- id: 'opencode',
126
- name: 'OpenCode',
127
- install: { type: 'npm', cmd: 'opencode-ai' },
128
- sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'opencode'),
129
- containerMount: '/home/devuser/.local/share/opencode',
130
- versionCmd: 'opencode version',
131
- setupHint: 'Configure OpenCode credentials inside the container before first use.',
132
- // OpenCode reads opencode.json from $XDG_CONFIG_HOME/opencode by default,
133
- // outside this tool mount. Pin the config file path so the inherited
134
- // sandbox opencode.json is the one the TUI actually reads.
135
- envVars: { OPENCODE_CONFIG: '/home/devuser/.local/share/opencode/opencode.json' },
136
- hostLiveMounts: [
137
- {
138
- hostPath: hostJoin(home, '.local', 'share', 'opencode', 'auth.json'),
139
- containerSubpath: 'auth.json'
140
- }
141
- ]
142
- },
143
- 'gemini-cli': {
144
- id: 'gemini-cli',
145
- name: 'Gemini CLI',
146
- install: { type: 'npm', cmd: '@google/gemini-cli' },
147
- sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'gemini-cli'),
148
- containerMount: '/home/devuser/.gemini',
149
- versionCmd: 'gemini --version',
150
- setupHint: 'Run gemini inside the container to finish authentication.',
151
- hostLiveMounts: [
152
- { hostPath: hostJoin(home, '.gemini', 'oauth_creds.json'), containerSubpath: 'oauth_creds.json' }
153
- ],
154
- hostPreSeedFiles: [
155
- { hostPath: hostJoin(home, '.gemini', 'settings.json'), sandboxName: 'settings.json' },
156
- { hostPath: hostJoin(home, '.gemini', 'google_accounts.json'), sandboxName: 'google_accounts.json' }
157
- ]
158
- },
159
- 'agent-infra': {
160
- id: 'agent-infra',
161
- name: 'agent-infra CLI',
162
- install: { type: 'npm', cmd: '@fitlab-ai/agent-infra@latest' },
163
- sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'agent-infra'),
164
- containerMount: '/home/devuser/.agent-infra-cli',
165
- versionCmd: 'ai version --raw',
166
- setupHint: 'Provides the ai and agent-infra CLI commands inside the sandbox.'
167
- }
168
- };
169
- }
170
-
171
- export function builtinToolIds(): string[] {
172
- return Object.keys(createBuiltinTools('', ''));
173
- }
174
-
175
49
  function validateTool(tool: SandboxTool): void {
176
50
  if (!tool.id || !TOOL_ID_PATTERN.test(tool.id)) {
177
51
  throw new Error(`Invalid sandbox tool id: ${String(tool.id)}`);
@@ -379,33 +253,7 @@ export function parseCustomTools(value: unknown, options: { home: string }): San
379
253
  }
380
254
 
381
255
  export function resolveTools(config: ToolsConfig): SandboxTool[] {
382
- const builtins = createBuiltinTools(config.home, config.project);
383
- const customs = config.customTools ?? [];
384
-
385
- const seen = new Set<string>();
386
- for (const tool of customs) {
387
- if (builtins[tool.id]) {
388
- throw new Error(`Custom sandbox tool id "${tool.id}" collides with a built-in tool`);
389
- }
390
- if (seen.has(tool.id)) {
391
- throw new Error(`Duplicate sandbox tool id "${tool.id}" in customTools`);
392
- }
393
- seen.add(tool.id);
394
- }
395
-
396
- const merged: Record<string, SandboxTool> = { ...builtins };
397
- for (const tool of customs) {
398
- merged[tool.id] = tool;
399
- }
400
-
401
- return config.tools.map((id) => {
402
- const tool = merged[id];
403
- if (!tool) {
404
- throw new Error(`Unknown sandbox tool: ${id}`);
405
- }
406
- validateTool(tool);
407
- return tool;
408
- });
256
+ return [...createSandboxCapabilityPlan(config).tools];
409
257
  }
410
258
 
411
259
  export function toolConfigDir(tool: SandboxTool, project: string, branch: string): string {