@fitlab-ai/agent-infra 0.9.2 → 0.9.4

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 (428) hide show
  1. package/README.md +12 -9
  2. package/README.zh-CN.md +12 -9
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +134 -108
  6. package/dist/lib/agent-clients/adapter.js +137 -4
  7. package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
  8. package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
  9. package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
  10. package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
  11. package/dist/lib/agent-clients/adapters/claude-code.js +27 -4
  12. package/dist/lib/agent-clients/adapters/codex.js +9 -0
  13. package/dist/lib/agent-clients/adapters/index.js +2 -2
  14. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
  15. package/dist/lib/agent-clients/adapters/opencode.js +24 -3
  16. package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  17. package/dist/lib/agent-clients/config.js +42 -4
  18. package/dist/lib/agent-clients/next-steps.js +11 -2
  19. package/dist/lib/agent-clients/reconcile.js +2 -1
  20. package/dist/lib/agent-clients/registry.js +16 -2
  21. package/dist/lib/agent-clients/schema.js +18 -0
  22. package/dist/lib/agent-clients/tokens.js +49 -0
  23. package/dist/lib/agent-clients/types.js +1 -1
  24. package/dist/lib/defaults.json +1 -1
  25. package/dist/lib/git/workflow.js +54 -1
  26. package/dist/lib/internal/agent-client.js +61 -4
  27. package/dist/lib/internal/git-workflow.js +6 -1
  28. package/dist/lib/internal/platform-checks.js +3 -3
  29. package/dist/lib/internal/platform-comment.js +29 -2
  30. package/dist/lib/internal/platform-issue.js +7 -0
  31. package/dist/lib/internal/platform-pr-review.js +132 -0
  32. package/dist/lib/internal/platform-pr.js +20 -2
  33. package/dist/lib/internal/platform-release-notes.js +52 -16
  34. package/dist/lib/internal/pr-review-grade.js +140 -0
  35. package/dist/lib/internal/release-workflow.js +13 -6
  36. package/dist/lib/internal/sandbox-control.js +24 -0
  37. package/dist/lib/internal/task-activity.js +105 -0
  38. package/dist/lib/internal/task-event.js +17 -1
  39. package/dist/lib/internal/task-ledger.js +12 -3
  40. package/dist/lib/internal/task-lifecycle.js +26 -1
  41. package/dist/lib/internal/task-orchestration.js +167 -18
  42. package/dist/lib/internal/task-review.js +8 -3
  43. package/dist/lib/platform/adapters.js +8 -1
  44. package/dist/lib/platform/completion-backfill.js +122 -0
  45. package/dist/lib/platform/github-client.js +19 -2
  46. package/dist/lib/platform/issue-comments.js +3 -2
  47. package/dist/lib/platform/merged-pr-equivalence.js +115 -5
  48. package/dist/lib/platform/pr-checks.js +65 -42
  49. package/dist/lib/platform/pr-review.js +166 -0
  50. package/dist/lib/platform/pull-requests.js +371 -4
  51. package/dist/lib/platform/release-notes.js +85 -3
  52. package/dist/lib/platform/releases.js +13 -5
  53. package/dist/lib/pr-review/evidence-grading.js +229 -0
  54. package/dist/lib/process-data/index.js +239 -0
  55. package/dist/lib/process-data/normalize.js +198 -0
  56. package/dist/lib/process-data/privacy.js +33 -0
  57. package/dist/lib/process-data/reconcile.js +95 -0
  58. package/dist/lib/process-data/repair.js +58 -0
  59. package/dist/lib/process-data/sources.js +345 -0
  60. package/dist/lib/process-data/store.js +282 -0
  61. package/dist/lib/process-data/types.js +2 -0
  62. package/dist/lib/run/index.js +4 -3
  63. package/dist/lib/run/tui.js +10 -13
  64. package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
  65. package/dist/lib/sandbox/capture.js +7 -2
  66. package/dist/lib/sandbox/commands/create.js +109 -248
  67. package/dist/lib/sandbox/commands/enter.js +15 -47
  68. package/dist/lib/sandbox/commands/list-running.js +16 -6
  69. package/dist/lib/sandbox/commands/ls.js +10 -6
  70. package/dist/lib/sandbox/commands/rebuild.js +4 -3
  71. package/dist/lib/sandbox/commands/refresh.js +4 -84
  72. package/dist/lib/sandbox/commands/rm.js +24 -3
  73. package/dist/lib/sandbox/commands/show.js +13 -4
  74. package/dist/lib/sandbox/commands/start.js +7 -4
  75. package/dist/lib/sandbox/config.js +2 -0
  76. package/dist/lib/sandbox/constants.js +6 -0
  77. package/dist/lib/sandbox/control/client.js +44 -0
  78. package/dist/lib/sandbox/control/protocol.js +35 -0
  79. package/dist/lib/sandbox/control/server.js +109 -0
  80. package/dist/lib/sandbox/dockerfile.js +36 -13
  81. package/dist/lib/sandbox/mounts.js +17 -4
  82. package/dist/lib/sandbox/recovery.js +128 -5
  83. package/dist/lib/sandbox/redaction.js +12 -0
  84. package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
  85. package/dist/lib/sandbox/workspace-identity.js +95 -0
  86. package/dist/lib/sandbox/workspace-view.js +103 -0
  87. package/dist/lib/task/activity-intent.js +307 -0
  88. package/dist/lib/task/artifact-lifecycle.js +2 -1
  89. package/dist/lib/task/commands/log.js +15 -14
  90. package/dist/lib/task/commit-finalization.js +267 -0
  91. package/dist/lib/task/commit-intent.js +165 -0
  92. package/dist/lib/task/decision-intents.js +31 -17
  93. package/dist/lib/task/delegation-receipts.js +25 -4
  94. package/dist/lib/task/events.js +67 -28
  95. package/dist/lib/task/finalization-artifacts.js +35 -0
  96. package/dist/lib/task/implementation-inputs.js +54 -7
  97. package/dist/lib/task/ledger-intents.js +35 -1
  98. package/dist/lib/task/lifecycle-execution.js +46 -0
  99. package/dist/lib/task/orchestration.js +753 -54
  100. package/dist/lib/task/resolve-ref.js +41 -1
  101. package/dist/lib/task/review-finalization.js +29 -9
  102. package/dist/lib/task/task-execution-lock.js +231 -0
  103. package/dist/lib/task/verification-checks.js +1 -1
  104. package/dist/lib/task/verification-engine.js +218 -2
  105. package/dist/lib/task/verification.js +4 -3
  106. package/dist/lib/task/workspace-snapshot.js +17 -5
  107. package/lib/agent-clients/adapter.ts +201 -4
  108. package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
  109. package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
  110. package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
  111. package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
  112. package/lib/agent-clients/adapters/claude-code.ts +31 -4
  113. package/lib/agent-clients/adapters/codex.ts +9 -0
  114. package/lib/agent-clients/adapters/index.ts +2 -2
  115. package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
  116. package/lib/agent-clients/adapters/opencode.ts +27 -3
  117. package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  118. package/lib/agent-clients/config.ts +49 -4
  119. package/lib/agent-clients/next-steps.ts +15 -2
  120. package/lib/agent-clients/reconcile.ts +2 -1
  121. package/lib/agent-clients/registry.ts +19 -1
  122. package/lib/agent-clients/schema.ts +18 -0
  123. package/lib/agent-clients/tokens.ts +53 -0
  124. package/lib/agent-clients/types.ts +15 -1
  125. package/lib/defaults.json +1 -1
  126. package/lib/git/workflow.ts +66 -2
  127. package/lib/internal/agent-client.ts +59 -4
  128. package/lib/internal/git-workflow.ts +5 -1
  129. package/lib/internal/platform-checks.ts +4 -4
  130. package/lib/internal/platform-comment.ts +17 -2
  131. package/lib/internal/platform-issue.ts +4 -0
  132. package/lib/internal/platform-pr-review.ts +104 -0
  133. package/lib/internal/platform-pr.ts +14 -1
  134. package/lib/internal/platform-release-notes.ts +59 -16
  135. package/lib/internal/pr-review-grade.ts +130 -0
  136. package/lib/internal/release-workflow.ts +13 -6
  137. package/lib/internal/sandbox-control.ts +24 -0
  138. package/lib/internal/task-activity.ts +91 -0
  139. package/lib/internal/task-event.ts +9 -1
  140. package/lib/internal/task-ledger.ts +12 -3
  141. package/lib/internal/task-lifecycle.ts +26 -1
  142. package/lib/internal/task-orchestration.ts +123 -14
  143. package/lib/internal/task-review.ts +8 -3
  144. package/lib/platform/adapters.ts +29 -2
  145. package/lib/platform/completion-backfill.ts +134 -0
  146. package/lib/platform/github-client.ts +20 -2
  147. package/lib/platform/issue-comments.ts +3 -2
  148. package/lib/platform/merged-pr-equivalence.ts +127 -5
  149. package/lib/platform/pr-checks.ts +73 -46
  150. package/lib/platform/pr-review.ts +201 -0
  151. package/lib/platform/pull-requests.ts +444 -3
  152. package/lib/platform/release-notes.ts +100 -4
  153. package/lib/platform/releases.ts +16 -4
  154. package/lib/pr-review/evidence-grading.ts +318 -0
  155. package/lib/process-data/index.ts +224 -0
  156. package/lib/process-data/normalize.ts +202 -0
  157. package/lib/process-data/privacy.ts +41 -0
  158. package/lib/process-data/reconcile.ts +110 -0
  159. package/lib/process-data/repair.ts +64 -0
  160. package/lib/process-data/sources.ts +354 -0
  161. package/lib/process-data/store.ts +315 -0
  162. package/lib/process-data/types.ts +135 -0
  163. package/lib/run/index.ts +5 -3
  164. package/lib/run/tui.ts +14 -12
  165. package/lib/sandbox/agent-client-reconciler.ts +44 -2
  166. package/lib/sandbox/capture.ts +9 -2
  167. package/lib/sandbox/commands/create.ts +126 -280
  168. package/lib/sandbox/commands/enter.ts +13 -59
  169. package/lib/sandbox/commands/list-running.ts +23 -6
  170. package/lib/sandbox/commands/ls.ts +22 -6
  171. package/lib/sandbox/commands/rebuild.ts +4 -3
  172. package/lib/sandbox/commands/refresh.ts +4 -126
  173. package/lib/sandbox/commands/rm.ts +24 -3
  174. package/lib/sandbox/commands/show.ts +30 -4
  175. package/lib/sandbox/commands/start.ts +6 -4
  176. package/lib/sandbox/config.ts +4 -0
  177. package/lib/sandbox/constants.ts +8 -0
  178. package/lib/sandbox/control/client.ts +55 -0
  179. package/lib/sandbox/control/protocol.ts +75 -0
  180. package/lib/sandbox/control/server.ts +117 -0
  181. package/lib/sandbox/dockerfile.ts +66 -13
  182. package/lib/sandbox/mounts.ts +24 -4
  183. package/lib/sandbox/recovery.ts +163 -6
  184. package/lib/sandbox/redaction.ts +14 -0
  185. package/lib/sandbox/runtimes/base.dockerfile +24 -74
  186. package/lib/sandbox/tool-types.ts +16 -0
  187. package/lib/sandbox/workspace-identity.ts +123 -0
  188. package/lib/sandbox/workspace-view.ts +143 -0
  189. package/lib/server/server.schema.json +2 -2
  190. package/lib/task/activity-intent.ts +417 -0
  191. package/lib/task/artifact-lifecycle.ts +2 -1
  192. package/lib/task/commands/log.ts +15 -14
  193. package/lib/task/commit-finalization.ts +356 -0
  194. package/lib/task/commit-intent.ts +230 -0
  195. package/lib/task/decision-intents.ts +32 -12
  196. package/lib/task/delegation-receipts.ts +34 -5
  197. package/lib/task/events.ts +75 -30
  198. package/lib/task/finalization-artifacts.ts +58 -0
  199. package/lib/task/implementation-inputs.ts +62 -8
  200. package/lib/task/ledger-intents.ts +43 -3
  201. package/lib/task/lifecycle-execution.ts +106 -0
  202. package/lib/task/orchestration.ts +1019 -72
  203. package/lib/task/resolve-ref.ts +46 -1
  204. package/lib/task/review-finalization.ts +40 -9
  205. package/lib/task/task-execution-lock.ts +296 -0
  206. package/lib/task/verification-checks.ts +1 -1
  207. package/lib/task/verification-engine.ts +249 -2
  208. package/lib/task/verification.ts +5 -3
  209. package/lib/task/workspace-snapshot.ts +26 -5
  210. package/package.json +3 -3
  211. package/templates/.agents/QUICKSTART.en.md +25 -8
  212. package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
  213. package/templates/.agents/README.en.md +10 -7
  214. package/templates/.agents/README.zh-CN.md +10 -7
  215. package/templates/.agents/hooks/lifecycle-delegation.js +15 -4
  216. package/templates/.agents/rules/README.en.md +1 -1
  217. package/templates/.agents/rules/README.zh-CN.md +1 -1
  218. package/templates/.agents/rules/create-issue.github.en.md +4 -2
  219. package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
  220. package/templates/.agents/rules/human-decision-context.en.md +11 -2
  221. package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
  222. package/templates/.agents/rules/issue-fields.github.en.md +3 -1
  223. package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
  224. package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
  225. package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
  226. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
  227. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
  228. package/templates/.agents/rules/issue-sync.github.en.md +7 -3
  229. package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
  230. package/templates/.agents/rules/lifecycle-orchestration.en.md +5 -2
  231. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +5 -2
  232. package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
  233. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
  234. package/templates/.agents/rules/next-step-output.en.md +3 -1
  235. package/templates/.agents/rules/next-step-output.zh-CN.md +3 -1
  236. package/templates/.agents/rules/no-mid-flow-questions.en.md +17 -0
  237. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +17 -0
  238. package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
  239. package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
  240. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +3 -3
  241. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
  242. package/templates/.agents/rules/pr-sync.en.md +3 -1
  243. package/templates/.agents/rules/pr-sync.github.en.md +11 -1
  244. package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
  245. package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
  246. package/templates/.agents/rules/release-commands.en.md +1 -1
  247. package/templates/.agents/rules/release-commands.github.en.md +12 -2
  248. package/templates/.agents/rules/release-commands.github.zh-CN.md +12 -2
  249. package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
  250. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  251. package/templates/.agents/rules/task-management.en.md +13 -0
  252. package/templates/.agents/rules/task-management.zh-CN.md +13 -0
  253. package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -7
  254. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -7
  255. package/templates/.agents/skills/block-task/SKILL.en.md +7 -5
  256. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +7 -5
  257. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -4
  258. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -4
  259. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
  260. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
  261. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
  262. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
  263. package/templates/.agents/skills/code-task/SKILL.en.md +10 -7
  264. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +12 -9
  265. package/templates/.agents/skills/commit/SKILL.en.md +23 -11
  266. package/templates/.agents/skills/commit/SKILL.zh-CN.md +29 -11
  267. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  268. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  269. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
  270. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -0
  271. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  272. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  273. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  274. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  275. package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
  276. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
  277. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +6 -4
  278. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
  279. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  280. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  281. package/templates/.agents/skills/complete-task/SKILL.en.md +22 -7
  282. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +22 -7
  283. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  284. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  285. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
  286. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
  287. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -5
  288. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -5
  289. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  290. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  291. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
  292. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
  293. package/templates/.agents/skills/create-task/SKILL.en.md +5 -3
  294. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -3
  295. package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
  296. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
  297. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -3
  298. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -3
  299. package/templates/.agents/skills/plan-task/SKILL.en.md +8 -5
  300. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
  301. package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
  302. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
  303. package/templates/.agents/skills/release/SKILL.en.md +16 -10
  304. package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
  305. package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
  306. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
  307. package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -4
  308. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -4
  309. package/templates/.agents/skills/review-code/SKILL.en.md +11 -8
  310. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -8
  311. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  312. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  313. package/templates/.agents/skills/review-plan/SKILL.en.md +7 -4
  314. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -4
  315. package/templates/.agents/skills/review-pr/SKILL.en.md +186 -0
  316. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +186 -0
  317. package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
  318. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
  319. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
  320. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
  321. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
  322. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
  323. package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
  324. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
  325. package/templates/.agents/skills/run-task/SKILL.en.md +8 -8
  326. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +8 -8
  327. package/templates/.agents/skills/run-task/config/verify.en.json +2 -1
  328. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +2 -1
  329. package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
  330. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
  331. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +98 -107
  332. package/templates/.agents/skills/watch-pr/SKILL.en.md +13 -13
  333. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +13 -13
  334. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
  335. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
  336. package/templates/.agents/templates/task.en.md +2 -2
  337. package/templates/.agents/templates/task.zh-CN.md +2 -2
  338. package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
  339. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
  340. package/templates/.agents/workflows/code-review.en.yaml +1 -1
  341. package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
  342. package/templates/.agents/workflows/feature-development.en.yaml +4 -4
  343. package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
  344. package/templates/.agents/workflows/refactoring.en.yaml +3 -3
  345. package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
  346. package/templates/.claude/commands/post-release.en.md +1 -0
  347. package/templates/.claude/commands/post-release.zh-CN.md +1 -0
  348. package/templates/.claude/commands/review-pr.en.md +8 -0
  349. package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
  350. package/templates/.claude/commands/watch-pr.en.md +1 -1
  351. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  352. package/templates/.claude/rules/tool-preferences.en.md +20 -0
  353. package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
  354. package/templates/.opencode/commands/post-release.en.md +2 -0
  355. package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
  356. package/templates/.opencode/commands/review-pr.en.md +11 -0
  357. package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
  358. package/templates/.opencode/commands/watch-pr.en.md +1 -1
  359. package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
  360. package/dist/lib/agent-clients/adapters/gemini-cli.js +0 -64
  361. package/lib/agent-clients/adapters/gemini-cli.ts +0 -65
  362. package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
  363. package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
  364. package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
  365. package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
  366. package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
  367. package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
  368. package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
  369. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
  370. package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
  371. package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
  372. package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
  373. package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
  374. package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
  375. package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
  376. package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
  377. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
  378. package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
  379. package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
  380. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
  381. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
  382. package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
  383. package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
  384. package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
  385. package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
  386. package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
  387. package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
  388. package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
  389. package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
  390. package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
  391. package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
  392. package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
  393. package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
  394. package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
  395. package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
  396. package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
  397. package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
  398. package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
  399. package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
  400. package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
  401. package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
  402. package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
  403. package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
  404. package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
  405. package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
  406. package/templates/.gemini/commands/_project_/release.en.toml +0 -8
  407. package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
  408. package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
  409. package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
  410. package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
  411. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
  412. package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
  413. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
  414. package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
  415. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
  416. package/templates/.gemini/commands/_project_/run-task.en.toml +0 -8
  417. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +0 -8
  418. package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
  419. package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
  420. package/templates/.gemini/commands/_project_/test.en.toml +0 -6
  421. package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
  422. package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
  423. package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
  424. package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
  425. package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
  426. package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
  427. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
  428. package/templates/.gemini/settings.json +0 -3
@@ -2,9 +2,10 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { createHash } from 'node:crypto';
5
- import { execFileSync } from 'node:child_process';
5
+ import { execFileSync, spawn } from 'node:child_process';
6
6
  import type { ExecFileSyncOptions, StdioOptions } from 'node:child_process';
7
7
  import { parseArgs } from 'node:util';
8
+ import { fileURLToPath } from 'node:url';
8
9
  import * as p from '@clack/prompts';
9
10
  import pc from 'picocolors';
10
11
  import { listAgentClientAdapters } from '../../agent-clients/registry.ts';
@@ -20,6 +21,8 @@ import {
20
21
  sandboxImageRefreshLabel,
21
22
  sandboxLabel,
22
23
  sandboxRuntimeCapabilityLabel,
24
+ sandboxTaskIdLabel,
25
+ sandboxWorkspaceModeLabel,
23
26
  shareBranchDir,
24
27
  shareCommonDir,
25
28
  shellConfigDir,
@@ -44,7 +47,11 @@ import {
44
47
  runSafeEngine,
45
48
  runVerboseEngine
46
49
  } from '../shell.ts';
47
- import { resolveTaskBranch } from '../task-resolver.ts';
50
+ import {
51
+ parseSandboxWorkspaceIdentity,
52
+ resolveSandboxTarget,
53
+ sameSandboxWorkspaceIdentity
54
+ } from '../workspace-identity.ts';
48
55
  import {
49
56
  createSandboxCapabilityPlan,
50
57
  runSandboxHooks,
@@ -64,16 +71,17 @@ import {
64
71
  } from '../recovery.ts';
65
72
  import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.ts';
66
73
  import { sandboxCoreBindMounts } from '../mounts.ts';
74
+ import {
75
+ assertSandboxTaskSource,
76
+ materializeSandboxControl,
77
+ materializeSandboxWorkspaceView
78
+ } from '../workspace-view.ts';
67
79
  import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.ts';
68
80
  import { validateSelinuxDisableEnv } from '../engines/selinux.ts';
69
81
  import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.ts';
70
82
  import { ensureSandboxDiscoveryReadmes } from '../readme-scaffold.ts';
71
83
  import { removeDirRecursive } from '../../remove-dir.ts';
72
- import {
73
- prepareClaudeCredentials,
74
- redactCommandError,
75
- validateClaudeCredentialsEnvOverride
76
- } from '../credentials.ts';
84
+ import { redactCommandError } from '../redaction.ts';
77
85
  import { detectHostTimezone } from '../host-timezone.ts';
78
86
  import {
79
87
  buildImageSignature,
@@ -84,10 +92,6 @@ import {
84
92
  parseRefreshTimestamp
85
93
  } from '../image-build.ts';
86
94
 
87
- export {
88
- ensureCodexModelInheritance,
89
- ensureCodexWorkspaceTrust
90
- } from '../../agent-clients/adapters/codex-sandbox.ts';
91
95
 
92
96
  const SANDBOX_ALIAS_BLOCK_BEGIN = '# >>> agent-infra managed aliases >>>';
93
97
  const SANDBOX_ALIAS_BLOCK_END = '# <<< agent-infra managed aliases <<<';
@@ -133,7 +137,6 @@ type TmpfsSeedPlanEntry = TmpfsSeedEntry & {
133
137
  volumeArgs: string[];
134
138
  };
135
139
  type RuntimeCheck = { name: string; cmd: string[] };
136
- type JsonObject = Record<string, unknown>;
137
140
  type GpgCache = { pub: Buffer; sec: Buffer } | null;
138
141
  type ExecSyncOptions = ExecFileSyncOptions & {
139
142
  input?: Buffer | string;
@@ -154,6 +157,12 @@ type HostShellConfig = {
154
157
  mounts: Array<{ hostPath: string; containerPath: string }>;
155
158
  };
156
159
 
160
+ function internalCliPath(): string {
161
+ const directory = path.dirname(fileURLToPath(import.meta.url));
162
+ const extension = path.extname(fileURLToPath(import.meta.url));
163
+ return path.resolve(directory, '..', '..', '..', 'bin', `internal-cli${extension}`);
164
+ }
165
+
157
166
  function resolveToolDirs(config: Pick<SandboxCreateConfig, 'project'>, tools: SandboxTool[], branch: string): ResolvedTool[] {
158
167
  return tools.map((tool) => {
159
168
  const candidates = toolConfigDirCandidates(tool, config.project, branch);
@@ -807,225 +816,6 @@ export function assertBranchAvailable(
807
816
  }
808
817
  }
809
818
 
810
- function readHostJsonSafe(filePath: string): JsonObject | null {
811
- if (!filePath || !fs.existsSync(filePath)) {
812
- return null;
813
- }
814
-
815
- try {
816
- const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8')) as unknown;
817
- return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as JsonObject : null;
818
- } catch {
819
- return null;
820
- }
821
- }
822
-
823
- const CLAUDE_SETTINGS_INHERIT_TOP_LEVEL_KEYS = [
824
- 'model',
825
- 'fallbackModel',
826
- 'availableModels',
827
- 'modelOverrides',
828
- 'enforceAvailableModels',
829
- 'advisorModel',
830
- 'apiKeyHelper',
831
- 'effortLevel'
832
- ];
833
-
834
- function isJsonObjectRecord(value: unknown): value is JsonObject {
835
- return value !== null && typeof value === 'object' && !Array.isArray(value);
836
- }
837
-
838
- function mergeMissingStringEnvFields(target: JsonObject, source: JsonObject): boolean {
839
- if (!isJsonObjectRecord(source.env)) {
840
- return false;
841
- }
842
- if (Object.hasOwn(target, 'env') && !isJsonObjectRecord(target.env)) {
843
- return false;
844
- }
845
-
846
- let targetEnv = target.env as JsonObject | undefined;
847
- let changed = false;
848
- for (const [key, value] of Object.entries(source.env)) {
849
- if (typeof value !== 'string' || value === '') {
850
- continue;
851
- }
852
- if (!targetEnv) {
853
- targetEnv = {};
854
- target.env = targetEnv;
855
- }
856
- if (!Object.hasOwn(targetEnv, key)) {
857
- targetEnv[key] = value;
858
- changed = true;
859
- }
860
- }
861
- return changed;
862
- }
863
-
864
- function mergeMissingTopLevelSettings(target: JsonObject, source: JsonObject): boolean {
865
- let changed = false;
866
- for (const key of CLAUDE_SETTINGS_INHERIT_TOP_LEVEL_KEYS) {
867
- if (!Object.hasOwn(source, key) || Object.hasOwn(target, key)) {
868
- continue;
869
- }
870
- const value = source[key];
871
- if (value === null || value === undefined || value === '') {
872
- continue;
873
- }
874
- target[key] = value;
875
- changed = true;
876
- }
877
- return changed;
878
- }
879
-
880
- export function ensureClaudeOnboarding(toolDir: string, hostHomeDir?: string): void {
881
- const claudeJsonPath = path.join(toolDir, '.claude.json');
882
- let data: JsonObject & {
883
- hasCompletedOnboarding?: boolean;
884
- projects?: Record<string, { hasTrustDialogAccepted?: boolean }>;
885
- model?: string;
886
- } = {};
887
- if (fs.existsSync(claudeJsonPath)) {
888
- try {
889
- data = JSON.parse(fs.readFileSync(claudeJsonPath, 'utf8')) as typeof data;
890
- } catch {
891
- // malformed JSON, start fresh
892
- }
893
- }
894
- let changed = false;
895
- if (!data.hasCompletedOnboarding) {
896
- data.hasCompletedOnboarding = true;
897
- changed = true;
898
- }
899
- if (!data.projects) {
900
- data.projects = {};
901
- changed = true;
902
- }
903
- if (!data.projects['/workspace']) {
904
- data.projects['/workspace'] = {};
905
- changed = true;
906
- }
907
- if (!data.projects['/workspace'].hasTrustDialogAccepted) {
908
- data.projects['/workspace'].hasTrustDialogAccepted = true;
909
- changed = true;
910
- }
911
- if (hostHomeDir) {
912
- const hostClaudeJson = readHostJsonSafe(path.join(hostHomeDir, '.claude.json'));
913
- if (
914
- hostClaudeJson
915
- && typeof hostClaudeJson.model === 'string'
916
- && hostClaudeJson.model !== ''
917
- && !Object.hasOwn(data, 'model')
918
- ) {
919
- data.model = hostClaudeJson.model;
920
- changed = true;
921
- }
922
- // Claude Code launch-pins a default effort per model generation (for
923
- // example xhigh for Opus 4.7). The saved effortLevel is honored only after
924
- // a top-level boolean `unpin*LaunchEffort: true` flag unlocks it, so mirror
925
- // those flags here with the existing first-write semantics.
926
- //
927
- // Pattern matching avoids one patch per future model generation. If
928
- // Anthropic changes the naming convention, this block will no-op and should
929
- // be revisited.
930
- if (hostClaudeJson) {
931
- for (const key of Object.keys(hostClaudeJson)) {
932
- if (
933
- /^unpin.*LaunchEffort$/.test(key)
934
- && hostClaudeJson[key] === true
935
- && !Object.hasOwn(data, key)
936
- ) {
937
- data[key] = true;
938
- changed = true;
939
- }
940
- }
941
- }
942
- }
943
- if (changed) {
944
- fs.writeFileSync(claudeJsonPath, JSON.stringify(data, null, 4), 'utf8');
945
- }
946
- }
947
-
948
- export function ensureClaudeSettings(toolDir: string, hostHomeDir?: string): void {
949
- const settingsPath = path.join(toolDir, 'settings.json');
950
- let data: JsonObject & { skipDangerousModePermissionPrompt?: boolean } = {};
951
- if (fs.existsSync(settingsPath)) {
952
- try {
953
- data = JSON.parse(fs.readFileSync(settingsPath, 'utf8')) as typeof data;
954
- } catch {
955
- // malformed JSON, start fresh
956
- }
957
- }
958
- let changed = false;
959
- if (data.skipDangerousModePermissionPrompt !== true) {
960
- data.skipDangerousModePermissionPrompt = true;
961
- changed = true;
962
- }
963
- if (hostHomeDir) {
964
- const hostSettings = readHostJsonSafe(path.join(hostHomeDir, '.claude', 'settings.json'));
965
- if (hostSettings) {
966
- changed = mergeMissingStringEnvFields(data, hostSettings) || changed;
967
- changed = mergeMissingTopLevelSettings(data, hostSettings) || changed;
968
- }
969
- }
970
- if (changed) {
971
- fs.writeFileSync(settingsPath, JSON.stringify(data, null, 4), 'utf8');
972
- }
973
- }
974
-
975
- export function ensureOpenCodeModelInheritance(toolDir: string, hostHomeDir?: string): void {
976
- if (!hostHomeDir) {
977
- return;
978
- }
979
-
980
- const hostConfigPath = path.join(hostHomeDir, '.config', 'opencode', 'opencode.json');
981
- const hostJson = readHostJsonSafe(hostConfigPath);
982
- if (!hostJson) {
983
- return;
984
- }
985
-
986
- const sandboxConfigPath = path.join(toolDir, 'opencode.json');
987
- let sandboxJson: JsonObject = {};
988
- if (fs.existsSync(sandboxConfigPath)) {
989
- const existing = readHostJsonSafe(sandboxConfigPath);
990
- if (!existing) {
991
- return;
992
- }
993
- sandboxJson = existing;
994
- }
995
- let changed = false;
996
- for (const key of ['model', 'small_model']) {
997
- if (Object.hasOwn(sandboxJson, key)) {
998
- continue;
999
- }
1000
- const value = hostJson[key];
1001
- if (typeof value !== 'string' || value === '') {
1002
- continue;
1003
- }
1004
- sandboxJson[key] = value;
1005
- changed = true;
1006
- }
1007
-
1008
- if (changed) {
1009
- fs.writeFileSync(sandboxConfigPath, JSON.stringify(sandboxJson, null, 2), 'utf8');
1010
- }
1011
- }
1012
-
1013
- export function ensureGeminiWorkspaceTrust(toolDir: string): void {
1014
- const trustPath = path.join(toolDir, 'trustedFolders.json');
1015
- let data: Record<string, string> = {};
1016
- if (fs.existsSync(trustPath)) {
1017
- try {
1018
- data = JSON.parse(fs.readFileSync(trustPath, 'utf8')) as Record<string, string>;
1019
- } catch {
1020
- // malformed JSON, start fresh
1021
- }
1022
- }
1023
- if (data['/workspace'] !== 'TRUST_FOLDER') {
1024
- data['/workspace'] = 'TRUST_FOLDER';
1025
- fs.writeFileSync(trustPath, JSON.stringify(data, null, 2), 'utf8');
1026
- }
1027
- }
1028
-
1029
819
  export function sandboxAliasesPath(home: string): string {
1030
820
  return hostJoin(home, '.agent-infra', 'aliases', 'sandbox.sh');
1031
821
  }
@@ -1211,11 +1001,10 @@ export async function create(args: string[]): Promise<void> {
1211
1001
  }
1212
1002
 
1213
1003
  validateSelinuxDisableEnv();
1214
- validateClaudeCredentialsEnvOverride();
1215
-
1216
1004
  const config = loadConfig();
1217
1005
  const [branchOrTaskId = '', base] = positionals;
1218
- const branch = resolveTaskBranch(branchOrTaskId, config.repoRoot);
1006
+ const target = resolveSandboxTarget(branchOrTaskId, config.repoRoot);
1007
+ const branch = target.branch;
1219
1008
  assertValidBranchName(branch);
1220
1009
  const effectiveConfig = {
1221
1010
  ...config,
@@ -1230,20 +1019,31 @@ export async function create(args: string[]): Promise<void> {
1230
1019
  const capabilityPlan = createSandboxCapabilityPlan(effectiveConfig);
1231
1020
  const tools = [...capabilityPlan.tools];
1232
1021
  const resolvedTools = resolveToolDirs(effectiveConfig, tools, branch);
1233
- // Fatal credential states still fail before filesystem/docker side effects.
1234
- // Missing credentials leave the selected Claude Code capability installed
1235
- // and mounted; only the absent live credential file is omitted.
1236
- const credentialOutcome = prepareClaudeCredentials(
1237
- effectiveConfig.home,
1238
- effectiveConfig.project,
1239
- resolvedTools
1240
- );
1022
+ const prepareHookResults = await runSandboxHooks({
1023
+ hooks: capabilityPlan.hooksByPhase.prepare,
1024
+ phase: 'prepare',
1025
+ context: {
1026
+ config: effectiveConfig,
1027
+ plan: capabilityPlan,
1028
+ create: {
1029
+ hostHome: effectiveConfig.home,
1030
+ hostEnv: { ...process.env },
1031
+ project: effectiveConfig.project,
1032
+ resolvedTools
1033
+ }
1034
+ },
1035
+ runCommand: runBoundedSandboxHookCommand
1036
+ });
1037
+ const prepareFailure = prepareHookResults.find((result) => result.status === 'fatal');
1038
+ if (prepareFailure) {
1039
+ throw new Error(prepareFailure.message ?? `Sandbox hook '${prepareFailure.hookId}' failed.`);
1040
+ }
1241
1041
  const effectiveResolvedTools = resolvedTools;
1242
1042
  const container = containerName(effectiveConfig, branch);
1243
1043
  const worktree = worktreeCandidates.find((candidate) => fs.existsSync(candidate)) ?? worktreeCandidates[0] ?? '';
1244
1044
  const shareCommon = shareCommonDir(effectiveConfig);
1245
1045
  const shareBranch = shareBranchDir(effectiveConfig, branch);
1246
- const preparedDockerfile = prepareDockerfile(effectiveConfig);
1046
+ const preparedDockerfile = prepareDockerfile(effectiveConfig, capabilityPlan.image);
1247
1047
  const baseBranch = base ?? runSafe('git', ['-C', effectiveConfig.repoRoot, 'branch', '--show-current']);
1248
1048
  const expectedImageSignature = buildImageSignature(preparedDockerfile, tools);
1249
1049
  const engine = detectEngine(effectiveConfig);
@@ -1253,26 +1053,12 @@ export async function create(args: string[]): Promise<void> {
1253
1053
  p.log.info(
1254
1054
  `Project: ${pc.bold(effectiveConfig.project)} | Branch: ${pc.bold(branch)} | Base: ${pc.bold(baseBranch || 'HEAD')}`
1255
1055
  );
1256
- if (credentialOutcome.status === 'SKIPPED') {
1257
- p.log.warn(
1258
- 'Claude Code credentials not found on host - creating this sandbox WITHOUT Claude Code credentials.\n'
1259
- + ' Claude Code is still installed in the image but will not be authenticated.\n'
1260
- + ' To enable it: run "claude" once on the host to complete login, then re-run "ai sandbox create".'
1261
- );
1056
+ for (const result of prepareHookResults) {
1057
+ if (result.status === 'warning' && result.message) p.log.warn(result.message);
1262
1058
  }
1263
1059
 
1264
1060
  try {
1265
1061
  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
- }
1276
1062
  await ensureDocker(effectiveConfig, (detail: string) => {
1277
1063
  p.log.info(` ${detail}`);
1278
1064
  });
@@ -1447,6 +1233,33 @@ export async function create(args: string[]): Promise<void> {
1447
1233
  .filter((name) => existing.includes(name));
1448
1234
 
1449
1235
  if (matchedContainers.length > 0) {
1236
+ for (const name of matchedContainers) {
1237
+ const rawLabels = runSafeEngine(engine, 'docker', [
1238
+ 'inspect', '--format', '{{json .Config.Labels}}', name
1239
+ ]).trim();
1240
+ let labels: Record<string, string> = {};
1241
+ try {
1242
+ labels = JSON.parse(rawLabels) as Record<string, string>;
1243
+ } catch {
1244
+ labels = {};
1245
+ }
1246
+ const existingIdentity = parseSandboxWorkspaceIdentity(labels, {
1247
+ mode: sandboxWorkspaceModeLabel(effectiveConfig),
1248
+ taskId: sandboxTaskIdLabel(effectiveConfig)
1249
+ });
1250
+ if (!sameSandboxWorkspaceIdentity(existingIdentity, target.workspace)) {
1251
+ const existingDescription = existingIdentity.mode === 'task-bound'
1252
+ ? `task-bound:${existingIdentity.taskId}`
1253
+ : existingIdentity.mode;
1254
+ const requestedDescription = target.workspace.mode === 'task-bound'
1255
+ ? `task-bound:${target.workspace.taskId}`
1256
+ : target.workspace.mode;
1257
+ throw new Error(
1258
+ `SANDBOX_WORKSPACE_IDENTITY_CONFLICT: container '${name}' is ${existingDescription}, `
1259
+ + `but this request is ${requestedDescription}. Run 'ai sandbox rm ${branch}' and retry.`
1260
+ );
1261
+ }
1262
+ }
1450
1263
  message('Removing old container instance...');
1451
1264
  for (const name of matchedContainers) {
1452
1265
  runSafeEngine(engine, 'docker', ['stop', name]);
@@ -1501,6 +1314,8 @@ export async function create(args: string[]): Promise<void> {
1501
1314
  plan: capabilityPlan,
1502
1315
  create: {
1503
1316
  hostHome: effectiveConfig.home,
1317
+ hostEnv: { ...process.env },
1318
+ project: effectiveConfig.project,
1504
1319
  resolvedTools: effectiveResolvedTools
1505
1320
  }
1506
1321
  },
@@ -1515,30 +1330,26 @@ export async function create(args: string[]): Promise<void> {
1515
1330
  ?? `Sandbox hook '${beforeCreateFailure.hookId}' failed.`
1516
1331
  );
1517
1332
  }
1518
- const claudeCodeEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'claude-code');
1519
- if (claudeCodeEntry) {
1520
- ensureClaudeOnboarding(claudeCodeEntry.dir, effectiveConfig.home);
1521
- ensureClaudeSettings(claudeCodeEntry.dir, effectiveConfig.home);
1522
- // prepareClaudeCredentials wrote OAuth credentials or confirmed
1523
- // provider/API settings are enough for Claude Code. If no usable
1524
- // auth was present, the claude-code entry was removed above.
1525
- }
1526
- const geminiEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'gemini-cli');
1527
- if (geminiEntry) {
1528
- ensureGeminiWorkspaceTrust(geminiEntry.dir);
1529
- }
1530
- const opencodeEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'opencode');
1531
- if (opencodeEntry) {
1532
- // The TUI reads <toolDir>/opencode.json via OPENCODE_CONFIG pinned in tools.js.
1533
- ensureOpenCodeModelInheritance(opencodeEntry.dir, effectiveConfig.home);
1534
- }
1535
1333
  const toolVolumes = effectiveResolvedTools.flatMap(({ tool, dir }) =>
1536
1334
  tool.tmpfs ? [] : ['-v', volumeArg(engine, dir, tool.containerMount)]
1537
1335
  );
1538
1336
  const tmpfsArgs = effectiveResolvedTools.flatMap(({ tool }) =>
1539
1337
  tool.tmpfs ? buildTmpfsRunArgs(tool.containerMount, tool.tmpfs) : []
1540
1338
  );
1541
- const workspaceDir = path.join(effectiveConfig.repoRoot, '.agents', 'workspace');
1339
+ const workspaceView = materializeSandboxWorkspaceView({
1340
+ base: effectiveConfig.workspaceViewBase,
1341
+ project: effectiveConfig.project,
1342
+ container,
1343
+ identity: target.workspace
1344
+ });
1345
+ const control = materializeSandboxControl({
1346
+ base: effectiveConfig.controlBase,
1347
+ repoRoot: effectiveConfig.repoRoot,
1348
+ project: effectiveConfig.project,
1349
+ container,
1350
+ branch,
1351
+ identity: target.workspace
1352
+ });
1542
1353
  hostShellConfig = prepareHostShellConfig({
1543
1354
  home: effectiveConfig.home,
1544
1355
  project: effectiveConfig.project,
@@ -1547,7 +1358,18 @@ export async function create(args: string[]): Promise<void> {
1547
1358
  });
1548
1359
  const coreVolumes = sandboxCoreBindMounts(effectiveConfig, branch, {
1549
1360
  worktree,
1550
- shellConfigHostDir: hostShellConfig.hostDir
1361
+ shellConfigHostDir: hostShellConfig.hostDir,
1362
+ workspaceViewRoot: workspaceView.root,
1363
+ controlDir: control.channelDir,
1364
+ ...(target.workspace.mode === 'task-bound'
1365
+ ? {
1366
+ taskSource: assertSandboxTaskSource(
1367
+ effectiveConfig.repoRoot,
1368
+ target.workspace.taskId
1369
+ ),
1370
+ taskId: target.workspace.taskId
1371
+ }
1372
+ : {})
1551
1373
  }).flatMap(({ hostPaths, containerPath, readOnly }) => [
1552
1374
  '-v',
1553
1375
  volumeArg(engine, hostPaths[0]!, containerPath, readOnly ? ':ro' : '')
@@ -1584,7 +1406,6 @@ export async function create(args: string[]): Promise<void> {
1584
1406
  ])
1585
1407
  );
1586
1408
 
1587
- fs.mkdirSync(workspaceDir, { recursive: true });
1588
1409
  fs.mkdirSync(shareCommon, { recursive: true });
1589
1410
  fs.mkdirSync(shareBranch, { recursive: true });
1590
1411
  fs.mkdirSync(clipboardHostDir(effectiveConfig.home), { recursive: true, mode: 0o700 });
@@ -1611,6 +1432,20 @@ export async function create(args: string[]): Promise<void> {
1611
1432
  '--label',
1612
1433
  `${sandboxBranchLabel(effectiveConfig)}=${branch}`,
1613
1434
  '--label',
1435
+ `${sandboxWorkspaceModeLabel(effectiveConfig)}=${target.workspace.mode}`,
1436
+ ...(target.workspace.mode === 'task-bound'
1437
+ ? [
1438
+ '--label',
1439
+ `${sandboxTaskIdLabel(effectiveConfig)}=${target.workspace.taskId}`,
1440
+ '-e',
1441
+ `AGENT_INFRA_TASK_ID=${target.workspace.taskId}`
1442
+ ]
1443
+ : []),
1444
+ '-e',
1445
+ `AGENT_INFRA_CONTROL_TOKEN=${control.token}`,
1446
+ '-e',
1447
+ 'AGENT_INFRA_CONTROL_DIR=/run/agent-infra/control',
1448
+ '--label',
1614
1449
  `${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
1615
1450
  ...coreVolumes,
1616
1451
  ...buildClipboardVolumeArgs(engine, effectiveConfig.home),
@@ -1631,6 +1466,16 @@ export async function create(args: string[]): Promise<void> {
1631
1466
  '/workspace',
1632
1467
  effectiveConfig.imageName
1633
1468
  ]);
1469
+ const broker = spawn(
1470
+ process.execPath,
1471
+ [internalCliPath(), 'sandbox-control', 'serve', '--manifest', control.manifestPath],
1472
+ { cwd: effectiveConfig.repoRoot, detached: true, stdio: 'ignore' }
1473
+ );
1474
+ broker.once('error', () => {
1475
+ // Fresh readiness verification below remains the source of truth;
1476
+ // consume detached spawn errors so they cannot crash the command.
1477
+ });
1478
+ broker.unref();
1634
1479
  createdTmpfsSeedPlan = tmpfsSeedPlan;
1635
1480
  } finally {
1636
1481
  envFile.cleanup();
@@ -1733,10 +1578,11 @@ export async function create(args: string[]): Promise<void> {
1733
1578
  }
1734
1579
 
1735
1580
  p.log.step('Verifying setup...');
1736
- assertFreshSandboxReady({
1581
+ await assertFreshSandboxReady({
1737
1582
  config: effectiveConfig,
1738
1583
  engine,
1739
1584
  branch,
1585
+ workspace: target.workspace,
1740
1586
  container,
1741
1587
  copiedEntries: createdTmpfsSeedPlan
1742
1588
  });
@@ -12,23 +12,16 @@ import {
12
12
  runBoundedSandboxHookCommand,
13
13
  runSandboxHooks
14
14
  } from '../agent-client-reconciler.ts';
15
- import {
16
- formatCredentialWarnings,
17
- formatRemaining,
18
- hasClaudeProviderAuth,
19
- reconcileClaudeCredentials,
20
- redactCommandError,
21
- validateClaudeCredentialsEnvOverride
22
- } from '../credentials.ts';
23
15
  import { runInteractiveEngine } from '../shell.ts';
24
16
  import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.ts';
17
+ import { redactCommandError } from '../redaction.ts';
25
18
  import { runInteractiveWithClipboardBridge } from '../clipboard/bridge.ts';
26
19
  import { detectHostTimezone } from '../host-timezone.ts';
27
20
  import {
28
21
  fetchSandboxRows,
29
- resolveBranchArg,
30
22
  selectSandboxContainer,
31
23
  } from './list-running.ts';
24
+ import { resolveSandboxTarget } from '../workspace-identity.ts';
32
25
 
33
26
  const USAGE = `Usage: ai sandbox exec [--recreate] <branch | TASK-id | N> [cmd...]
34
27
 
@@ -98,37 +91,6 @@ export function runSandboxInteractive(params: {
98
91
  return runBridge({ engine, dockerArgs, container, home });
99
92
  }
100
93
 
101
- export function formatCredentialSyncStatus(
102
- result: ReturnType<typeof reconcileClaudeCredentials>,
103
- isTTY = process.stderr.isTTY,
104
- providerAuthAvailable = false
105
- ): string | null {
106
- if (providerAuthAvailable && (result.status === 'STALE_ACCESS' || result.status === 'MISSING')) {
107
- return null;
108
- }
109
- if (result.status === 'STALE_ACCESS') {
110
- return 'Warning: Claude Code credentials on host appear stale. Run "ai sandbox refresh" or "claude /login" to renew.\n';
111
- }
112
- if (result.status === 'MISSING') {
113
- return 'Warning: Claude Code credentials missing on host. Run "claude /login" to authenticate.\n';
114
- }
115
- if (result.status === 'KEYCHAIN_WRITE_FAILED') {
116
- return `Warning: A sandbox refresh produced newer credentials but host Keychain write failed (${formatCredentialWarnings(result.warnings)}). Run "ai sandbox refresh" again or "claude /status" on the host to retry.\n`;
117
- }
118
- if (result.status === 'KEYCHAIN_LOCKED' || result.status === 'KEYCHAIN_ERROR') {
119
- return 'Warning: Host keychain is unavailable; Claude credential sync skipped. Run "ai sandbox refresh" for details.\n';
120
- }
121
- if (result.status === 'OK' && result.authoritative !== 'host') {
122
- const message = `Synced Claude Code credentials from sandbox refresh back to host (expires in ${formatRemaining(result.expiresAt)})`;
123
- return isTTY ? `\x1b[2m${message}\x1b[0m\n` : `${message}\n`;
124
- }
125
- if (result.status === 'OK' && result.filesWritten.length > 0) {
126
- const message = `Synced Claude Code credentials from host Keychain (expires in ${formatRemaining(result.expiresAt)})`;
127
- return isTTY ? `\x1b[2m${message}\x1b[0m\n` : `${message}\n`;
128
- }
129
- return null;
130
- }
131
-
132
94
  export function parseEnterArgs(args: string[]): {
133
95
  target: string;
134
96
  command: string[];
@@ -157,10 +119,10 @@ export async function enter(args: string[]): Promise<number> {
157
119
  }
158
120
 
159
121
  const config = loadConfig();
160
- validateClaudeCredentialsEnvOverride();
161
122
  const engine = detectEngine(config);
162
123
  const parsed = parseEnterArgs(args);
163
- const branch = resolveBranchArg(parsed.target, { repoRoot: config.repoRoot });
124
+ const target = resolveSandboxTarget(parsed.target, config.repoRoot);
125
+ const branch = target.branch;
164
126
  assertValidBranchName(branch);
165
127
 
166
128
  const { running, nonRunning } = fetchSandboxRows(
@@ -182,11 +144,12 @@ export async function enter(args: string[]): Promise<number> {
182
144
  config,
183
145
  engine,
184
146
  branch,
147
+ workspace: target.workspace,
185
148
  row: found,
186
149
  allowRecreate: parsed.recreate,
187
- recreate: async (targetBranch) => {
150
+ recreate: async () => {
188
151
  const { create } = await import('./create.ts');
189
- await create([targetBranch, '--no-refresh']);
152
+ await create([target.requestedRef, '--no-refresh']);
190
153
  }
191
154
  });
192
155
  const container = ready.container;
@@ -195,7 +158,11 @@ export async function enter(args: string[]): Promise<number> {
195
158
  const enterHookResults = await runSandboxHooks({
196
159
  hooks: capabilityPlan.hooksByPhase['before-enter'],
197
160
  phase: 'before-enter',
198
- context: { config, plan: capabilityPlan },
161
+ context: {
162
+ config,
163
+ plan: capabilityPlan,
164
+ enter: { hostHome: config.home, hostEnv: { ...process.env } }
165
+ },
199
166
  runCommand: runBoundedSandboxHookCommand
200
167
  });
201
168
  for (const result of enterHookResults) {
@@ -207,20 +174,7 @@ export async function enter(args: string[]): Promise<number> {
207
174
  `Warning: ${result.message ?? `Sandbox hook '${result.hookId}' did not complete.`}\n`
208
175
  );
209
176
  }
210
- }
211
-
212
- if (capabilityPlan.selectedAgentClients.some((adapter) => adapter.id === 'claude-code')) {
213
- try {
214
- // Scan all projects so a refresh from a neighbouring sandbox can still flow back to the host.
215
- const providerAuthAvailable = hasClaudeProviderAuth(config.home);
216
- const result = reconcileClaudeCredentials(config.home);
217
- const message = formatCredentialSyncStatus(result, process.stderr.isTTY, providerAuthAvailable);
218
- if (message) {
219
- process.stderr.write(message);
220
- }
221
- } catch (error) {
222
- process.stderr.write(`Warning: Failed to sync Claude Code credentials: ${redactCommandError(error instanceof Error ? error.message : 'unknown error')}\n`);
223
- }
177
+ if (result.message && result.status !== 'warning') process.stderr.write(result.message);
224
178
  }
225
179
 
226
180
  const envFlags = [...terminalEnvFlags(), ...hostTimezoneEnvFlags()];