@fitlab-ai/agent-infra 0.8.4 → 0.9.0

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 (443) hide show
  1. package/bin/cli.ts +2 -2
  2. package/dist/bin/cli.js +2 -2
  3. package/dist/bin/internal-cli.js +98 -0
  4. package/dist/lib/decide.js +81 -90
  5. package/dist/lib/defaults.json +9 -0
  6. package/dist/lib/git/review-snapshot.js +79 -0
  7. package/dist/lib/git/workflow.js +101 -0
  8. package/dist/lib/init.js +5 -0
  9. package/dist/lib/internal/git-workflow.js +42 -0
  10. package/dist/lib/internal/platform-checks.js +119 -0
  11. package/dist/lib/internal/platform-comment.js +129 -0
  12. package/dist/lib/internal/platform-context.js +46 -0
  13. package/dist/lib/internal/platform-issue.js +142 -0
  14. package/dist/lib/internal/platform-pr.js +146 -0
  15. package/dist/lib/internal/release-workflow.js +217 -0
  16. package/dist/lib/internal/task-artifact.js +67 -0
  17. package/dist/lib/internal/task-context.js +43 -0
  18. package/dist/lib/internal/task-event.js +70 -0
  19. package/dist/lib/internal/task-ledger.js +114 -0
  20. package/dist/lib/internal/task-lifecycle.js +58 -0
  21. package/dist/lib/internal/task-short-id.js +83 -0
  22. package/dist/lib/internal/task-snapshot.js +47 -0
  23. package/dist/lib/internal/task-verify.js +50 -0
  24. package/dist/lib/internal/task-warning.js +74 -0
  25. package/dist/lib/platform/adapters.js +14 -0
  26. package/dist/lib/platform/context.js +146 -0
  27. package/dist/lib/platform/github-client.js +124 -0
  28. package/dist/lib/platform/issue-comments.js +409 -0
  29. package/dist/lib/platform/issue-metadata.js +183 -0
  30. package/dist/lib/platform/issues.js +617 -0
  31. package/dist/lib/platform/metadata-labels.js +28 -0
  32. package/dist/lib/platform/pr-checks.js +216 -0
  33. package/dist/lib/platform/pr-summary.js +88 -0
  34. package/dist/lib/platform/pull-request-metadata.js +38 -0
  35. package/dist/lib/platform/pull-requests.js +338 -0
  36. package/dist/lib/platform/releases.js +82 -0
  37. package/dist/lib/platform/types.js +22 -0
  38. package/dist/lib/platform/verification-required.js +78 -0
  39. package/dist/lib/platform/verification-sync.js +1014 -0
  40. package/dist/lib/platform/verification.js +6 -0
  41. package/dist/lib/release/channels.js +31 -0
  42. package/dist/lib/release/workflow.js +32 -0
  43. package/dist/lib/render.js +3 -2
  44. package/dist/lib/run/index.js +24 -7
  45. package/dist/lib/sandbox/capture.js +25 -3
  46. package/dist/lib/sandbox/commands/create.js +66 -26
  47. package/dist/lib/sandbox/commands/enter.js +34 -11
  48. package/dist/lib/sandbox/commands/list-running.js +10 -8
  49. package/dist/lib/sandbox/commands/show.js +2 -2
  50. package/dist/lib/sandbox/commands/start.js +53 -15
  51. package/dist/lib/sandbox/engine.js +33 -3
  52. package/dist/lib/sandbox/engines/colima.js +2 -2
  53. package/dist/lib/sandbox/index.js +6 -6
  54. package/dist/lib/sandbox/mounts.js +36 -0
  55. package/dist/lib/sandbox/recovery.js +562 -0
  56. package/dist/lib/sandbox/runtimes/base.dockerfile +4 -0
  57. package/dist/lib/sandbox/task-resolver.js +5 -1
  58. package/dist/lib/sandbox/tools.js +19 -0
  59. package/dist/lib/server/daemon.js +9 -0
  60. package/dist/lib/server/process-control.js +66 -73
  61. package/dist/lib/server/process-state.js +168 -0
  62. package/dist/lib/server/protocol.js +5 -5
  63. package/dist/lib/task/activity-log.js +61 -0
  64. package/dist/lib/task/artifact-lifecycle.js +427 -0
  65. package/dist/lib/task/command-options.js +30 -0
  66. package/dist/lib/task/commands/cat.js +36 -6
  67. package/dist/lib/task/commands/decisions.js +80 -46
  68. package/dist/lib/task/commands/files.js +21 -7
  69. package/dist/lib/task/commands/grep.js +55 -7
  70. package/dist/lib/task/commands/issue-body.js +23 -14
  71. package/dist/lib/task/commands/log.js +27 -70
  72. package/dist/lib/task/commands/show.js +20 -7
  73. package/dist/lib/task/commands/status.js +18 -8
  74. package/dist/lib/task/decision-intents.js +154 -0
  75. package/dist/lib/task/decision-items.js +53 -0
  76. package/dist/lib/task/events.js +318 -0
  77. package/dist/lib/task/frontmatter.js +159 -1
  78. package/dist/lib/task/implementation-inputs.js +117 -0
  79. package/dist/lib/task/index.js +12 -9
  80. package/dist/lib/task/issue-form.js +19 -6
  81. package/dist/lib/task/ledger-intents.js +185 -0
  82. package/dist/lib/task/ledger.js +65 -35
  83. package/dist/lib/task/lifecycle.js +484 -0
  84. package/dist/lib/task/resolve-ref.js +152 -32
  85. package/dist/lib/task/review-artifacts.js +101 -0
  86. package/dist/lib/task/review-fingerprint.js +46 -0
  87. package/dist/lib/task/sections.js +358 -4
  88. package/dist/lib/task/short-id.js +292 -7
  89. package/dist/lib/task/snapshot.js +95 -0
  90. package/dist/lib/task/verification-checks.js +8 -0
  91. package/dist/lib/task/verification-config.js +14 -0
  92. package/dist/lib/task/verification-engine.js +906 -0
  93. package/dist/lib/task/verification-review.js +3 -0
  94. package/dist/lib/task/verification-types.js +2 -0
  95. package/dist/lib/task/verification.js +113 -0
  96. package/dist/lib/task/workflow-warning-intents.js +131 -0
  97. package/dist/lib/task/workflow-warnings.js +10 -78
  98. package/dist/lib/task/write.js +241 -0
  99. package/dist/lib/update.js +2 -0
  100. package/lib/decide.ts +57 -97
  101. package/lib/defaults.json +9 -0
  102. package/lib/git/review-snapshot.ts +73 -0
  103. package/lib/git/workflow.ts +101 -0
  104. package/lib/init.ts +9 -0
  105. package/lib/internal/git-workflow.ts +37 -0
  106. package/lib/internal/platform-checks.ts +101 -0
  107. package/lib/internal/platform-comment.ts +99 -0
  108. package/lib/internal/platform-context.ts +33 -0
  109. package/lib/internal/platform-issue.ts +116 -0
  110. package/lib/internal/platform-pr.ts +119 -0
  111. package/lib/internal/release-workflow.ts +186 -0
  112. package/lib/internal/task-artifact.ts +47 -0
  113. package/lib/internal/task-context.ts +35 -0
  114. package/lib/internal/task-event.ts +51 -0
  115. package/lib/internal/task-ledger.ts +86 -0
  116. package/lib/internal/task-lifecycle.ts +41 -0
  117. package/lib/internal/task-short-id.ts +55 -0
  118. package/lib/internal/task-snapshot.ts +32 -0
  119. package/lib/internal/task-verify.ts +33 -0
  120. package/lib/internal/task-warning.ts +50 -0
  121. package/lib/platform/adapters.ts +30 -0
  122. package/lib/platform/context.ts +164 -0
  123. package/lib/platform/github-client.ts +146 -0
  124. package/lib/platform/issue-comments.ts +479 -0
  125. package/lib/platform/issue-metadata.ts +250 -0
  126. package/lib/platform/issues.ts +691 -0
  127. package/lib/platform/metadata-labels.ts +33 -0
  128. package/lib/platform/pr-checks.ts +246 -0
  129. package/lib/platform/pr-summary.ts +90 -0
  130. package/lib/platform/pull-request-metadata.ts +69 -0
  131. package/lib/platform/pull-requests.ts +380 -0
  132. package/lib/platform/releases.ts +71 -0
  133. package/lib/platform/types.ts +74 -0
  134. package/lib/platform/verification-required.ts +83 -0
  135. package/{templates/.agents/scripts/platform-adapters/platform-sync.github.js → lib/platform/verification-sync.ts} +221 -363
  136. package/lib/platform/verification.ts +11 -0
  137. package/lib/release/channels.ts +30 -0
  138. package/lib/release/workflow.ts +31 -0
  139. package/lib/render.ts +3 -2
  140. package/lib/run/index.ts +25 -7
  141. package/lib/sandbox/capture.ts +29 -3
  142. package/lib/sandbox/commands/create.ts +75 -28
  143. package/lib/sandbox/commands/enter.ts +38 -11
  144. package/lib/sandbox/commands/list-running.ts +10 -8
  145. package/lib/sandbox/commands/show.ts +2 -2
  146. package/lib/sandbox/commands/start.ts +50 -16
  147. package/lib/sandbox/engine.ts +37 -3
  148. package/lib/sandbox/engines/colima.ts +2 -2
  149. package/lib/sandbox/index.ts +6 -6
  150. package/lib/sandbox/mounts.ts +55 -0
  151. package/lib/sandbox/recovery.ts +828 -0
  152. package/lib/sandbox/runtimes/base.dockerfile +4 -0
  153. package/lib/sandbox/task-resolver.ts +5 -1
  154. package/lib/sandbox/tools.ts +31 -0
  155. package/lib/server/daemon.ts +9 -0
  156. package/lib/server/process-control.ts +70 -71
  157. package/lib/server/process-state.ts +183 -0
  158. package/lib/server/protocol.ts +5 -5
  159. package/lib/task/activity-log.ts +62 -0
  160. package/lib/task/artifact-lifecycle.ts +484 -0
  161. package/lib/task/command-options.ts +32 -0
  162. package/lib/task/commands/cat.ts +22 -9
  163. package/lib/task/commands/decisions.ts +49 -47
  164. package/lib/task/commands/files.ts +14 -6
  165. package/lib/task/commands/grep.ts +34 -7
  166. package/lib/task/commands/issue-body.ts +16 -16
  167. package/lib/task/commands/log.ts +17 -70
  168. package/lib/task/commands/show.ts +14 -6
  169. package/lib/task/commands/status.ts +17 -7
  170. package/lib/task/decision-intents.ts +163 -0
  171. package/lib/task/decision-items.ts +64 -0
  172. package/lib/task/events.ts +320 -0
  173. package/lib/task/frontmatter.ts +202 -2
  174. package/lib/task/implementation-inputs.ts +137 -0
  175. package/lib/task/index.ts +12 -9
  176. package/lib/task/issue-form.ts +20 -6
  177. package/lib/task/ledger-intents.ts +201 -0
  178. package/lib/task/ledger.ts +77 -31
  179. package/lib/task/lifecycle.ts +458 -0
  180. package/lib/task/resolve-ref.ts +197 -36
  181. package/{templates/.agents/scripts/lib/review-artifacts.js → lib/task/review-artifacts.ts} +11 -12
  182. package/{templates/.agents/scripts/lib/post-review-commit.js → lib/task/review-fingerprint.ts} +20 -10
  183. package/lib/task/sections.ts +486 -4
  184. package/lib/task/short-id.ts +403 -9
  185. package/lib/task/snapshot.ts +143 -0
  186. package/lib/task/verification-checks.ts +9 -0
  187. package/lib/task/verification-config.ts +20 -0
  188. package/{templates/.agents/scripts/validate-artifact.js → lib/task/verification-engine.ts} +257 -451
  189. package/lib/task/verification-review.ts +9 -0
  190. package/lib/task/verification-types.ts +29 -0
  191. package/lib/task/verification.ts +156 -0
  192. package/lib/task/workflow-warning-intents.ts +142 -0
  193. package/lib/task/workflow-warnings.ts +10 -78
  194. package/lib/task/write.ts +468 -0
  195. package/lib/update.ts +6 -0
  196. package/package.json +5 -2
  197. package/templates/.agents/README.en.md +6 -3
  198. package/templates/.agents/README.zh-CN.md +6 -3
  199. package/templates/.agents/rules/README.en.md +6 -5
  200. package/templates/.agents/rules/README.zh-CN.md +6 -5
  201. package/templates/.agents/rules/create-issue.en.md +1 -1
  202. package/templates/.agents/rules/create-issue.github.en.md +5 -184
  203. package/templates/.agents/rules/create-issue.github.zh-CN.md +12 -178
  204. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  205. package/templates/.agents/rules/human-decision-context.en.md +36 -0
  206. package/templates/.agents/rules/human-decision-context.zh-CN.md +36 -0
  207. package/templates/.agents/rules/issue-fields.github.en.md +4 -148
  208. package/templates/.agents/rules/issue-fields.github.zh-CN.md +9 -144
  209. package/templates/.agents/rules/issue-pr-commands.en.md +43 -3
  210. package/templates/.agents/rules/issue-pr-commands.github.en.md +15 -220
  211. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +23 -213
  212. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +45 -3
  213. package/templates/.agents/rules/issue-sync.en.md +1 -1
  214. package/templates/.agents/rules/issue-sync.github.en.md +14 -301
  215. package/templates/.agents/rules/issue-sync.github.zh-CN.md +36 -287
  216. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  217. package/templates/.agents/rules/milestone-inference.en.md +1 -1
  218. package/templates/.agents/rules/milestone-inference.github.en.md +7 -119
  219. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +8 -108
  220. package/templates/.agents/rules/milestone-inference.zh-CN.md +1 -1
  221. package/templates/.agents/rules/next-step-output.en.md +12 -11
  222. package/templates/.agents/rules/next-step-output.zh-CN.md +12 -11
  223. package/templates/.agents/rules/no-mid-flow-questions.en.md +3 -3
  224. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +3 -3
  225. package/templates/.agents/rules/pr-checks-commands.en.md +16 -3
  226. package/templates/.agents/rules/pr-checks-commands.github.en.md +10 -54
  227. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +19 -47
  228. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +30 -3
  229. package/templates/.agents/rules/pr-sync.en.md +30 -3
  230. package/templates/.agents/rules/pr-sync.github.en.md +12 -115
  231. package/templates/.agents/rules/pr-sync.github.zh-CN.md +26 -104
  232. package/templates/.agents/rules/pr-sync.zh-CN.md +43 -3
  233. package/templates/.agents/rules/review-handshake.en.md +13 -9
  234. package/templates/.agents/rules/review-handshake.zh-CN.md +13 -9
  235. package/templates/.agents/rules/task-management.en.md +11 -2
  236. package/templates/.agents/rules/task-management.zh-CN.md +11 -3
  237. package/templates/.agents/rules/task-short-id.en.md +25 -29
  238. package/templates/.agents/rules/task-short-id.zh-CN.md +20 -23
  239. package/templates/.agents/scripts/lib/agent-infra-package.js +141 -0
  240. package/templates/.agents/scripts/task-short-id.js +71 -533
  241. package/templates/.agents/skills/analyze-task/SKILL.en.md +22 -55
  242. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +26 -59
  243. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -3
  244. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -3
  245. package/templates/.agents/skills/block-task/SKILL.en.md +21 -47
  246. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +23 -47
  247. package/templates/.agents/skills/block-task/config/verify.json +1 -2
  248. package/templates/.agents/skills/cancel-task/SKILL.en.md +16 -45
  249. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +16 -45
  250. package/templates/.agents/skills/cancel-task/config/verify.json +1 -2
  251. package/templates/.agents/skills/check-task/SKILL.en.md +6 -3
  252. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +6 -3
  253. package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -24
  254. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -24
  255. package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -24
  256. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -24
  257. package/templates/.agents/skills/code-task/SKILL.en.md +22 -35
  258. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +28 -44
  259. package/templates/.agents/skills/code-task/config/verify.en.json +3 -3
  260. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +3 -3
  261. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +13 -9
  262. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +13 -9
  263. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +1 -1
  264. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +1 -1
  265. package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -1
  266. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -1
  267. package/templates/.agents/skills/commit/SKILL.en.md +18 -14
  268. package/templates/.agents/skills/commit/SKILL.zh-CN.md +18 -14
  269. package/templates/.agents/skills/commit/config/verify.json +1 -2
  270. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -11
  271. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -11
  272. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +4 -8
  273. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +4 -8
  274. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +6 -18
  275. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +5 -17
  276. package/templates/.agents/skills/commit/reference/task-status-update.en.md +42 -2
  277. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +42 -2
  278. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +13 -36
  279. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +13 -36
  280. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -3
  281. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -3
  282. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +10 -54
  283. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +10 -79
  284. package/templates/.agents/skills/complete-task/SKILL.en.md +59 -63
  285. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +59 -63
  286. package/templates/.agents/skills/complete-task/config/verify.en.json +11 -6
  287. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +11 -6
  288. package/templates/.agents/skills/create-pr/SKILL.en.md +19 -44
  289. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +19 -43
  290. package/templates/.agents/skills/create-pr/config/verify.json +1 -2
  291. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +8 -12
  292. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +7 -11
  293. package/templates/.agents/skills/create-task/SKILL.en.md +12 -13
  294. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +12 -13
  295. package/templates/.agents/skills/create-task/config/verify.json +1 -2
  296. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
  297. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +2 -2
  298. package/templates/.agents/skills/import-codescan/config/verify.json +1 -2
  299. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
  300. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +2 -2
  301. package/templates/.agents/skills/import-dependabot/config/verify.json +1 -2
  302. package/templates/.agents/skills/import-issue/SKILL.en.md +9 -13
  303. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +10 -14
  304. package/templates/.agents/skills/import-issue/config/verify.json +1 -2
  305. package/templates/.agents/skills/init-milestones/SKILL.en.md +2 -2
  306. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +2 -2
  307. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +5 -4
  308. package/templates/.agents/skills/plan-task/SKILL.en.md +17 -42
  309. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +22 -49
  310. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -3
  311. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -3
  312. package/templates/.agents/skills/post-release/SKILL.en.md +11 -81
  313. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +8 -78
  314. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  315. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  316. package/templates/.agents/skills/release/SKILL.en.md +16 -163
  317. package/templates/.agents/skills/release/SKILL.zh-CN.md +15 -162
  318. package/templates/.agents/skills/restore-task/SKILL.en.md +11 -29
  319. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -29
  320. package/templates/.agents/skills/restore-task/config/verify.json +1 -2
  321. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -25
  322. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +13 -33
  323. package/templates/.agents/skills/review-analysis/config/verify.en.json +3 -4
  324. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +3 -4
  325. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +9 -12
  326. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +9 -12
  327. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +9 -2
  328. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +9 -2
  329. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +7 -0
  330. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +7 -0
  331. package/templates/.agents/skills/review-code/SKILL.en.md +22 -36
  332. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +23 -41
  333. package/templates/.agents/skills/review-code/config/verify.en.json +5 -5
  334. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +5 -5
  335. package/templates/.agents/skills/review-code/reference/output-templates.en.md +56 -18
  336. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +56 -18
  337. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -4
  338. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -4
  339. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +7 -0
  340. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +7 -0
  341. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -25
  342. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +13 -33
  343. package/templates/.agents/skills/review-plan/config/verify.en.json +3 -4
  344. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +3 -4
  345. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +9 -12
  346. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +9 -12
  347. package/templates/.agents/skills/review-plan/reference/report-template.en.md +9 -2
  348. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +9 -2
  349. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +7 -0
  350. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +7 -0
  351. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +6 -3
  352. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +6 -3
  353. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +177 -151
  354. package/templates/.agents/skills/watch-pr/SKILL.en.md +24 -14
  355. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +25 -15
  356. package/templates/.agents/skills/watch-pr/config/verify.json +1 -2
  357. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
  358. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
  359. package/templates/.agents/templates/task.en.md +6 -1
  360. package/templates/.agents/templates/task.zh-CN.md +6 -1
  361. package/templates/.claude/commands/analyze-task.en.md +1 -1
  362. package/templates/.claude/commands/analyze-task.zh-CN.md +1 -1
  363. package/templates/.claude/commands/block-task.en.md +1 -1
  364. package/templates/.claude/commands/block-task.zh-CN.md +1 -1
  365. package/templates/.claude/commands/cancel-task.en.md +1 -1
  366. package/templates/.claude/commands/cancel-task.zh-CN.md +1 -1
  367. package/templates/.claude/commands/check-task.en.md +1 -1
  368. package/templates/.claude/commands/check-task.zh-CN.md +1 -1
  369. package/templates/.claude/commands/code-task.en.md +1 -1
  370. package/templates/.claude/commands/code-task.zh-CN.md +1 -1
  371. package/templates/.claude/commands/commit.en.md +1 -0
  372. package/templates/.claude/commands/commit.zh-CN.md +1 -0
  373. package/templates/.claude/commands/complete-manual-validation.en.md +1 -1
  374. package/templates/.claude/commands/complete-manual-validation.zh-CN.md +1 -1
  375. package/templates/.claude/commands/complete-task.en.md +1 -1
  376. package/templates/.claude/commands/complete-task.zh-CN.md +1 -1
  377. package/templates/.claude/commands/create-pr.en.md +1 -1
  378. package/templates/.claude/commands/create-pr.zh-CN.md +1 -1
  379. package/templates/.claude/commands/plan-task.en.md +1 -1
  380. package/templates/.claude/commands/plan-task.zh-CN.md +1 -1
  381. package/templates/.claude/commands/review-analysis.en.md +1 -1
  382. package/templates/.claude/commands/review-analysis.zh-CN.md +1 -1
  383. package/templates/.claude/commands/review-code.en.md +1 -1
  384. package/templates/.claude/commands/review-code.zh-CN.md +1 -1
  385. package/templates/.claude/commands/review-plan.en.md +1 -1
  386. package/templates/.claude/commands/review-plan.zh-CN.md +1 -1
  387. package/templates/.claude/commands/watch-pr.en.md +1 -1
  388. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  389. package/templates/.gemini/commands/_project_/analyze-task.en.toml +2 -0
  390. package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +2 -0
  391. package/templates/.gemini/commands/_project_/block-task.en.toml +2 -0
  392. package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +2 -0
  393. package/templates/.gemini/commands/_project_/cancel-task.en.toml +2 -0
  394. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +2 -0
  395. package/templates/.gemini/commands/_project_/check-task.en.toml +2 -0
  396. package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +2 -0
  397. package/templates/.gemini/commands/_project_/code-task.en.toml +2 -0
  398. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +2 -0
  399. package/templates/.gemini/commands/_project_/commit.en.toml +4 -0
  400. package/templates/.gemini/commands/_project_/commit.zh-CN.toml +4 -0
  401. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +2 -0
  402. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +2 -0
  403. package/templates/.gemini/commands/_project_/complete-task.en.toml +2 -0
  404. package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +2 -0
  405. package/templates/.gemini/commands/_project_/create-pr.en.toml +2 -0
  406. package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +2 -0
  407. package/templates/.gemini/commands/_project_/plan-task.en.toml +2 -0
  408. package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +2 -0
  409. package/templates/.gemini/commands/_project_/review-analysis.en.toml +2 -0
  410. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +2 -0
  411. package/templates/.gemini/commands/_project_/review-code.en.toml +2 -0
  412. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +2 -0
  413. package/templates/.gemini/commands/_project_/review-plan.en.toml +2 -0
  414. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +2 -0
  415. package/templates/.gemini/commands/_project_/watch-pr.en.toml +2 -0
  416. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +2 -0
  417. package/templates/.git-hooks/check-large-files.cjs +92 -0
  418. package/templates/.git-hooks/check-version-format.sh +5 -1
  419. package/templates/.git-hooks/pre-commit +1 -0
  420. package/templates/.opencode/commands/analyze-task.en.md +1 -1
  421. package/templates/.opencode/commands/analyze-task.zh-CN.md +1 -1
  422. package/templates/.opencode/commands/check-task.en.md +1 -1
  423. package/templates/.opencode/commands/check-task.zh-CN.md +1 -1
  424. package/templates/.opencode/commands/code-task.en.md +1 -1
  425. package/templates/.opencode/commands/code-task.zh-CN.md +1 -1
  426. package/templates/.opencode/commands/commit.en.md +2 -0
  427. package/templates/.opencode/commands/commit.zh-CN.md +2 -0
  428. package/templates/.opencode/commands/complete-task.en.md +1 -1
  429. package/templates/.opencode/commands/complete-task.zh-CN.md +1 -1
  430. package/templates/.opencode/commands/plan-task.en.md +1 -1
  431. package/templates/.opencode/commands/plan-task.zh-CN.md +1 -1
  432. package/templates/.opencode/commands/review-analysis.en.md +1 -1
  433. package/templates/.opencode/commands/review-analysis.zh-CN.md +1 -1
  434. package/templates/.opencode/commands/review-code.en.md +1 -1
  435. package/templates/.opencode/commands/review-code.zh-CN.md +1 -1
  436. package/templates/.opencode/commands/review-plan.en.md +1 -1
  437. package/templates/.opencode/commands/review-plan.zh-CN.md +1 -1
  438. package/templates/.agents/scripts/platform-adapters/platform-sync.js +0 -13
  439. package/templates/.agents/scripts/review-diff-fingerprint.js +0 -99
  440. package/templates/.agents/scripts/workflow-warnings.js +0 -290
  441. package/templates/.agents/skills/code-task/scripts/detect-mode.js +0 -257
  442. package/templates/.agents/skills/release/scripts/manage-milestones.github.sh +0 -80
  443. package/templates/.agents/skills/release/scripts/manage-milestones.sh +0 -6
@@ -0,0 +1,828 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { containerNameCandidates, sandboxBranchLabel, sandboxLabel } from './constants.ts';
4
+ import type { SandboxConfig } from './config.ts';
5
+ import { toEnginePath } from './engines/wsl2-paths.ts';
6
+ import { sandboxCoreBindMounts } from './mounts.ts';
7
+ import { runEngine, runOkEngine, runVerboseEngine } from './shell.ts';
8
+ import {
9
+ declaredTmpfsSeedEntries,
10
+ resolveTools,
11
+ toolConfigDirCandidates,
12
+ type TmpfsSeedEntry
13
+ } from './tools.ts';
14
+ import {
15
+ fetchSandboxRows,
16
+ selectSandboxContainer,
17
+ startSandboxContainer,
18
+ type SandboxRow
19
+ } from './commands/list-running.ts';
20
+
21
+ export type SandboxRecoveryFinding = {
22
+ repairKind: 'permissions' | 'missing-seed' | 'builtin-link' | 'hard-failure';
23
+ message: string;
24
+ path?: string;
25
+ seed?: TmpfsSeedEntry;
26
+ };
27
+
28
+ export type SandboxRecoverySnapshot = {
29
+ identityOk: boolean;
30
+ mounts: Array<{
31
+ path: string;
32
+ expectedType: string;
33
+ actualType: string | null;
34
+ expectedSource: string | null;
35
+ actualSource: string | null;
36
+ sourceMatches: boolean;
37
+ expectedRW: boolean;
38
+ actualRW: boolean | null;
39
+ sourceAccessible: boolean;
40
+ }>;
41
+ tmpfs: Array<{ path: string; permissionsOk: boolean; writable: boolean }>;
42
+ seeds: Array<{
43
+ toolId: string;
44
+ containerMount?: string;
45
+ stagingPath: string;
46
+ targetPath: string;
47
+ mounted: boolean;
48
+ targetState: 'ok' | 'missing' | 'wrong-type' | 'inaccessible';
49
+ }>;
50
+ aliasesReadable: boolean;
51
+ codex?: {
52
+ commandAvailable: boolean;
53
+ stateWritable: boolean;
54
+ promptsSourceExists: boolean;
55
+ promptsValid: boolean;
56
+ };
57
+ };
58
+
59
+ export type SandboxReadyResult = {
60
+ container: string;
61
+ path: 'healthy' | 'recovered' | 'recreated';
62
+ warnings: string[];
63
+ };
64
+
65
+ type DockerMount = {
66
+ Type?: unknown;
67
+ Source?: unknown;
68
+ Destination?: unknown;
69
+ RW?: unknown;
70
+ };
71
+
72
+ type DockerInspection = {
73
+ Id?: unknown;
74
+ Config?: { Labels?: Record<string, string> };
75
+ HostConfig?: { Tmpfs?: unknown };
76
+ Mounts?: DockerMount[];
77
+ };
78
+
79
+ type RecoveryCommandDeps = {
80
+ run?: typeof runEngine;
81
+ runOk?: typeof runOkEngine;
82
+ runVerbose?: typeof runVerboseEngine;
83
+ start?: typeof startSandboxContainer;
84
+ fetchRows?: typeof fetchSandboxRows;
85
+ };
86
+
87
+ type EnsureSandboxReadyParams = {
88
+ config: SandboxConfig;
89
+ engine: string;
90
+ branch: string;
91
+ row: SandboxRow;
92
+ allowRecreate?: boolean;
93
+ recreate?: (branch: string) => Promise<void>;
94
+ writeWarning?: (message: string) => void;
95
+ deps?: RecoveryCommandDeps;
96
+ };
97
+
98
+ type ExpectedMount = {
99
+ path: string;
100
+ expectedType: 'bind' | 'tmpfs';
101
+ hostPaths: string[];
102
+ expectedRW: boolean;
103
+ };
104
+
105
+ function findingKey(finding: SandboxRecoveryFinding): string {
106
+ return `${finding.repairKind}:${finding.path ?? finding.seed?.targetPath ?? finding.message}`;
107
+ }
108
+
109
+ export function classifySandboxRecovery(snapshot: SandboxRecoverySnapshot): SandboxRecoveryFinding[] {
110
+ const findings: SandboxRecoveryFinding[] = [];
111
+ if (!snapshot.identityOk) {
112
+ findings.push({
113
+ repairKind: 'hard-failure',
114
+ message: 'Container identity does not match the requested sandbox branch.'
115
+ });
116
+ }
117
+
118
+ for (const mount of snapshot.mounts) {
119
+ if (
120
+ mount.actualType !== mount.expectedType
121
+ || !mount.sourceMatches
122
+ || mount.actualRW !== mount.expectedRW
123
+ || !mount.sourceAccessible
124
+ ) {
125
+ findings.push({
126
+ repairKind: 'hard-failure',
127
+ message: `Expected ${mount.expectedType} mount at ${mount.path}`
128
+ + `${mount.expectedSource === null ? '' : ` from ${mount.expectedSource}`}`
129
+ + ` with RW=${mount.expectedRW}, found ${mount.actualType ?? 'none'}`
130
+ + `${mount.actualSource === null ? '' : ` from ${mount.actualSource}`}`
131
+ + ` with RW=${mount.actualRW ?? 'unknown'}.`,
132
+ path: mount.path
133
+ });
134
+ }
135
+ }
136
+
137
+ for (const mount of snapshot.tmpfs) {
138
+ if (!mount.permissionsOk || !mount.writable) {
139
+ findings.push({
140
+ repairKind: 'permissions',
141
+ message: `Tmpfs ${mount.path} is not owned and writable by devuser with mode 0700.`,
142
+ path: mount.path
143
+ });
144
+ }
145
+ }
146
+
147
+ for (const seed of snapshot.seeds) {
148
+ if (!seed.mounted) {
149
+ continue;
150
+ }
151
+ if (seed.targetState === 'missing' || seed.targetState === 'wrong-type') {
152
+ findings.push({
153
+ repairKind: 'missing-seed',
154
+ message: `Runtime seed target ${seed.targetPath} must be restored from staging.`,
155
+ path: seed.targetPath,
156
+ seed: {
157
+ toolId: seed.toolId,
158
+ containerMount: seed.containerMount ?? seed.targetPath.slice(0, seed.targetPath.lastIndexOf('/')),
159
+ stagingPath: seed.stagingPath,
160
+ targetPath: seed.targetPath
161
+ }
162
+ });
163
+ } else if (seed.targetState === 'inaccessible') {
164
+ findings.push({
165
+ repairKind: 'hard-failure',
166
+ message: `Runtime seed target ${seed.targetPath} exists but is not accessible to devuser.`,
167
+ path: seed.targetPath
168
+ });
169
+ }
170
+ }
171
+
172
+ if (!snapshot.aliasesReadable) {
173
+ findings.push({
174
+ repairKind: 'hard-failure',
175
+ message: 'Sandbox shell aliases are not readable by devuser.',
176
+ path: '/home/devuser/.bash_aliases'
177
+ });
178
+ }
179
+
180
+ if (snapshot.codex) {
181
+ if (!snapshot.codex.commandAvailable) {
182
+ findings.push({
183
+ repairKind: 'hard-failure',
184
+ message: 'Codex is not available on PATH inside the sandbox.'
185
+ });
186
+ }
187
+ if (!snapshot.codex.stateWritable) {
188
+ findings.push({
189
+ repairKind: 'permissions',
190
+ message: 'Codex state directory is not writable by devuser.',
191
+ path: '/home/devuser/.codex'
192
+ });
193
+ }
194
+ if (snapshot.codex.promptsSourceExists && !snapshot.codex.promptsValid) {
195
+ findings.push({
196
+ repairKind: 'builtin-link',
197
+ message: 'Codex prompts link does not point to the workspace commands directory.',
198
+ path: '/home/devuser/.codex/prompts'
199
+ });
200
+ }
201
+ }
202
+
203
+ const seen = new Set<string>();
204
+ return findings.filter((finding) => {
205
+ const key = findingKey(finding);
206
+ if (seen.has(key)) return false;
207
+ seen.add(key);
208
+ return true;
209
+ });
210
+ }
211
+
212
+ function inspectContainer(
213
+ engine: string,
214
+ container: string,
215
+ runFn: typeof runEngine
216
+ ): DockerInspection {
217
+ const raw = runFn(engine, 'docker', ['inspect', container]);
218
+ const parsed = JSON.parse(raw) as unknown;
219
+ if (!Array.isArray(parsed) || !parsed[0] || typeof parsed[0] !== 'object') {
220
+ throw new Error(`Unable to inspect sandbox container '${container}'.`);
221
+ }
222
+ return parsed[0] as DockerInspection;
223
+ }
224
+
225
+ function probe(
226
+ engine: string,
227
+ container: string,
228
+ script: string,
229
+ args: string[],
230
+ runOkFn: typeof runOkEngine,
231
+ user = 'devuser'
232
+ ): boolean {
233
+ return runOkFn(engine, 'docker', [
234
+ 'exec', '--user', user, container, 'sh', '-c', script, 'agent-infra-recovery', ...args
235
+ ]);
236
+ }
237
+
238
+ function normalizeMountSource(engine: string, source: string): string {
239
+ const normalized = toEnginePath(engine, source).replace(/\\/g, '/').replace(/\/+$/, '');
240
+ return /^[A-Za-z]:\//.test(normalized)
241
+ ? `${normalized[0]!.toLowerCase()}${normalized.slice(1)}`
242
+ : normalized;
243
+ }
244
+
245
+ function sourceCandidates(engine: string, hostPath: string): string[] {
246
+ const candidates = [hostPath];
247
+ try {
248
+ candidates.push(fs.realpathSync(hostPath));
249
+ } catch {
250
+ // The access check below reports a missing or inaccessible host source.
251
+ }
252
+ return [...new Set(candidates.map((candidate) => normalizeMountSource(engine, candidate)))];
253
+ }
254
+
255
+ function sameHostSource(left: string, right: string): boolean {
256
+ try {
257
+ const leftStat = fs.statSync(left, { bigint: true });
258
+ const rightStat = fs.statSync(right, { bigint: true });
259
+ return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
260
+ } catch {
261
+ return false;
262
+ }
263
+ }
264
+
265
+ function hostSourceAccessible(hostPath: string, writable: boolean): boolean {
266
+ try {
267
+ fs.accessSync(
268
+ hostPath,
269
+ fs.constants.R_OK | (writable ? fs.constants.W_OK : 0)
270
+ );
271
+ return true;
272
+ } catch {
273
+ return false;
274
+ }
275
+ }
276
+
277
+ function expectedMounts(params: {
278
+ config: SandboxConfig;
279
+ branch: string;
280
+ engine: string;
281
+ actualMounts: Map<string, DockerMount>;
282
+ }): ExpectedMount[] {
283
+ const { config, branch, actualMounts } = params;
284
+ const tools = resolveTools(config);
285
+ const core = sandboxCoreBindMounts(config, branch).map((mount) => ({
286
+ path: mount.containerPath,
287
+ expectedType: 'bind' as const,
288
+ hostPaths: mount.hostPaths,
289
+ expectedRW: !mount.readOnly
290
+ }));
291
+ const live = tools.flatMap((tool) =>
292
+ (tool.hostLiveMounts ?? []).flatMap(({ hostPath, containerSubpath }) => {
293
+ const destination = path.posix.join(tool.containerMount, containerSubpath);
294
+ if (!fs.existsSync(hostPath) && !actualMounts.has(destination)) return [];
295
+ return [{
296
+ path: destination,
297
+ expectedType: 'bind' as const,
298
+ hostPaths: [hostPath],
299
+ expectedRW: true
300
+ }];
301
+ })
302
+ );
303
+ const persistentTools = tools
304
+ .filter((tool) => !tool.tmpfs && actualMounts.has(tool.containerMount))
305
+ .map((tool) => ({
306
+ path: tool.containerMount,
307
+ expectedType: 'bind' as const,
308
+ hostPaths: toolConfigDirCandidates(tool, config.project, branch),
309
+ expectedRW: true
310
+ }));
311
+ const staging = tools.flatMap((tool) =>
312
+ (tool.tmpfs?.seed ?? []).flatMap((seedEntry, index) => {
313
+ const stagingPath = `/run/agent-infra/tmpfs-seeds/${tool.id}/${index}`;
314
+ if (!actualMounts.has(stagingPath)) return [];
315
+ return [{
316
+ path: stagingPath,
317
+ expectedType: 'bind' as const,
318
+ hostPaths: toolConfigDirCandidates(tool, config.project, branch)
319
+ .map((candidate) => path.join(candidate, seedEntry)),
320
+ expectedRW: false
321
+ }];
322
+ })
323
+ );
324
+ return [
325
+ ...core,
326
+ ...persistentTools,
327
+ ...live,
328
+ ...staging,
329
+ ...tools
330
+ .filter((tool) => tool.tmpfs)
331
+ .map((tool) => ({
332
+ path: tool.containerMount,
333
+ expectedType: 'tmpfs' as const,
334
+ hostPaths: [],
335
+ expectedRW: true
336
+ }))
337
+ ];
338
+ }
339
+
340
+ function targetState(
341
+ engine: string,
342
+ container: string,
343
+ seed: TmpfsSeedEntry,
344
+ runOkFn: typeof runOkEngine
345
+ ): SandboxRecoverySnapshot['seeds'][number]['targetState'] {
346
+ const exists = probe(
347
+ engine,
348
+ container,
349
+ 'test -e "$1" || test -L "$1"',
350
+ [seed.targetPath],
351
+ runOkFn
352
+ );
353
+ if (!exists) return 'missing';
354
+
355
+ const compatible = probe(
356
+ engine,
357
+ container,
358
+ 'if test -d "$1"; then test -d "$2"; else test -f "$2"; fi',
359
+ [seed.stagingPath, seed.targetPath],
360
+ runOkFn
361
+ );
362
+ if (!compatible) return 'wrong-type';
363
+
364
+ const readable = runOkFn(engine, 'docker', [
365
+ 'exec', '--user', 'devuser', container, 'test', '-r', seed.targetPath
366
+ ]);
367
+ const writable = runOkFn(engine, 'docker', [
368
+ 'exec', '--user', 'devuser', container, 'test', '-w', seed.targetPath
369
+ ]);
370
+ return readable && writable ? 'ok' : 'inaccessible';
371
+ }
372
+
373
+ export function collectSandboxRecoverySnapshot(params: {
374
+ config: SandboxConfig;
375
+ engine: string;
376
+ branch: string;
377
+ container: string;
378
+ deps?: RecoveryCommandDeps;
379
+ }): SandboxRecoverySnapshot {
380
+ const runFn = params.deps?.run ?? runEngine;
381
+ const runOkFn = params.deps?.runOk ?? runOkEngine;
382
+ const inspection = inspectContainer(params.engine, params.container, runFn);
383
+ const mounts = Array.isArray(inspection.Mounts) ? inspection.Mounts : [];
384
+ const mountsByDestination = new Map(
385
+ mounts.flatMap((mount) =>
386
+ typeof mount.Destination === 'string'
387
+ ? [[mount.Destination, mount] as const]
388
+ : []
389
+ )
390
+ );
391
+ const declaredTmpfs = inspection.HostConfig?.Tmpfs;
392
+ if (declaredTmpfs && typeof declaredTmpfs === 'object' && !Array.isArray(declaredTmpfs)) {
393
+ for (const [destination, rawOptions] of Object.entries(declaredTmpfs)) {
394
+ if (mountsByDestination.has(destination) || typeof rawOptions !== 'string') continue;
395
+ const options = rawOptions.split(',').map((option) => option.trim());
396
+ mountsByDestination.set(destination, {
397
+ Type: 'tmpfs',
398
+ Source: '',
399
+ Destination: destination,
400
+ RW: !options.includes('ro')
401
+ });
402
+ }
403
+ }
404
+ const tools = resolveTools(params.config);
405
+ const seeds = declaredTmpfsSeedEntries(tools).map((seed) => {
406
+ const mounted = mountsByDestination.get(seed.stagingPath)?.Type === 'bind';
407
+ return {
408
+ toolId: seed.toolId,
409
+ containerMount: seed.containerMount,
410
+ stagingPath: seed.stagingPath,
411
+ targetPath: seed.targetPath,
412
+ mounted,
413
+ targetState: mounted
414
+ ? targetState(params.engine, params.container, seed, runOkFn)
415
+ : 'missing' as const
416
+ };
417
+ });
418
+ const tmpfs = tools.filter((tool) => tool.tmpfs).map((tool) => ({
419
+ path: tool.containerMount,
420
+ permissionsOk: probe(
421
+ params.engine,
422
+ params.container,
423
+ 'test "$(stat -c %u:%g:%a -- "$1")" = "$(id -u devuser):$(id -g devuser):700"',
424
+ [tool.containerMount],
425
+ runOkFn
426
+ ),
427
+ writable: probe(
428
+ params.engine,
429
+ params.container,
430
+ 'probe="$1/.agent-infra-ready-$$"; trap \'rm -f -- "$probe"\' EXIT; : > "$probe"',
431
+ [tool.containerMount],
432
+ runOkFn
433
+ )
434
+ }));
435
+ const labels = inspection.Config?.Labels ?? {};
436
+ const branchLabel = labels[sandboxBranchLabel(params.config)];
437
+ const hasCodex = tools.some((tool) => tool.id === 'codex');
438
+ const mountSnapshots = expectedMounts({
439
+ config: params.config,
440
+ branch: params.branch,
441
+ engine: params.engine,
442
+ actualMounts: mountsByDestination
443
+ }).map((expected) => {
444
+ const actual = mountsByDestination.get(expected.path);
445
+ const actualSource = typeof actual?.Source === 'string' ? actual.Source : null;
446
+ const actualSourceCandidates = actualSource === null
447
+ ? []
448
+ : sourceCandidates(params.engine, actualSource);
449
+ const matchedHostPath = expected.hostPaths.find((hostPath) =>
450
+ sourceCandidates(params.engine, hostPath).some((candidate) =>
451
+ actualSourceCandidates.includes(candidate)
452
+ )
453
+ || (actualSource !== null && sameHostSource(hostPath, actualSource))
454
+ );
455
+ const sourceMatches = expected.expectedType === 'tmpfs'
456
+ ? actualSource === '' || actualSource === null
457
+ : matchedHostPath !== undefined;
458
+
459
+ return {
460
+ path: expected.path,
461
+ expectedType: expected.expectedType,
462
+ actualType: typeof actual?.Type === 'string' ? actual.Type : null,
463
+ expectedSource: expected.hostPaths[0]
464
+ ? normalizeMountSource(params.engine, expected.hostPaths[0])
465
+ : null,
466
+ actualSource,
467
+ sourceMatches,
468
+ expectedRW: expected.expectedRW,
469
+ actualRW: typeof actual?.RW === 'boolean' ? actual.RW : null,
470
+ sourceAccessible: expected.expectedType === 'tmpfs'
471
+ ? true
472
+ : matchedHostPath !== undefined
473
+ && hostSourceAccessible(matchedHostPath, expected.expectedRW)
474
+ };
475
+ });
476
+
477
+ return {
478
+ identityOk: typeof inspection.Id === 'string'
479
+ && inspection.Id.length > 0
480
+ && branchLabel === params.branch,
481
+ mounts: mountSnapshots,
482
+ tmpfs,
483
+ seeds,
484
+ aliasesReadable: probe(
485
+ params.engine,
486
+ params.container,
487
+ 'test -r "$1"',
488
+ ['/home/devuser/.bash_aliases'],
489
+ runOkFn
490
+ ),
491
+ codex: hasCodex
492
+ ? {
493
+ commandAvailable: probe(
494
+ params.engine,
495
+ params.container,
496
+ 'command -v codex',
497
+ [],
498
+ runOkFn
499
+ ),
500
+ stateWritable: probe(
501
+ params.engine,
502
+ params.container,
503
+ 'probe="$1/.agent-infra-codex-state-$$"; trap \'rm -f -- "$probe"\' EXIT; : > "$probe"',
504
+ ['/home/devuser/.codex'],
505
+ runOkFn
506
+ ),
507
+ promptsSourceExists: probe(
508
+ params.engine,
509
+ params.container,
510
+ 'test -d "$1"',
511
+ ['/workspace/.codex/commands'],
512
+ runOkFn
513
+ ),
514
+ promptsValid: probe(
515
+ params.engine,
516
+ params.container,
517
+ 'test "$(readlink -- "$1")" = "$2"',
518
+ ['/home/devuser/.codex/prompts', '/workspace/.codex/commands'],
519
+ runOkFn
520
+ )
521
+ }
522
+ : undefined
523
+ };
524
+ }
525
+
526
+ export function prepareTmpfsMounts(params: {
527
+ engine: string;
528
+ container: string;
529
+ mountPaths: string[];
530
+ deps?: RecoveryCommandDeps;
531
+ }): void {
532
+ const runVerboseFn = params.deps?.runVerbose ?? runVerboseEngine;
533
+ for (const mountPath of params.mountPaths) {
534
+ runVerboseFn(params.engine, 'docker', [
535
+ 'exec', '--user', 'root', params.container, 'chown', 'devuser:', '--', mountPath
536
+ ]);
537
+ runVerboseFn(params.engine, 'docker', [
538
+ 'exec', '--user', 'root', params.container, 'chmod', '0700', '--', mountPath
539
+ ]);
540
+ }
541
+ }
542
+
543
+ export function hydrateTmpfsSeedEntries(params: {
544
+ engine: string;
545
+ container: string;
546
+ entries: TmpfsSeedEntry[];
547
+ replace: boolean;
548
+ deps?: RecoveryCommandDeps;
549
+ }): void {
550
+ const runVerboseFn = params.deps?.runVerbose ?? runVerboseEngine;
551
+ for (const entry of params.entries) {
552
+ if (params.replace) {
553
+ runVerboseFn(params.engine, 'docker', [
554
+ 'exec', '--user', 'devuser', params.container, 'rm', '-rf', '--', entry.targetPath
555
+ ]);
556
+ }
557
+ runVerboseFn(params.engine, 'docker', [
558
+ 'exec', '--user', 'devuser', params.container, 'mkdir', '-p',
559
+ entry.targetPath.slice(0, entry.targetPath.lastIndexOf('/'))
560
+ ]);
561
+ runVerboseFn(params.engine, 'docker', [
562
+ 'exec', '--user', 'devuser', params.container, 'cp', '-R', '--',
563
+ entry.stagingPath, entry.targetPath
564
+ ]);
565
+ }
566
+ validateTmpfsSeedEntries(params);
567
+ }
568
+
569
+ export function validateTmpfsSeedEntries(params: {
570
+ engine: string;
571
+ container: string;
572
+ entries: TmpfsSeedEntry[];
573
+ deps?: RecoveryCommandDeps;
574
+ }): void {
575
+ const runVerboseFn = params.deps?.runVerbose ?? runVerboseEngine;
576
+ const runOkFn = params.deps?.runOk ?? runOkEngine;
577
+ for (const entry of params.entries) {
578
+ runVerboseFn(params.engine, 'docker', [
579
+ 'exec', '--user', 'devuser', params.container, 'diff', '-qr', '--',
580
+ entry.stagingPath, entry.targetPath
581
+ ]);
582
+ const readable = runOkFn(params.engine, 'docker', [
583
+ 'exec', '--user', 'devuser', params.container, 'test', '-r', entry.targetPath
584
+ ]);
585
+ const writable = runOkFn(params.engine, 'docker', [
586
+ 'exec', '--user', 'devuser', params.container, 'test', '-w', entry.targetPath
587
+ ]);
588
+ if (!readable || !writable) {
589
+ throw new Error(`Hydrated seed target ${entry.targetPath} is not writable by devuser.`);
590
+ }
591
+ }
592
+ }
593
+
594
+ function mountedSeeds(snapshot: SandboxRecoverySnapshot): TmpfsSeedEntry[] {
595
+ return snapshot.seeds.filter((seed) => seed.mounted).map((seed) => ({
596
+ toolId: seed.toolId,
597
+ containerMount: seed.containerMount ?? seed.targetPath.slice(0, seed.targetPath.lastIndexOf('/')),
598
+ stagingPath: seed.stagingPath,
599
+ targetPath: seed.targetPath
600
+ }));
601
+ }
602
+
603
+ function repairFindings(params: {
604
+ engine: string;
605
+ container: string;
606
+ findings: SandboxRecoveryFinding[];
607
+ deps?: RecoveryCommandDeps;
608
+ }): void {
609
+ const permissionPaths = params.findings
610
+ .filter((finding) => finding.repairKind === 'permissions' && finding.path)
611
+ .map((finding) => finding.path!);
612
+ prepareTmpfsMounts({
613
+ engine: params.engine,
614
+ container: params.container,
615
+ mountPaths: [...new Set(permissionPaths)],
616
+ deps: params.deps
617
+ });
618
+ const missingSeeds = params.findings.flatMap((finding) =>
619
+ finding.repairKind === 'missing-seed' && finding.seed ? [finding.seed] : []
620
+ );
621
+ hydrateTmpfsSeedEntries({
622
+ engine: params.engine,
623
+ container: params.container,
624
+ entries: missingSeeds,
625
+ replace: true,
626
+ deps: params.deps
627
+ });
628
+ const runVerboseFn = params.deps?.runVerbose ?? runVerboseEngine;
629
+ if (params.findings.some((finding) => finding.repairKind === 'builtin-link')) {
630
+ runVerboseFn(params.engine, 'docker', [
631
+ 'exec', '--user', 'devuser', params.container, 'ln', '-sfn',
632
+ '/workspace/.codex/commands', '/home/devuser/.codex/prompts'
633
+ ]);
634
+ }
635
+ }
636
+
637
+ function describeFindings(findings: SandboxRecoveryFinding[]): string {
638
+ return findings.map((finding) => finding.message).join(' ');
639
+ }
640
+
641
+ function assess(params: {
642
+ config: SandboxConfig;
643
+ engine: string;
644
+ branch: string;
645
+ container: string;
646
+ deps?: RecoveryCommandDeps;
647
+ }): { snapshot: SandboxRecoverySnapshot; findings: SandboxRecoveryFinding[] } {
648
+ const snapshot = collectSandboxRecoverySnapshot(params);
649
+ return { snapshot, findings: classifySandboxRecovery(snapshot) };
650
+ }
651
+
652
+ export function assertFreshSandboxReady(params: {
653
+ config: SandboxConfig;
654
+ engine: string;
655
+ branch: string;
656
+ container: string;
657
+ copiedEntries: TmpfsSeedEntry[];
658
+ deps?: RecoveryCommandDeps;
659
+ }): void {
660
+ validateTmpfsSeedEntries({
661
+ engine: params.engine,
662
+ container: params.container,
663
+ entries: params.copiedEntries,
664
+ deps: params.deps
665
+ });
666
+ const { findings } = assess(params);
667
+ if (findings.length > 0) {
668
+ throw new Error(`Fresh sandbox readiness check failed: ${describeFindings(findings)}`);
669
+ }
670
+ }
671
+
672
+ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Promise<SandboxReadyResult> {
673
+ const deps = params.deps;
674
+ const startFn = deps?.start ?? startSandboxContainer;
675
+ const warnings: string[] = [];
676
+ let failure: Error | null = null;
677
+
678
+ try {
679
+ if (!params.row.running) {
680
+ startFn(params.engine, params.row.name);
681
+ const initial = assess({
682
+ config: params.config,
683
+ engine: params.engine,
684
+ branch: params.branch,
685
+ container: params.row.name,
686
+ deps
687
+ });
688
+ if (initial.findings.some((finding) => finding.repairKind === 'hard-failure')) {
689
+ throw new Error(describeFindings(
690
+ initial.findings.filter((finding) => finding.repairKind === 'hard-failure')
691
+ ));
692
+ }
693
+ const tools = resolveTools(params.config);
694
+ prepareTmpfsMounts({
695
+ engine: params.engine,
696
+ container: params.row.name,
697
+ mountPaths: tools.filter((tool) => tool.tmpfs).map((tool) => tool.containerMount),
698
+ deps
699
+ });
700
+ hydrateTmpfsSeedEntries({
701
+ engine: params.engine,
702
+ container: params.row.name,
703
+ entries: mountedSeeds(initial.snapshot),
704
+ replace: true,
705
+ deps
706
+ });
707
+ let final = assess({
708
+ config: params.config,
709
+ engine: params.engine,
710
+ branch: params.branch,
711
+ container: params.row.name,
712
+ deps
713
+ });
714
+ if (!final.findings.some((finding) => finding.repairKind === 'hard-failure')) {
715
+ repairFindings({
716
+ engine: params.engine,
717
+ container: params.row.name,
718
+ findings: final.findings,
719
+ deps
720
+ });
721
+ final = assess({
722
+ config: params.config,
723
+ engine: params.engine,
724
+ branch: params.branch,
725
+ container: params.row.name,
726
+ deps
727
+ });
728
+ }
729
+ if (final.findings.length > 0) {
730
+ throw new Error(describeFindings(final.findings));
731
+ }
732
+ return { container: params.row.name, path: 'recovered', warnings };
733
+ }
734
+
735
+ const initial = assess({
736
+ config: params.config,
737
+ engine: params.engine,
738
+ branch: params.branch,
739
+ container: params.row.name,
740
+ deps
741
+ });
742
+ if (initial.findings.length === 0) {
743
+ return { container: params.row.name, path: 'healthy', warnings };
744
+ }
745
+ if (initial.findings.some((finding) => finding.repairKind === 'hard-failure')) {
746
+ throw new Error(describeFindings(initial.findings));
747
+ }
748
+ let current = initial;
749
+ const permissionFindings = current.findings.filter(
750
+ (finding) => finding.repairKind === 'permissions'
751
+ );
752
+ if (permissionFindings.length > 0) {
753
+ repairFindings({
754
+ engine: params.engine,
755
+ container: params.row.name,
756
+ findings: permissionFindings,
757
+ deps
758
+ });
759
+ current = assess({
760
+ config: params.config,
761
+ engine: params.engine,
762
+ branch: params.branch,
763
+ container: params.row.name,
764
+ deps
765
+ });
766
+ if (current.findings.some(
767
+ (finding) => finding.repairKind === 'hard-failure'
768
+ || finding.repairKind === 'permissions'
769
+ )) {
770
+ throw new Error(describeFindings(current.findings));
771
+ }
772
+ }
773
+ repairFindings({
774
+ engine: params.engine,
775
+ container: params.row.name,
776
+ findings: current.findings,
777
+ deps
778
+ });
779
+ const final = assess({
780
+ config: params.config,
781
+ engine: params.engine,
782
+ branch: params.branch,
783
+ container: params.row.name,
784
+ deps
785
+ });
786
+ if (final.findings.length > 0) {
787
+ throw new Error(describeFindings(final.findings));
788
+ }
789
+ return { container: params.row.name, path: 'recovered', warnings };
790
+ } catch (error) {
791
+ failure = error instanceof Error ? error : new Error(String(error));
792
+ }
793
+
794
+ const dataBoundary =
795
+ 'The existing container writable layer, ordinary /tmp data, processes, tmux sessions, and RAM state may be lost by replacement; worktree and host-managed sandbox data are preserved.';
796
+ if (!params.allowRecreate || !params.recreate) {
797
+ throw new Error(`Sandbox recovery failed: ${failure.message} ${dataBoundary} Re-run with --recreate to authorize container-only replacement.`);
798
+ }
799
+
800
+ const warning = `Sandbox recovery failed in place. Replacing only the container. ${dataBoundary}`;
801
+ warnings.push(warning);
802
+ (params.writeWarning ?? ((message) => process.stderr.write(`${message}\n`)))(warning);
803
+ await params.recreate(params.branch);
804
+
805
+ const rows = (deps?.fetchRows ?? fetchSandboxRows)(
806
+ params.engine,
807
+ sandboxLabel(params.config),
808
+ sandboxBranchLabel(params.config)
809
+ );
810
+ const replacement = selectSandboxContainer(
811
+ [...rows.running, ...rows.nonRunning],
812
+ containerNameCandidates(params.config, params.branch)
813
+ );
814
+ if (!replacement?.running) {
815
+ throw new Error('Replacement sandbox container was not found in a running state.');
816
+ }
817
+ const final = assess({
818
+ config: params.config,
819
+ engine: params.engine,
820
+ branch: params.branch,
821
+ container: replacement.name,
822
+ deps
823
+ });
824
+ if (final.findings.length > 0) {
825
+ throw new Error(`Replacement sandbox readiness check failed: ${describeFindings(final.findings)}`);
826
+ }
827
+ return { container: replacement.name, path: 'recreated', warnings };
828
+ }