@fitlab-ai/agent-infra 0.9.0 → 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 (327) hide show
  1. package/README.md +22 -5
  2. package/README.zh-CN.md +21 -5
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +20 -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/platform-release-notes.js +53 -0
  31. package/dist/lib/internal/release-workflow.js +175 -17
  32. package/dist/lib/internal/task-orchestration.js +151 -0
  33. package/dist/lib/internal/task-review.js +65 -0
  34. package/dist/lib/merge.js +2 -1
  35. package/dist/lib/platform/adapters.js +38 -1
  36. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  37. package/dist/lib/platform/github-release-notes.js +96 -0
  38. package/dist/lib/platform/issues.js +4 -2
  39. package/dist/lib/platform/merged-pr-equivalence.js +84 -0
  40. package/dist/lib/platform/pr-checks.js +43 -15
  41. package/dist/lib/platform/pull-requests.js +113 -7
  42. package/dist/lib/platform/release-notes.js +143 -0
  43. package/dist/lib/platform/releases.js +1 -1
  44. package/dist/lib/platform/verification-required.js +31 -9
  45. package/dist/lib/prompt.js +15 -3
  46. package/dist/lib/release/channels.js +4 -1
  47. package/dist/lib/release/workflow.js +4 -2
  48. package/dist/lib/remove-dir.js +25 -0
  49. package/dist/lib/run/tui.js +8 -2
  50. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  51. package/dist/lib/sandbox/build-proxy.js +63 -0
  52. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  53. package/dist/lib/sandbox/commands/create.js +133 -189
  54. package/dist/lib/sandbox/commands/enter.js +17 -1
  55. package/dist/lib/sandbox/commands/prune.js +2 -2
  56. package/dist/lib/sandbox/commands/rebuild.js +24 -7
  57. package/dist/lib/sandbox/commands/rm.js +3 -2
  58. package/dist/lib/sandbox/config.js +21 -4
  59. package/dist/lib/sandbox/constants.js +3 -0
  60. package/dist/lib/sandbox/dockerfile.js +2 -1
  61. package/dist/lib/sandbox/image-build.js +2 -1
  62. package/dist/lib/sandbox/managed-fs.js +10 -5
  63. package/dist/lib/sandbox/recovery.js +52 -36
  64. package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
  65. package/dist/lib/sandbox/runtimes/base.dockerfile +8 -1
  66. package/dist/lib/sandbox/runtimes/java17.dockerfile +8 -1
  67. package/dist/lib/sandbox/runtimes/java21.dockerfile +8 -1
  68. package/dist/lib/sandbox/runtimes/node20.dockerfile +8 -1
  69. package/dist/lib/sandbox/runtimes/node22.dockerfile +8 -1
  70. package/dist/lib/sandbox/runtimes/python3.dockerfile +8 -1
  71. package/dist/lib/sandbox/shell.js +3 -2
  72. package/dist/lib/sandbox/tool-types.js +8 -0
  73. package/dist/lib/sandbox/tools.js +2 -126
  74. package/dist/lib/task/artifact-lifecycle.js +1 -49
  75. package/dist/lib/task/commands/decisions.js +50 -20
  76. package/dist/lib/task/commands/issue-body.js +10 -5
  77. package/dist/lib/task/delegation-receipts.js +107 -0
  78. package/dist/lib/task/events.js +101 -2
  79. package/dist/lib/task/issue-form.js +2 -0
  80. package/dist/lib/task/orchestration.js +420 -0
  81. package/dist/lib/task/review-artifacts.js +143 -59
  82. package/dist/lib/task/review-finalization.js +166 -0
  83. package/dist/lib/task/review-fingerprint.js +13 -0
  84. package/dist/lib/task/verification-checks.js +2 -1
  85. package/dist/lib/task/verification-engine.js +168 -23
  86. package/dist/lib/task/verification-review.js +1 -1
  87. package/dist/lib/task/verification.js +4 -2
  88. package/dist/lib/task/workspace-snapshot.js +34 -0
  89. package/dist/lib/update.js +42 -161
  90. package/lib/agent-client.ts +214 -0
  91. package/lib/agent-clients/adapter.ts +461 -0
  92. package/lib/agent-clients/adapters/claude-code.ts +70 -0
  93. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  94. package/lib/agent-clients/adapters/codex.ts +58 -0
  95. package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
  96. package/lib/agent-clients/adapters/index.ts +27 -0
  97. package/lib/agent-clients/adapters/opencode.ts +66 -0
  98. package/lib/agent-clients/config.ts +278 -0
  99. package/lib/agent-clients/custom-tuis.ts +149 -0
  100. package/lib/agent-clients/invocation.ts +19 -0
  101. package/lib/agent-clients/next-steps.ts +72 -0
  102. package/lib/agent-clients/project-assets.ts +96 -0
  103. package/lib/agent-clients/reconcile.ts +324 -0
  104. package/lib/agent-clients/registry.ts +144 -0
  105. package/lib/agent-clients/schema.ts +62 -0
  106. package/lib/agent-clients/types.ts +78 -0
  107. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  108. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  109. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  110. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  111. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  112. package/lib/builtin-tuis.ts +16 -15
  113. package/lib/defaults.json +24 -12
  114. package/lib/git/review-snapshot.ts +39 -9
  115. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  116. package/lib/init.ts +53 -121
  117. package/lib/internal/agent-client.ts +131 -0
  118. package/lib/internal/git-workflow.ts +1 -1
  119. package/lib/internal/platform-release-notes.ts +55 -0
  120. package/lib/internal/release-workflow.ts +172 -18
  121. package/lib/internal/task-orchestration.ts +132 -0
  122. package/lib/internal/task-review.ts +65 -0
  123. package/lib/merge.ts +2 -1
  124. package/lib/platform/adapters.ts +142 -3
  125. package/lib/platform/change-request-git-evidence.ts +159 -0
  126. package/lib/platform/github-release-notes.ts +132 -0
  127. package/lib/platform/issues.ts +6 -4
  128. package/lib/platform/merged-pr-equivalence.ts +107 -0
  129. package/lib/platform/pr-checks.ts +57 -28
  130. package/lib/platform/pull-requests.ts +140 -25
  131. package/lib/platform/release-notes.ts +167 -0
  132. package/lib/platform/releases.ts +1 -1
  133. package/lib/platform/verification-required.ts +37 -9
  134. package/lib/prompt.ts +16 -3
  135. package/lib/release/channels.ts +4 -1
  136. package/lib/release/workflow.ts +4 -3
  137. package/lib/remove-dir.ts +28 -0
  138. package/lib/run/tui.ts +9 -1
  139. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  140. package/lib/sandbox/build-proxy.ts +94 -0
  141. package/lib/sandbox/clipboard/linux.ts +2 -1
  142. package/lib/sandbox/commands/create.ts +188 -203
  143. package/lib/sandbox/commands/enter.ts +23 -1
  144. package/lib/sandbox/commands/prune.ts +2 -2
  145. package/lib/sandbox/commands/rebuild.ts +29 -7
  146. package/lib/sandbox/commands/rm.ts +3 -2
  147. package/lib/sandbox/config.ts +31 -4
  148. package/lib/sandbox/constants.ts +6 -0
  149. package/lib/sandbox/dockerfile.ts +2 -1
  150. package/lib/sandbox/image-build.ts +4 -1
  151. package/lib/sandbox/managed-fs.ts +18 -5
  152. package/lib/sandbox/recovery.ts +98 -67
  153. package/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
  154. package/lib/sandbox/runtimes/base.dockerfile +8 -1
  155. package/lib/sandbox/runtimes/java17.dockerfile +8 -1
  156. package/lib/sandbox/runtimes/java21.dockerfile +8 -1
  157. package/lib/sandbox/runtimes/node20.dockerfile +8 -1
  158. package/lib/sandbox/runtimes/node22.dockerfile +8 -1
  159. package/lib/sandbox/runtimes/python3.dockerfile +8 -1
  160. package/lib/sandbox/shell.ts +4 -2
  161. package/lib/sandbox/tool-types.ts +143 -0
  162. package/lib/sandbox/tools.ts +5 -157
  163. package/lib/task/artifact-lifecycle.ts +1 -38
  164. package/lib/task/commands/decisions.ts +53 -20
  165. package/lib/task/commands/issue-body.ts +10 -5
  166. package/lib/task/delegation-receipts.ts +170 -0
  167. package/lib/task/events.ts +111 -3
  168. package/lib/task/issue-form.ts +4 -0
  169. package/lib/task/orchestration.ts +543 -0
  170. package/lib/task/review-artifacts.ts +201 -69
  171. package/lib/task/review-finalization.ts +275 -0
  172. package/lib/task/review-fingerprint.ts +15 -1
  173. package/lib/task/verification-checks.ts +2 -1
  174. package/lib/task/verification-engine.ts +190 -25
  175. package/lib/task/verification-review.ts +2 -0
  176. package/lib/task/verification.ts +6 -3
  177. package/lib/task/workspace-snapshot.ts +36 -0
  178. package/lib/update.ts +48 -184
  179. package/package.json +2 -2
  180. package/templates/.agents/README.en.md +21 -20
  181. package/templates/.agents/README.zh-CN.md +21 -20
  182. package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
  183. package/templates/.agents/rules/README.en.md +2 -0
  184. package/templates/.agents/rules/README.zh-CN.md +2 -0
  185. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  186. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  187. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  188. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  189. package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
  190. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
  191. package/templates/.agents/rules/next-step-output.en.md +16 -0
  192. package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
  193. package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
  194. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
  195. package/templates/.agents/rules/release-commands.en.md +2 -2
  196. package/templates/.agents/rules/release-commands.github.en.md +15 -39
  197. package/templates/.agents/rules/release-commands.github.zh-CN.md +14 -38
  198. package/templates/.agents/rules/release-commands.zh-CN.md +2 -2
  199. package/templates/.agents/rules/review-handshake.en.md +4 -3
  200. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -3
  201. package/templates/.agents/rules/review-method.en.md +113 -0
  202. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  203. package/templates/.agents/rules/testing-discipline.en.md +20 -2
  204. package/templates/.agents/rules/testing-discipline.zh-CN.md +20 -2
  205. package/templates/.agents/skills/analyze-task/SKILL.en.md +9 -6
  206. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +9 -6
  207. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  208. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
  209. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
  210. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
  211. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  212. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  213. package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
  214. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
  215. package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
  216. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
  217. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  218. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  219. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  220. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  221. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  222. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  223. package/templates/.agents/skills/commit/SKILL.en.md +11 -1
  224. package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
  225. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  226. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  227. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  228. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  229. package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
  230. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
  231. package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
  232. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
  233. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  234. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  235. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -38
  236. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +16 -37
  237. package/templates/.agents/skills/create-task/SKILL.en.md +15 -11
  238. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +15 -11
  239. package/templates/.agents/skills/create-task/reference/context-capture.en.md +29 -0
  240. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +29 -0
  241. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  242. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  243. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  244. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  245. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  246. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  247. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  248. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  249. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  250. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  251. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  252. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  253. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  254. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  255. package/templates/.agents/skills/release/SKILL.en.md +1 -1
  256. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
  257. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
  258. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  259. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  260. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  261. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  262. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  263. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  264. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  265. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  266. package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
  267. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
  268. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  269. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  270. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  271. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  272. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  273. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  274. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  275. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  276. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  277. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  278. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  279. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  280. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  281. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  282. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  283. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  284. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  285. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  286. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
  287. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
  288. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  289. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  290. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  291. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  292. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  293. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  294. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  295. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  296. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  297. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  298. package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
  299. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
  300. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  301. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  302. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  303. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  304. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  305. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  306. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
  307. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  308. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  309. package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
  310. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
  311. package/templates/.agents/templates/task.en.md +18 -0
  312. package/templates/.agents/templates/task.zh-CN.md +18 -0
  313. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  314. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  315. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  316. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  317. package/templates/.claude/commands/run-task.en.md +8 -0
  318. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  319. package/templates/.claude/settings.json +24 -0
  320. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  321. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  322. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  323. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  324. package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
  325. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
  326. package/templates/.opencode/commands/run-task.en.md +11 -0
  327. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
@@ -7,7 +7,8 @@ import type { ExecFileSyncOptions, StdioOptions } from 'node:child_process';
7
7
  import { parseArgs } from 'node:util';
8
8
  import * as p from '@clack/prompts';
9
9
  import pc from 'picocolors';
10
- import * as toml from 'smol-toml';
10
+ import { listAgentClientAdapters } from '../../agent-clients/registry.ts';
11
+ import type { SandboxAlias } from '../tool-types.ts';
11
12
  import { loadConfig } from '../config.ts';
12
13
  import {
13
14
  assertValidBranchName,
@@ -18,12 +19,19 @@ import {
18
19
  sandboxImageConfigLabel,
19
20
  sandboxImageRefreshLabel,
20
21
  sandboxLabel,
22
+ sandboxRuntimeCapabilityLabel,
21
23
  shareBranchDir,
22
24
  shareCommonDir,
23
25
  shellConfigDir,
24
26
  worktreeDirCandidates
25
27
  } from '../constants.ts';
26
28
  import { prepareDockerfile } from '../dockerfile.ts';
29
+ import {
30
+ assertBuildProxyCompatibility,
31
+ buildProxyFailureHint,
32
+ prepareBuildProxy,
33
+ redactBuildProxyValues
34
+ } from '../build-proxy.ts';
27
35
  import { detectEngine, ensureDocker } from '../engine.ts';
28
36
  import {
29
37
  commandForEngine,
@@ -37,6 +45,11 @@ import {
37
45
  runVerboseEngine
38
46
  } from '../shell.ts';
39
47
  import { resolveTaskBranch } from '../task-resolver.ts';
48
+ import {
49
+ createSandboxCapabilityPlan,
50
+ runSandboxHooks,
51
+ runBoundedSandboxHookCommand
52
+ } from '../agent-client-reconciler.ts';
40
53
  import {
41
54
  resolveTools,
42
55
  tmpfsSeedStagingPath,
@@ -55,6 +68,7 @@ import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.
55
68
  import { validateSelinuxDisableEnv } from '../engines/selinux.ts';
56
69
  import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.ts';
57
70
  import { ensureSandboxDiscoveryReadmes } from '../readme-scaffold.ts';
71
+ import { removeDirRecursive } from '../../remove-dir.ts';
58
72
  import {
59
73
  prepareClaudeCredentials,
60
74
  redactCommandError,
@@ -70,32 +84,16 @@ import {
70
84
  parseRefreshTimestamp
71
85
  } from '../image-build.ts';
72
86
 
73
- const OPENCODE_YOLO_PERMISSION = '{"*":"allow","read":"allow","bash":"allow","edit":"allow","webfetch":"allow","external_directory":"allow","doom_loop":"allow"}';
87
+ export {
88
+ ensureCodexModelInheritance,
89
+ ensureCodexWorkspaceTrust
90
+ } from '../../agent-clients/adapters/codex-sandbox.ts';
91
+
74
92
  const SANDBOX_ALIAS_BLOCK_BEGIN = '# >>> agent-infra managed aliases >>>';
75
93
  const SANDBOX_ALIAS_BLOCK_END = '# <<< agent-infra managed aliases <<<';
76
- const SANDBOX_ALIAS_NAMES = [
77
- 'claude-yolo',
78
- 'opencode-yolo',
79
- 'codex-yolo',
80
- 'gemini-yolo',
81
- 'cy',
82
- 'oy',
83
- 'xy',
84
- 'gy'
85
- ];
86
- const DEFAULT_SANDBOX_ALIASES = `alias claude-yolo='claude --dangerously-skip-permissions; tput ed'
87
- alias opencode-yolo='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
88
- alias codex-yolo='codex --yolo; tput ed'
89
- alias gemini-yolo='gemini --yolo; tput ed'
90
-
91
- alias cy='claude --dangerously-skip-permissions; tput ed'
92
- alias oy='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
93
- alias xy='codex --yolo; tput ed'
94
- alias gy='gemini --yolo; tput ed'
95
- `;
96
94
  const CONTAINER_HOME = '/home/devuser';
97
95
  const CONTAINER_SHELL_CONFIG_MOUNT = `${CONTAINER_HOME}/.host-shell-config`;
98
- const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh] [--inherit-proxy|-P]
96
+ const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh] [--inherit-proxy|-P] [--inherit-build-proxy|-B]
99
97
 
100
98
  Host aliases:
101
99
  ${'~'}/.agent-infra/aliases/sandbox.sh is auto-created on first run and exposed
@@ -105,7 +103,9 @@ Host aliases:
105
103
 
106
104
  Proxy:
107
105
  --inherit-proxy, -P Copy non-empty standard host proxy variables into the
108
- container environment through the private env file.`;
106
+ container environment through the private env file.
107
+ --inherit-build-proxy, -B Pass uppercase HTTP_PROXY, HTTPS_PROXY, and NO_PROXY
108
+ to managed image build steps for this invocation.`;
109
109
  const HOST_PROXY_ENV_KEYS = [
110
110
  'http_proxy',
111
111
  'HTTP_PROXY',
@@ -116,6 +116,15 @@ const HOST_PROXY_ENV_KEYS = [
116
116
  'no_proxy',
117
117
  'NO_PROXY'
118
118
  ] as const;
119
+ const MANAGED_GITHUB_GIT_CONFIG = [
120
+ '[credential]',
121
+ '\thelper = !gh auth git-credential',
122
+ '[credential "https://github.com"]',
123
+ '\thelper =',
124
+ '\thelper = !gh auth git-credential',
125
+ '[url "https://github.com/"]',
126
+ '\tinsteadOf = git@github.com:'
127
+ ];
119
128
 
120
129
  type SandboxCreateConfig = ReturnType<typeof loadConfig>;
121
130
  type PreparedDockerfile = ReturnType<typeof prepareDockerfile>;
@@ -134,9 +143,9 @@ type ExecSyncOptions = ExecFileSyncOptions & {
134
143
  };
135
144
  type ExecSyncFn = (cmd: string, args: string[], options?: ExecSyncOptions) => Buffer | string;
136
145
  type EngineExecFn = (engine: string, cmd: string, args: string[], opts?: ExecFileSyncOptions) => Buffer | string;
137
- type EngineRunFn = (engine: string, cmd: string, args: string[], opts?: { cwd?: string }) => string;
146
+ type EngineRunFn = (engine: string, cmd: string, args: string[], opts?: { cwd?: string; env?: NodeJS.ProcessEnv }) => string;
138
147
  type EngineRunSafeFn = EngineRunFn;
139
- type EngineRunVerboseFn = (engine: string, cmd: string, args: string[], opts?: { cwd?: string }) => void;
148
+ type EngineRunVerboseFn = (engine: string, cmd: string, args: string[], opts?: { cwd?: string; env?: NodeJS.ProcessEnv }) => void;
140
149
  type DirectRunFn = (cmd: string, args: string[], opts?: { cwd?: string }) => string;
141
150
  type DirectRunSafeFn = DirectRunFn;
142
151
  type DirectRunVerboseFn = (cmd: string, args: string[], opts?: { cwd?: string }) => void;
@@ -255,6 +264,16 @@ function normalizeContainerHomeSeparators(content: string): string {
255
264
  return content.replace(containerHomePattern, (value) => value.replaceAll('\\', '/'));
256
265
  }
257
266
 
267
+ function isGitHubCliCredentialHelper(line: string): boolean {
268
+ const helperMatch = line.match(/^\s*helper\s*=\s*(.+?)\s*$/i);
269
+ if (!helperMatch) {
270
+ return false;
271
+ }
272
+
273
+ const helper = (helperMatch[1] ?? '').trim().replace(/^"(.*)"$/, '$1');
274
+ return /^!\s*(?:"[^"]*[\\/]+gh"|(?:\S*[\\/]+)?gh)\s+auth\s+git-credential(?:\s|$)/i.test(helper);
275
+ }
276
+
258
277
  export function sanitizeGitConfig(
259
278
  gitconfig: string,
260
279
  home: string,
@@ -272,11 +291,13 @@ export function sanitizeGitConfig(
272
291
  const sanitized = [];
273
292
  let inGpgSection = false;
274
293
  let currentSection = '';
294
+ let currentSectionName = '';
275
295
 
276
296
  for (const line of lines) {
277
297
  const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
278
298
  if (sectionMatch) {
279
299
  const sectionName = (sectionMatch[1] ?? '').trim();
300
+ currentSectionName = sectionName;
280
301
  currentSection = ((sectionName.match(/^([^\s"]+)/)?.[1]) ?? '').toLowerCase();
281
302
  inGpgSection = /^gpg(?:\s+"[^"]+")?$/i.test(sectionName);
282
303
  if (stripGpg && inGpgSection) {
@@ -304,11 +325,23 @@ export function sanitizeGitConfig(
304
325
  if (stripGpg && currentSection === 'user' && /^\s*signingKey\s*=.*$/i.test(line)) {
305
326
  continue;
306
327
  }
328
+ if (currentSection === 'credential' && isGitHubCliCredentialHelper(line)) {
329
+ continue;
330
+ }
331
+ if (
332
+ /^url\s+"https:\/\/github\.com\/"$/i.test(currentSectionName)
333
+ && /^\s*insteadOf\s*=\s*git@github\.com:\s*$/i.test(line)
334
+ ) {
335
+ continue;
336
+ }
307
337
 
308
338
  sanitized.push(line);
309
339
  }
310
340
 
311
- return appendSafeDirectories(sanitized, repoRoot).join('\n');
341
+ return [
342
+ ...appendSafeDirectories(sanitized, repoRoot),
343
+ ...MANAGED_GITHUB_GIT_CONFIG
344
+ ].join('\n');
312
345
  }
313
346
 
314
347
  export function hostHasGpgKeys(home: string, execFn: ExecSyncFn = execFileSync): boolean {
@@ -369,7 +402,7 @@ export function prepareHostShellConfig({
369
402
  repoRoot: string;
370
403
  }): HostShellConfig {
371
404
  const hostDir = hostShellConfigDir(home, project, branch);
372
- fs.rmSync(hostDir, { recursive: true, force: true });
405
+ removeDirRecursive(hostDir);
373
406
  fs.mkdirSync(hostDir, { recursive: true });
374
407
 
375
408
  writeSanitizedGitconfig({
@@ -418,10 +451,19 @@ function normalizeWorktreePath(worktreePath: string): string {
418
451
  return '';
419
452
  }
420
453
 
454
+ const wslMount = process.platform === 'win32'
455
+ ? worktreePath.match(/^\/mnt\/([A-Za-z])(?:\/(.*))?$/)
456
+ : null;
457
+ const hostPath = wslMount
458
+ ? `${wslMount[1]!.toUpperCase()}:\\${(wslMount[2] ?? '').replace(/\//g, '\\')}`
459
+ : worktreePath;
460
+
421
461
  try {
422
- return fs.existsSync(worktreePath) ? fs.realpathSync(worktreePath) : path.resolve(worktreePath);
462
+ const normalized = fs.existsSync(hostPath) ? fs.realpathSync(hostPath) : path.resolve(hostPath);
463
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
423
464
  } catch {
424
- return path.resolve(worktreePath);
465
+ const normalized = path.resolve(hostPath);
466
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
425
467
  }
426
468
  }
427
469
 
@@ -673,7 +715,7 @@ export function buildContainerEnvFile(
673
715
  mkdtempFn = fs.mkdtempSync,
674
716
  writeFileFn = fs.writeFileSync,
675
717
  chmodFn = fs.chmodSync,
676
- rmFn = fs.rmSync,
718
+ rmFn = removeDirRecursive as typeof fs.rmSync,
677
719
  tmpDir = os.tmpdir(),
678
720
  runSafeFn = runSafe
679
721
  } = options;
@@ -930,154 +972,6 @@ export function ensureClaudeSettings(toolDir: string, hostHomeDir?: string): voi
930
972
  }
931
973
  }
932
974
 
933
- function resolveHostCatalogPath(value: unknown, hostHomeDir: string): string | null {
934
- if (typeof value !== 'string' || value === '') {
935
- return null;
936
- }
937
- let resolved: string;
938
- if (value === '~' || value.startsWith('~/') || value.startsWith('~\\')) {
939
- resolved = path.join(hostHomeDir, value.slice(1).replace(/^[/\\]+/, ''));
940
- } else if (path.isAbsolute(value)) {
941
- resolved = value;
942
- } else {
943
- resolved = path.join(hostHomeDir, '.codex', value);
944
- }
945
- try {
946
- if (!fs.statSync(resolved).isFile()) {
947
- return null;
948
- }
949
- fs.accessSync(resolved, fs.constants.R_OK);
950
- return resolved;
951
- } catch {
952
- return null;
953
- }
954
- }
955
-
956
- const CODEX_DISABLED_FEATURE_FLAGS = ['apps', 'enable_mcp_apps'] as const;
957
-
958
- function removeCodexMcpServers(sandboxParsed: JsonObject): boolean {
959
- if (!Object.hasOwn(sandboxParsed, 'mcp_servers')) {
960
- return false;
961
- }
962
- delete sandboxParsed.mcp_servers;
963
- return true;
964
- }
965
-
966
- function inheritDisabledCodexFeatureFlags(sandboxParsed: JsonObject, hostParsed: JsonObject): boolean {
967
- if (!isJsonObjectRecord(hostParsed.features)) {
968
- return false;
969
- }
970
- if (Object.hasOwn(sandboxParsed, 'features') && !isJsonObjectRecord(sandboxParsed.features)) {
971
- return false;
972
- }
973
-
974
- let sandboxFeatures = sandboxParsed.features as JsonObject | undefined;
975
- let changed = false;
976
- for (const key of CODEX_DISABLED_FEATURE_FLAGS) {
977
- if (hostParsed.features[key] !== false) {
978
- continue;
979
- }
980
- if (!sandboxFeatures) {
981
- sandboxFeatures = {};
982
- sandboxParsed.features = sandboxFeatures;
983
- }
984
- if (sandboxFeatures[key] !== false) {
985
- sandboxFeatures[key] = false;
986
- changed = true;
987
- }
988
- }
989
- return changed;
990
- }
991
-
992
- export function ensureCodexModelInheritance(
993
- toolDir: string,
994
- hostHomeDir?: string,
995
- containerCodexDir: string = '/home/devuser/.codex'
996
- ): void {
997
- if (!hostHomeDir) {
998
- return;
999
- }
1000
-
1001
- const hostConfigPath = path.join(hostHomeDir, '.codex', 'config.toml');
1002
- if (!fs.existsSync(hostConfigPath)) {
1003
- return;
1004
- }
1005
-
1006
- let hostParsed: JsonObject;
1007
- try {
1008
- hostParsed = toml.parse(fs.readFileSync(hostConfigPath, 'utf8')) as JsonObject;
1009
- } catch {
1010
- return;
1011
- }
1012
-
1013
- const sandboxConfigPath = path.join(toolDir, 'config.toml');
1014
- // This rewrites sandbox-side TOML and drops comments; the host config stays untouched.
1015
- let sandboxParsed: JsonObject = {};
1016
- if (fs.existsSync(sandboxConfigPath)) {
1017
- try {
1018
- sandboxParsed = toml.parse(fs.readFileSync(sandboxConfigPath, 'utf8')) as JsonObject;
1019
- } catch {
1020
- return;
1021
- }
1022
- }
1023
-
1024
- let changed = removeCodexMcpServers(sandboxParsed);
1025
- changed = inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) || changed;
1026
-
1027
- const inheritSpecs: Array<readonly [string, 'string' | 'number']> = [
1028
- ['model', 'string'],
1029
- ['model_reasoning_effort', 'string'],
1030
- ['model_auto_compact_token_limit', 'number']
1031
- ];
1032
-
1033
- for (const [key, type] of inheritSpecs) {
1034
- if (Object.hasOwn(sandboxParsed, key)) {
1035
- continue;
1036
- }
1037
- const value = hostParsed[key];
1038
- if (type === 'string' && (typeof value !== 'string' || value === '')) {
1039
- continue;
1040
- }
1041
- if (type === 'number' && (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
1042
- continue;
1043
- }
1044
- sandboxParsed[key] = value;
1045
- changed = true;
1046
- }
1047
-
1048
- if (!Object.hasOwn(sandboxParsed, 'model_catalog_json')) {
1049
- const hostCatalogPath = resolveHostCatalogPath(hostParsed['model_catalog_json'], hostHomeDir);
1050
- if (hostCatalogPath) {
1051
- try {
1052
- const basename = path.basename(hostCatalogPath);
1053
- const destDir = path.join(toolDir, 'model-catalogs');
1054
- fs.mkdirSync(destDir, { recursive: true });
1055
- fs.copyFileSync(hostCatalogPath, path.join(destDir, basename));
1056
- sandboxParsed['model_catalog_json'] = path.posix.join(containerCodexDir, 'model-catalogs', basename);
1057
- changed = true;
1058
- } catch {
1059
- // Copy failed (e.g. permissions): skip catalog, keep scalar inheritance intact.
1060
- }
1061
- }
1062
- }
1063
-
1064
- if (changed) {
1065
- fs.writeFileSync(sandboxConfigPath, `${toml.stringify(sandboxParsed)}\n`, 'utf8');
1066
- }
1067
- }
1068
-
1069
- export function ensureCodexWorkspaceTrust(toolDir: string): void {
1070
- const configPath = path.join(toolDir, 'config.toml');
1071
- let content = '';
1072
- if (fs.existsSync(configPath)) {
1073
- content = fs.readFileSync(configPath, 'utf8');
1074
- }
1075
- if (!content.includes('[projects."/workspace"]')) {
1076
- const entry = '\n[projects."/workspace"]\ntrust_level = "trusted"\n';
1077
- fs.writeFileSync(configPath, content + entry, 'utf8');
1078
- }
1079
- }
1080
-
1081
975
  export function ensureOpenCodeModelInheritance(toolDir: string, hostHomeDir?: string): void {
1082
976
  if (!hostHomeDir) {
1083
977
  return;
@@ -1148,7 +1042,10 @@ function stripManagedSandboxAliasBlocks(content: string): string {
1148
1042
  return content.replace(blockPattern, '').trimEnd();
1149
1043
  }
1150
1044
 
1151
- function isLegacyManagedSandboxAliasFile(content: string): boolean {
1045
+ function isLegacyManagedSandboxAliasFile(
1046
+ content: string,
1047
+ aliases: readonly SandboxAlias[]
1048
+ ): boolean {
1152
1049
  const lines = content
1153
1050
  .split(/\r?\n/)
1154
1051
  .map((line) => line.trim())
@@ -1158,13 +1055,29 @@ function isLegacyManagedSandboxAliasFile(content: string): boolean {
1158
1055
  return false;
1159
1056
  }
1160
1057
 
1161
- const aliasPattern = new RegExp(`^alias (${SANDBOX_ALIAS_NAMES.map(escapeRegExp).join('|')})=`);
1058
+ const aliasPattern = new RegExp(
1059
+ `^alias (${aliases.map((alias) => escapeRegExp(alias.name)).join('|')})=`
1060
+ );
1162
1061
  return lines.every((line) => aliasPattern.test(line));
1163
1062
  }
1164
1063
 
1165
- export function ensureSandboxAliasesFile(home: string): { created: boolean; path: string } {
1064
+ function registrySandboxAliases(): readonly SandboxAlias[] {
1065
+ return listAgentClientAdapters().flatMap((adapter) => adapter.sandbox.aliases);
1066
+ }
1067
+
1068
+ function renderSandboxAliases(aliases: readonly SandboxAlias[]): string {
1069
+ return `${aliases.map((alias) =>
1070
+ `alias ${alias.name}='${alias.command.replaceAll("'", "'\\''")}'`
1071
+ ).join('\n')}\n`;
1072
+ }
1073
+
1074
+ export function ensureSandboxAliasesFile(
1075
+ home: string,
1076
+ aliases: readonly SandboxAlias[] = registrySandboxAliases()
1077
+ ): { created: boolean; path: string } {
1166
1078
  const aliasesPath = sandboxAliasesPath(home);
1167
- const managedBlock = `${SANDBOX_ALIAS_BLOCK_BEGIN}\n${DEFAULT_SANDBOX_ALIASES}${SANDBOX_ALIAS_BLOCK_END}\n`;
1079
+ const managedBlock =
1080
+ `${SANDBOX_ALIAS_BLOCK_BEGIN}\n${renderSandboxAliases(aliases)}${SANDBOX_ALIAS_BLOCK_END}\n`;
1168
1081
  fs.mkdirSync(path.dirname(aliasesPath), { recursive: true });
1169
1082
  const created = !fs.existsSync(aliasesPath);
1170
1083
  let existing = '';
@@ -1173,7 +1086,7 @@ export function ensureSandboxAliasesFile(home: string): { created: boolean; path
1173
1086
  existing = fs.readFileSync(aliasesPath, 'utf8');
1174
1087
  }
1175
1088
 
1176
- const userContent = isLegacyManagedSandboxAliasFile(existing)
1089
+ const userContent = isLegacyManagedSandboxAliasFile(existing, aliases)
1177
1090
  ? ''
1178
1091
  : stripManagedSandboxAliasBlocks(existing);
1179
1092
  const nextContent = userContent
@@ -1231,7 +1144,9 @@ export function buildImage(
1231
1144
  runVerboseFn = runVerboseEngine,
1232
1145
  env = process.env,
1233
1146
  refresh = false,
1234
- lastRefresh
1147
+ lastRefresh,
1148
+ buildProxyArgs = [],
1149
+ buildEnv
1235
1150
  }: {
1236
1151
  engine?: string;
1237
1152
  runFn?: EngineRunFn;
@@ -1240,6 +1155,8 @@ export function buildImage(
1240
1155
  env?: NodeJS.ProcessEnv;
1241
1156
  refresh?: boolean;
1242
1157
  lastRefresh?: number;
1158
+ buildProxyArgs?: string[];
1159
+ buildEnv?: NodeJS.ProcessEnv;
1243
1160
  } = {}
1244
1161
  ): void {
1245
1162
  const selectedEngine = engine ?? detectEngine({ engine: config.engine });
@@ -1252,9 +1169,10 @@ export function buildImage(
1252
1169
  runSafeFn,
1253
1170
  env,
1254
1171
  refresh,
1255
- lastRefresh
1172
+ lastRefresh,
1173
+ buildProxyArgs
1256
1174
  }),
1257
- { cwd: config.repoRoot }
1175
+ { cwd: config.repoRoot, env: buildEnv }
1258
1176
  );
1259
1177
  }
1260
1178
 
@@ -1278,6 +1196,7 @@ export async function create(args: string[]): Promise<void> {
1278
1196
  memory: { type: 'string' },
1279
1197
  'no-refresh': { type: 'boolean' },
1280
1198
  'inherit-proxy': { type: 'boolean', short: 'P' },
1199
+ 'inherit-build-proxy': { type: 'boolean', short: 'B' },
1281
1200
  help: { type: 'boolean', short: 'h' }
1282
1201
  }
1283
1202
  });
@@ -1308,19 +1227,18 @@ export async function create(args: string[]): Promise<void> {
1308
1227
  };
1309
1228
  const worktreeCandidates = worktreeDirCandidates(effectiveConfig, branch);
1310
1229
  assertBranchAvailable(config.repoRoot, branch, { allowedWorktrees: worktreeCandidates });
1311
- const tools = resolveTools(effectiveConfig);
1230
+ const capabilityPlan = createSandboxCapabilityPlan(effectiveConfig);
1231
+ const tools = [...capabilityPlan.tools];
1312
1232
  const resolvedTools = resolveToolDirs(effectiveConfig, tools, branch);
1313
1233
  // Fatal credential states still fail before filesystem/docker side effects.
1314
- // A genuinely missing Claude Code credential only removes Claude Code's
1315
- // sandbox config and credential mounts for this create run.
1234
+ // Missing credentials leave the selected Claude Code capability installed
1235
+ // and mounted; only the absent live credential file is omitted.
1316
1236
  const credentialOutcome = prepareClaudeCredentials(
1317
1237
  effectiveConfig.home,
1318
1238
  effectiveConfig.project,
1319
1239
  resolvedTools
1320
1240
  );
1321
- const effectiveResolvedTools = credentialOutcome.status === 'SKIPPED'
1322
- ? resolvedTools.filter(({ tool }) => tool.id !== 'claude-code')
1323
- : resolvedTools;
1241
+ const effectiveResolvedTools = resolvedTools;
1324
1242
  const container = containerName(effectiveConfig, branch);
1325
1243
  const worktree = worktreeCandidates.find((candidate) => fs.existsSync(candidate)) ?? worktreeCandidates[0] ?? '';
1326
1244
  const shareCommon = shareCommonDir(effectiveConfig);
@@ -1345,6 +1263,16 @@ export async function create(args: string[]): Promise<void> {
1345
1263
 
1346
1264
  try {
1347
1265
  p.log.step('Checking container engine...');
1266
+ const prepareHookResults = await runSandboxHooks({
1267
+ hooks: capabilityPlan.hooksByPhase.prepare,
1268
+ phase: 'prepare',
1269
+ context: { config: effectiveConfig, plan: capabilityPlan },
1270
+ runCommand: runBoundedSandboxHookCommand
1271
+ });
1272
+ const prepareFailure = prepareHookResults.find((result) => result.status === 'fatal');
1273
+ if (prepareFailure) {
1274
+ throw new Error(prepareFailure.message ?? `Sandbox hook '${prepareFailure.hookId}' failed.`);
1275
+ }
1348
1276
  await ensureDocker(effectiveConfig, (detail: string) => {
1349
1277
  p.log.info(` ${detail}`);
1350
1278
  });
@@ -1363,6 +1291,15 @@ export async function create(args: string[]): Promise<void> {
1363
1291
  const needsImageBuild = signatureStale || refreshDue;
1364
1292
 
1365
1293
  if (needsImageBuild) {
1294
+ if (values['inherit-build-proxy'] && effectiveConfig.dockerfile) {
1295
+ throw new Error('Build proxy inheritance is unavailable with a custom sandbox Dockerfile.');
1296
+ }
1297
+ const buildProxy = prepareBuildProxy(
1298
+ values['inherit-build-proxy'] ?? false,
1299
+ process.env,
1300
+ engine
1301
+ );
1302
+ if (values['inherit-build-proxy']) assertBuildProxyCompatibility(engine);
1366
1303
  const buildRefresh = !imageExists || refreshDue;
1367
1304
  const buildLastRefresh = buildRefresh ? now : currentLastRefresh || 0;
1368
1305
 
@@ -1379,7 +1316,13 @@ export async function create(args: string[]): Promise<void> {
1379
1316
  tools,
1380
1317
  preparedDockerfile.path,
1381
1318
  expectedImageSignature,
1382
- { engine, refresh: buildRefresh, lastRefresh: buildLastRefresh }
1319
+ {
1320
+ engine,
1321
+ refresh: buildRefresh,
1322
+ lastRefresh: buildLastRefresh,
1323
+ buildProxyArgs: buildProxy.args,
1324
+ buildEnv: buildProxy.env
1325
+ }
1383
1326
  );
1384
1327
  p.log.success(
1385
1328
  refreshDue
@@ -1392,10 +1335,14 @@ export async function create(args: string[]): Promise<void> {
1392
1335
  if (refreshDue && !signatureStale && imageExists) {
1393
1336
  p.log.warn(
1394
1337
  'Scheduled sandbox image refresh failed; continuing with the existing image. ' +
1395
- commandErrorMessage(error)
1338
+ redactBuildProxyValues(commandErrorMessage(error), buildProxy.redactionValues)
1339
+ + (values['inherit-build-proxy'] ? ` ${buildProxyFailureHint(engine)}` : '')
1396
1340
  );
1397
1341
  } else {
1398
- throw error;
1342
+ const hint = values['inherit-build-proxy'] ? `\n${buildProxyFailureHint(engine)}` : '';
1343
+ throw new Error(
1344
+ `${redactBuildProxyValues(commandErrorMessage(error), buildProxy.redactionValues)}${hint}`
1345
+ );
1399
1346
  }
1400
1347
  }
1401
1348
  } else {
@@ -1410,7 +1357,7 @@ export async function create(args: string[]): Promise<void> {
1410
1357
  if (fs.readdirSync(worktree).length > 0) {
1411
1358
  return `Worktree exists at ${worktree}`;
1412
1359
  }
1413
- fs.rmSync(worktree, { recursive: true, force: true });
1360
+ removeDirRecursive(worktree);
1414
1361
  }
1415
1362
 
1416
1363
  const branchExists = runOk('git', [
@@ -1507,7 +1454,10 @@ export async function create(args: string[]): Promise<void> {
1507
1454
  }
1508
1455
  }
1509
1456
 
1510
- const aliasesFile = ensureSandboxAliasesFile(effectiveConfig.home);
1457
+ const aliasesFile = ensureSandboxAliasesFile(
1458
+ effectiveConfig.home,
1459
+ capabilityPlan.aliases
1460
+ );
1511
1461
  if (aliasesFile.created) {
1512
1462
  message(`Created default sandbox aliases at ${aliasesFile.path}`);
1513
1463
  }
@@ -1543,6 +1493,28 @@ export async function create(args: string[]): Promise<void> {
1543
1493
  let hostShellConfig: HostShellConfig;
1544
1494
  let tmpfsSeedPlan: TmpfsSeedPlanEntry[] = [];
1545
1495
  try {
1496
+ const beforeCreateResults = await runSandboxHooks({
1497
+ hooks: capabilityPlan.hooksByPhase['before-container-create'],
1498
+ phase: 'before-container-create',
1499
+ context: {
1500
+ config: effectiveConfig,
1501
+ plan: capabilityPlan,
1502
+ create: {
1503
+ hostHome: effectiveConfig.home,
1504
+ resolvedTools: effectiveResolvedTools
1505
+ }
1506
+ },
1507
+ runCommand: runBoundedSandboxHookCommand
1508
+ });
1509
+ const beforeCreateFailure = beforeCreateResults.find(
1510
+ (result) => result.status === 'fatal'
1511
+ );
1512
+ if (beforeCreateFailure) {
1513
+ throw new Error(
1514
+ beforeCreateFailure.message
1515
+ ?? `Sandbox hook '${beforeCreateFailure.hookId}' failed.`
1516
+ );
1517
+ }
1546
1518
  const claudeCodeEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'claude-code');
1547
1519
  if (claudeCodeEntry) {
1548
1520
  ensureClaudeOnboarding(claudeCodeEntry.dir, effectiveConfig.home);
@@ -1551,11 +1523,6 @@ export async function create(args: string[]): Promise<void> {
1551
1523
  // provider/API settings are enough for Claude Code. If no usable
1552
1524
  // auth was present, the claude-code entry was removed above.
1553
1525
  }
1554
- const codexEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'codex');
1555
- if (codexEntry) {
1556
- ensureCodexModelInheritance(codexEntry.dir, effectiveConfig.home, codexEntry.tool.containerMount);
1557
- ensureCodexWorkspaceTrust(codexEntry.dir);
1558
- }
1559
1526
  const geminiEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'gemini-cli');
1560
1527
  if (geminiEntry) {
1561
1528
  ensureGeminiWorkspaceTrust(geminiEntry.dir);
@@ -1643,6 +1610,8 @@ export async function create(args: string[]): Promise<void> {
1643
1610
  sandboxLabel(effectiveConfig),
1644
1611
  '--label',
1645
1612
  `${sandboxBranchLabel(effectiveConfig)}=${branch}`,
1613
+ '--label',
1614
+ `${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
1646
1615
  ...coreVolumes,
1647
1616
  ...buildClipboardVolumeArgs(engine, effectiveConfig.home),
1648
1617
  '-v',
@@ -1739,6 +1708,22 @@ export async function create(args: string[]): Promise<void> {
1739
1708
  }
1740
1709
  }
1741
1710
 
1711
+ const afterStartResults = await runSandboxHooks({
1712
+ hooks: capabilityPlan.hooksByPhase['after-container-start'],
1713
+ phase: 'after-container-start',
1714
+ context: { config: effectiveConfig, plan: capabilityPlan },
1715
+ runCommand: runBoundedSandboxHookCommand
1716
+ });
1717
+ const afterStartFailure = afterStartResults.find(
1718
+ (result) => result.status === 'fatal'
1719
+ );
1720
+ if (afterStartFailure) {
1721
+ throw new Error(
1722
+ afterStartFailure.message
1723
+ ?? `Sandbox hook '${afterStartFailure.hookId}' failed.`
1724
+ );
1725
+ }
1726
+
1742
1727
  return 'Container started';
1743
1728
  }
1744
1729
  }
@@ -7,6 +7,11 @@ import {
7
7
  } from '../constants.ts';
8
8
  import { detectEngine } from '../engine.ts';
9
9
  import { ensureSandboxReady } from '../recovery.ts';
10
+ import {
11
+ createSandboxCapabilityPlan,
12
+ runBoundedSandboxHookCommand,
13
+ runSandboxHooks
14
+ } from '../agent-client-reconciler.ts';
10
15
  import {
11
16
  formatCredentialWarnings,
12
17
  formatRemaining,
@@ -186,8 +191,25 @@ export async function enter(args: string[]): Promise<number> {
186
191
  });
187
192
  const container = ready.container;
188
193
  const cmd = parsed.command;
194
+ const capabilityPlan = createSandboxCapabilityPlan(config);
195
+ const enterHookResults = await runSandboxHooks({
196
+ hooks: capabilityPlan.hooksByPhase['before-enter'],
197
+ phase: 'before-enter',
198
+ context: { config, plan: capabilityPlan },
199
+ runCommand: runBoundedSandboxHookCommand
200
+ });
201
+ for (const result of enterHookResults) {
202
+ if (result.status === 'fatal') {
203
+ throw new Error(result.message ?? `Sandbox hook '${result.hookId}' failed.`);
204
+ }
205
+ if (result.status === 'warning') {
206
+ process.stderr.write(
207
+ `Warning: ${result.message ?? `Sandbox hook '${result.hookId}' did not complete.`}\n`
208
+ );
209
+ }
210
+ }
189
211
 
190
- if (config.tools.includes('claude-code')) {
212
+ if (capabilityPlan.selectedAgentClients.some((adapter) => adapter.id === 'claude-code')) {
191
213
  try {
192
214
  // Scan all projects so a refresh from a neighbouring sandbox can still flow back to the host.
193
215
  const providerAuthAvailable = hasClaudeProviderAuth(config.home);