@fitlab-ai/agent-infra 0.9.12 → 0.9.14

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 (402) hide show
  1. package/README.md +11 -11
  2. package/README.zh-CN.md +11 -11
  3. package/bin/cli.ts +26 -9
  4. package/dist/bin/cli.js +173 -157
  5. package/dist/bin/internal-cli.js +66 -40
  6. package/dist/lib/agent-client.js +5 -0
  7. package/dist/lib/agent-clients/next-steps.js +6 -1
  8. package/dist/lib/decide.js +3 -3
  9. package/dist/lib/internal/agent-client.js +5 -2
  10. package/dist/lib/internal/cli-route-inventory.js +152 -0
  11. package/dist/lib/internal/codex-lifecycle.js +9 -6
  12. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  13. package/dist/lib/internal/git-workflow.js +10 -6
  14. package/dist/lib/internal/platform-checks.js +19 -7
  15. package/dist/lib/internal/platform-comment.js +25 -13
  16. package/dist/lib/internal/platform-context.js +6 -3
  17. package/dist/lib/internal/platform-issue.js +20 -13
  18. package/dist/lib/internal/platform-metadata.js +88 -0
  19. package/dist/lib/internal/platform-pr-review.js +26 -13
  20. package/dist/lib/internal/platform-pr.js +84 -31
  21. package/dist/lib/internal/platform-release-notes.js +9 -6
  22. package/dist/lib/internal/platform-security.js +93 -0
  23. package/dist/lib/internal/pr-review-grade.js +25 -12
  24. package/dist/lib/internal/release-workflow.js +23 -9
  25. package/dist/lib/internal/sandbox-control.js +50 -7
  26. package/dist/lib/internal/task-activity.js +16 -7
  27. package/dist/lib/internal/task-artifact.js +235 -24
  28. package/dist/lib/internal/task-context.js +4 -1
  29. package/dist/lib/internal/task-create.js +50 -17
  30. package/dist/lib/internal/task-delivery.js +4 -1
  31. package/dist/lib/internal/task-event.js +10 -4
  32. package/dist/lib/internal/task-finalization.js +6 -3
  33. package/dist/lib/internal/task-invalidation.js +63 -0
  34. package/dist/lib/internal/task-ledger.js +20 -6
  35. package/dist/lib/internal/task-lifecycle.js +6 -3
  36. package/dist/lib/internal/task-operation-registry.js +421 -0
  37. package/dist/lib/internal/task-orchestration.js +12 -0
  38. package/dist/lib/internal/task-override.js +14 -7
  39. package/dist/lib/internal/task-qualification.js +90 -0
  40. package/dist/lib/internal/task-review.js +7 -4
  41. package/dist/lib/internal/task-short-id.js +11 -1
  42. package/dist/lib/internal/task-snapshot.js +4 -1
  43. package/dist/lib/internal/task-validate.js +7 -2
  44. package/dist/lib/internal/task-verify.js +11 -7
  45. package/dist/lib/internal/task-warning.js +11 -4
  46. package/dist/lib/platform/adapters.js +1 -3
  47. package/dist/lib/platform/change-request-git-evidence.js +27 -7
  48. package/dist/lib/platform/comment-safety.js +226 -0
  49. package/dist/lib/platform/completion-backfill.js +6 -5
  50. package/dist/lib/platform/context.js +70 -130
  51. package/dist/lib/platform/github-client.js +1 -1
  52. package/dist/lib/platform/github-provider.js +1130 -0
  53. package/dist/lib/platform/in-label-sync.js +143 -0
  54. package/dist/lib/platform/issue-comments.js +237 -72
  55. package/dist/lib/platform/issues.js +335 -189
  56. package/dist/lib/platform/metadata-labels.js +6 -2
  57. package/dist/lib/platform/none-provider.js +28 -0
  58. package/dist/lib/platform/pr-change-report.js +545 -0
  59. package/dist/lib/platform/pr-checks.js +119 -104
  60. package/dist/lib/platform/pr-review.js +88 -72
  61. package/dist/lib/platform/pr-summary.js +541 -50
  62. package/dist/lib/platform/provider-bridge.js +45 -0
  63. package/dist/lib/platform/provider-contract.d.ts +553 -0
  64. package/dist/lib/platform/provider-contract.js +99 -0
  65. package/dist/lib/platform/provider-loader.js +251 -0
  66. package/dist/lib/platform/provider-validation.js +577 -0
  67. package/dist/lib/platform/pull-request-metadata.js +8 -6
  68. package/dist/lib/platform/pull-requests.js +734 -543
  69. package/dist/lib/platform/release-notes.js +78 -55
  70. package/dist/lib/platform/releases.js +79 -50
  71. package/dist/lib/platform/repository-metadata.js +377 -0
  72. package/dist/lib/platform/resource-identity.d.ts +26 -0
  73. package/dist/lib/platform/resource-identity.js +92 -0
  74. package/dist/lib/platform/security-alerts.js +155 -0
  75. package/dist/lib/platform/task-identities.js +31 -0
  76. package/dist/lib/platform/verification-required.js +11 -12
  77. package/dist/lib/platform/verification-sync.js +129 -248
  78. package/dist/lib/pr-review/evidence-grading.js +19 -6
  79. package/dist/lib/process-data/index.js +7 -0
  80. package/dist/lib/run/index.js +68 -23
  81. package/dist/lib/sandbox/commands/control-status.js +22 -0
  82. package/dist/lib/sandbox/commands/create.js +32 -6
  83. package/dist/lib/sandbox/commands/ls.js +5 -0
  84. package/dist/lib/sandbox/commands/rm.js +827 -120
  85. package/dist/lib/sandbox/commands/show.js +16 -0
  86. package/dist/lib/sandbox/control/client.js +174 -19
  87. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  88. package/dist/lib/sandbox/control/container-identity.js +28 -31
  89. package/dist/lib/sandbox/control/executor.js +151 -16
  90. package/dist/lib/sandbox/control/lifecycle.js +621 -127
  91. package/dist/lib/sandbox/control/native-file-lock.js +177 -0
  92. package/dist/lib/sandbox/control/protocol.js +77 -0
  93. package/dist/lib/sandbox/control/server.js +677 -34
  94. package/dist/lib/sandbox/control/state.js +209 -4
  95. package/dist/lib/sandbox/control/task-view.js +183 -0
  96. package/dist/lib/sandbox/engines/authority.js +289 -0
  97. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  98. package/dist/lib/sandbox/index.js +5 -0
  99. package/dist/lib/sandbox/recovery.js +21 -1
  100. package/dist/lib/sandbox/workspace-view.js +2 -1
  101. package/dist/lib/sandbox/worktree-safety.js +4 -1
  102. package/dist/lib/server/index.js +8 -0
  103. package/dist/lib/server/protocol.js +38 -1
  104. package/dist/lib/task/activity-log.js +8 -1
  105. package/dist/lib/task/artifact-lifecycle.js +75 -18
  106. package/dist/lib/task/artifact-operations.js +520 -0
  107. package/dist/lib/task/artifact-repair-intent.js +59 -0
  108. package/dist/lib/task/artifact-schema.js +135 -0
  109. package/dist/lib/task/capabilities.js +310 -0
  110. package/dist/lib/task/commands/status.js +32 -0
  111. package/dist/lib/task/commit-operation.js +12 -1
  112. package/dist/lib/task/control-authority.js +12 -5
  113. package/dist/lib/task/create-service.js +111 -12
  114. package/dist/lib/task/create.js +25 -0
  115. package/dist/lib/task/current-contract.js +5 -1
  116. package/dist/lib/task/decision-details.js +1 -1
  117. package/dist/lib/task/events.js +416 -17
  118. package/dist/lib/task/finalization.js +37 -13
  119. package/dist/lib/task/human-override.js +58 -36
  120. package/dist/lib/task/index.js +11 -3
  121. package/dist/lib/task/invalidation-command.js +86 -0
  122. package/dist/lib/task/invalidation.js +260 -0
  123. package/dist/lib/task/ledger-intents.js +37 -0
  124. package/dist/lib/task/local-artifact-finalization.js +299 -0
  125. package/dist/lib/task/orchestration.js +240 -144
  126. package/dist/lib/task/pr-delivery-fact.js +102 -101
  127. package/dist/lib/task/qualification-audit.js +490 -0
  128. package/dist/lib/task/qualification-confirmation.js +109 -0
  129. package/dist/lib/task/qualification-intents.js +148 -0
  130. package/dist/lib/task/review-artifacts.js +22 -23
  131. package/dist/lib/task/review-finalization.js +121 -4
  132. package/dist/lib/task/review-fingerprint.js +24 -2
  133. package/dist/lib/task/rework-intent.js +138 -0
  134. package/dist/lib/task/sections.js +6 -4
  135. package/dist/lib/task/skill-scope.js +23 -0
  136. package/dist/lib/task/task-execution-lock.js +27 -17
  137. package/dist/lib/task/verification-config.js +11 -0
  138. package/dist/lib/task/verification-engine.js +93 -16
  139. package/dist/lib/task/verification.js +3 -3
  140. package/dist/lib/task/workflow-warning-intents.js +1 -1
  141. package/dist/lib/task/write.js +11 -0
  142. package/dist/lib/version.js +11 -1
  143. package/dist/lifecycle-build-manifest.json +3 -3
  144. package/lib/agent-client.ts +9 -0
  145. package/lib/agent-clients/next-steps.ts +6 -1
  146. package/lib/decide.ts +3 -3
  147. package/lib/init.ts +1 -1
  148. package/lib/internal/agent-client.ts +4 -2
  149. package/lib/internal/cli-route-inventory.ts +140 -0
  150. package/lib/internal/codex-lifecycle.ts +8 -6
  151. package/lib/internal/codex-sandbox-controller.ts +7 -2
  152. package/lib/internal/git-workflow.ts +9 -6
  153. package/lib/internal/platform-checks.ts +14 -6
  154. package/lib/internal/platform-comment.ts +20 -12
  155. package/lib/internal/platform-context.ts +5 -3
  156. package/lib/internal/platform-issue.ts +17 -11
  157. package/lib/internal/platform-metadata.ts +70 -0
  158. package/lib/internal/platform-pr-review.ts +23 -12
  159. package/lib/internal/platform-pr.ts +65 -27
  160. package/lib/internal/platform-release-notes.ts +8 -6
  161. package/lib/internal/platform-security.ts +83 -0
  162. package/lib/internal/pr-review-grade.ts +20 -11
  163. package/lib/internal/release-workflow.ts +21 -9
  164. package/lib/internal/sandbox-control.ts +44 -6
  165. package/lib/internal/task-activity.ts +15 -7
  166. package/lib/internal/task-artifact.ts +204 -22
  167. package/lib/internal/task-context.ts +3 -1
  168. package/lib/internal/task-create.ts +78 -17
  169. package/lib/internal/task-delivery.ts +3 -1
  170. package/lib/internal/task-event.ts +9 -4
  171. package/lib/internal/task-finalization.ts +6 -4
  172. package/lib/internal/task-invalidation.ts +41 -0
  173. package/lib/internal/task-ledger.ts +21 -8
  174. package/lib/internal/task-lifecycle.ts +5 -3
  175. package/lib/internal/task-operation-registry.ts +482 -0
  176. package/lib/internal/task-orchestration.ts +12 -0
  177. package/lib/internal/task-override.ts +15 -9
  178. package/lib/internal/task-qualification.ts +61 -0
  179. package/lib/internal/task-review.ts +6 -4
  180. package/lib/internal/task-short-id.ts +10 -1
  181. package/lib/internal/task-snapshot.ts +3 -1
  182. package/lib/internal/task-validate.ts +6 -2
  183. package/lib/internal/task-verify.ts +10 -7
  184. package/lib/internal/task-warning.ts +10 -4
  185. package/lib/platform/adapters.ts +3 -2
  186. package/lib/platform/change-request-git-evidence.ts +36 -9
  187. package/lib/platform/comment-safety.ts +250 -0
  188. package/lib/platform/completion-backfill.ts +9 -8
  189. package/lib/platform/context.ts +97 -134
  190. package/lib/platform/github-client.ts +2 -2
  191. package/lib/platform/github-provider.ts +1132 -0
  192. package/lib/platform/in-label-sync.ts +208 -0
  193. package/lib/platform/issue-comments.ts +255 -80
  194. package/lib/platform/issues.ts +344 -174
  195. package/lib/platform/metadata-labels.ts +5 -2
  196. package/lib/platform/none-provider.ts +35 -0
  197. package/lib/platform/pr-change-report.ts +639 -0
  198. package/lib/platform/pr-checks.ts +126 -106
  199. package/lib/platform/pr-review.ts +88 -85
  200. package/lib/platform/pr-summary.ts +581 -53
  201. package/lib/platform/provider-bridge.ts +82 -0
  202. package/lib/platform/provider-contract.ts +611 -0
  203. package/lib/platform/provider-loader.ts +307 -0
  204. package/lib/platform/provider-validation.ts +611 -0
  205. package/lib/platform/pull-request-metadata.ts +10 -7
  206. package/lib/platform/pull-requests.ts +781 -550
  207. package/lib/platform/release-notes.ts +76 -59
  208. package/lib/platform/releases.ts +77 -43
  209. package/lib/platform/repository-metadata.ts +453 -0
  210. package/lib/platform/resource-identity.ts +121 -0
  211. package/lib/platform/security-alerts.ts +191 -0
  212. package/lib/platform/task-identities.ts +40 -0
  213. package/lib/platform/types.ts +4 -2
  214. package/lib/platform/verification-required.ts +11 -13
  215. package/lib/platform/verification-sync.ts +128 -304
  216. package/lib/pr-review/evidence-grading.ts +25 -8
  217. package/lib/process-data/index.ts +7 -0
  218. package/lib/run/index.ts +62 -22
  219. package/lib/sandbox/commands/control-status.ts +26 -0
  220. package/lib/sandbox/commands/create.ts +46 -6
  221. package/lib/sandbox/commands/ls.ts +7 -0
  222. package/lib/sandbox/commands/rm.ts +1000 -87
  223. package/lib/sandbox/commands/show.ts +16 -0
  224. package/lib/sandbox/control/client.ts +203 -19
  225. package/lib/sandbox/control/completed-reentry.ts +98 -0
  226. package/lib/sandbox/control/container-identity.ts +27 -43
  227. package/lib/sandbox/control/executor.ts +180 -15
  228. package/lib/sandbox/control/lifecycle.ts +629 -17
  229. package/lib/sandbox/control/native-file-lock.ts +209 -0
  230. package/lib/sandbox/control/protocol.ts +118 -2
  231. package/lib/sandbox/control/server.ts +724 -34
  232. package/lib/sandbox/control/state.ts +257 -4
  233. package/lib/sandbox/control/task-view.ts +240 -0
  234. package/lib/sandbox/engines/authority.ts +365 -0
  235. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  236. package/lib/sandbox/index.ts +7 -0
  237. package/lib/sandbox/recovery.ts +21 -1
  238. package/lib/sandbox/workspace-view.ts +9 -3
  239. package/lib/sandbox/worktree-safety.ts +4 -1
  240. package/lib/server/index.ts +10 -0
  241. package/lib/server/protocol.ts +36 -1
  242. package/lib/task/activity-log.ts +8 -0
  243. package/lib/task/artifact-lifecycle.ts +70 -22
  244. package/lib/task/artifact-operations.ts +626 -0
  245. package/lib/task/artifact-repair-intent.ts +71 -0
  246. package/lib/task/artifact-schema.ts +192 -0
  247. package/lib/task/capabilities.ts +331 -0
  248. package/lib/task/commands/status.ts +38 -0
  249. package/lib/task/commit-operation.ts +12 -1
  250. package/lib/task/control-authority.ts +22 -9
  251. package/lib/task/create-service.ts +139 -14
  252. package/lib/task/create.ts +27 -0
  253. package/lib/task/current-contract.ts +6 -1
  254. package/lib/task/decision-details.ts +6 -2
  255. package/lib/task/events.ts +392 -16
  256. package/lib/task/finalization.ts +50 -14
  257. package/lib/task/human-override.ts +70 -46
  258. package/lib/task/index.ts +16 -3
  259. package/lib/task/invalidation-command.ts +86 -0
  260. package/lib/task/invalidation.ts +332 -0
  261. package/lib/task/ledger-intents.ts +35 -1
  262. package/lib/task/local-artifact-finalization.ts +432 -0
  263. package/lib/task/orchestration.ts +223 -133
  264. package/lib/task/pr-delivery-fact.ts +131 -148
  265. package/lib/task/qualification-audit.ts +599 -0
  266. package/lib/task/qualification-confirmation.ts +117 -0
  267. package/lib/task/qualification-intents.ts +150 -0
  268. package/lib/task/review-artifacts.ts +21 -22
  269. package/lib/task/review-finalization.ts +133 -4
  270. package/lib/task/review-fingerprint.ts +23 -2
  271. package/lib/task/rework-intent.ts +165 -0
  272. package/lib/task/sections.ts +6 -4
  273. package/lib/task/skill-scope.ts +24 -0
  274. package/lib/task/task-execution-lock.ts +23 -14
  275. package/lib/task/verification-config.ts +12 -0
  276. package/lib/task/verification-engine.ts +103 -17
  277. package/lib/task/verification.ts +4 -4
  278. package/lib/task/workflow-warning-intents.ts +1 -1
  279. package/lib/task/write.ts +19 -0
  280. package/lib/update.ts +1 -1
  281. package/lib/version.ts +15 -1
  282. package/package.json +12 -1
  283. package/templates/.agents/rules/README.en.md +3 -1
  284. package/templates/.agents/rules/README.zh-CN.md +3 -1
  285. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  286. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  287. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  288. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  289. package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
  290. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  291. package/templates/.agents/rules/issue-sync.en.md +2 -0
  292. package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
  293. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  294. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  295. package/templates/.agents/rules/local-artifact-repair.en.md +13 -2
  296. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -2
  297. package/templates/.agents/rules/pr-sync.en.md +10 -4
  298. package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
  299. package/templates/.agents/rules/security-alerts.en.md +8 -8
  300. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  301. package/templates/.agents/rules/task-short-id.en.md +1 -4
  302. package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
  303. package/templates/.agents/skills/analyze-task/SKILL.en.md +28 -5
  304. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +29 -5
  305. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  306. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  307. package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
  308. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
  309. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  310. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  311. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  312. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  313. package/templates/.agents/skills/code-task/SKILL.en.md +41 -9
  314. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +43 -11
  315. package/templates/.agents/skills/code-task/config/verify.en.json +6 -14
  316. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +6 -14
  317. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  318. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  319. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  320. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  321. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  322. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  323. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  324. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  325. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
  326. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
  327. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
  328. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
  329. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +10 -4
  330. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +10 -4
  331. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  332. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  333. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
  334. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
  335. package/templates/.agents/skills/complete-task/SKILL.en.md +26 -21
  336. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +25 -21
  337. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
  338. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
  339. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  340. package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
  341. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
  342. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
  343. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
  344. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  345. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  346. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
  347. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  348. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  349. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  350. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  351. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  352. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  353. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  354. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  355. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  356. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  357. package/templates/.agents/skills/plan-task/SKILL.en.md +27 -4
  358. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +26 -4
  359. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  360. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  361. package/templates/.agents/skills/review-analysis/SKILL.en.md +17 -4
  362. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +17 -4
  363. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  364. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  365. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  366. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  367. package/templates/.agents/skills/review-code/SKILL.en.md +18 -5
  368. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +18 -5
  369. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  370. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  371. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  372. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  373. package/templates/.agents/skills/review-plan/SKILL.en.md +17 -4
  374. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +17 -4
  375. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  376. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  377. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  378. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  379. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  380. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  381. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  382. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  383. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +9 -3
  384. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +9 -3
  385. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
  386. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
  387. package/templates/.agents/skills/test/SKILL.en.md +13 -2
  388. package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
  389. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  390. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  391. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  392. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
  393. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
  394. package/templates/.agents/templates/task.en.md +24 -1
  395. package/templates/.agents/templates/task.zh-CN.md +24 -1
  396. package/templates/.github/workflows/pr-label.yml +7 -26
  397. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  398. package/templates/.agents/scripts/security-alerts.sh +0 -31
  399. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  400. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  401. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  402. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -1,7 +1,8 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
+ import os from 'node:os';
3
4
  import path from 'node:path';
4
- import { randomUUID } from 'node:crypto';
5
+ import { createHash, randomUUID } from 'node:crypto';
5
6
  import { buildProcessTreeStopCommand } from '../../server/process-control.ts';
6
7
  import {
7
8
  getProcessIdentityState,
@@ -16,6 +17,12 @@ import type {
16
17
  } from './protocol.ts';
17
18
  import { DEFAULT_SANDBOX_CONTROL_TIMING } from './protocol.ts';
18
19
  import { inspectSandboxControlContainer, type ContainerObservation } from './container-identity.ts';
20
+ import {
21
+ acquireSandboxResourceLock,
22
+ resolveSandboxLockNamespace,
23
+ type SandboxResourceLock
24
+ } from './native-file-lock.ts';
25
+ import { isSandboxAuthorityEvidence } from '../engines/authority.ts';
19
26
  import {
20
27
  parseSandboxControlStatus,
21
28
  readExecution,
@@ -30,6 +37,528 @@ const QUIESCING_FILE = 'quiescing.json';
30
37
  const BROKER_STARTING_FILE = 'broker-starting.json';
31
38
  const REPLACEMENT_FILE = 'replacement.json';
32
39
  const REPLACEMENT_STATE_SUFFIX = '.replacement-state.json';
40
+ const REMOVAL_JOURNAL_ROOT = path.join('.agent-infra', 'sandbox-removal-journal');
41
+
42
+ export const SANDBOX_REMOVAL_JOURNAL_PHASES = [
43
+ 'prepared', 'target-committed', 'container-removal', 'container-absent',
44
+ 'carrier-finalizing', 'carrier-removed', 'workspace-finalizing', 'workspace-removed',
45
+ 'branch-finalizing', 'branch-removed', 'tool-finalizing', 'tool-removed',
46
+ 'shell-finalizing', 'shell-removed', 'share-finalizing', 'share-removed', 'completed'
47
+ ] as const;
48
+ export type SandboxRemovalJournalPhase = typeof SANDBOX_REMOVAL_JOURNAL_PHASES[number];
49
+ export type SandboxRemovalTargetCommit = Readonly<{
50
+ branch: string;
51
+ project: string;
52
+ controlRoot: string;
53
+ targetDigest: string;
54
+ permitDigest: string;
55
+ removeWorktree: boolean;
56
+ removeBranch: boolean;
57
+ removeShare: boolean;
58
+ worktreePaths: readonly string[];
59
+ workspaceViewPaths: readonly string[];
60
+ toolPaths: readonly string[];
61
+ shellPaths: readonly string[];
62
+ sharePath: string;
63
+ permits: readonly Readonly<{
64
+ path: string;
65
+ mode: 'clean' | 'discard';
66
+ snapshot: Readonly<{
67
+ worktree: string;
68
+ branch: string;
69
+ head: string;
70
+ changes: readonly Readonly<{
71
+ indexStatus: string;
72
+ worktreeStatus: string;
73
+ path: string;
74
+ originalPath?: string;
75
+ }>[];
76
+ identity: string;
77
+ source?: 'registered' | 'recovered';
78
+ recovery?: Readonly<{
79
+ repoRoot: string;
80
+ worktreeBase: string;
81
+ branch: string;
82
+ identitySource: 'branch-only' | 'task-bound';
83
+ taskId: string | null;
84
+ }>;
85
+ }>;
86
+ }>[];
87
+ }>;
88
+ export type SandboxRemovalJournal = Readonly<{
89
+ version: 2;
90
+ operation: 'sandbox-rm';
91
+ oldOperation: 'sandbox-rm';
92
+ newOperation: 'sandbox-rm';
93
+ handoffId: string;
94
+ transitionId: string;
95
+ engine: string;
96
+ containerId: string;
97
+ generation: string;
98
+ authorityFingerprint: string;
99
+ carrierIdentityDigest: string;
100
+ lockDomain: string;
101
+ owner: ProcessIdentity & Readonly<{ leaseNonce: string }>;
102
+ phase: SandboxRemovalJournalPhase;
103
+ expectedOldJournalRevision: number | null;
104
+ revision: number;
105
+ target: SandboxRemovalTargetCommit;
106
+ recordedAt: number;
107
+ }>;
108
+
109
+ function removalCarrierDigest(engine: string, containerId: string, generation: string): string {
110
+ return createHash('sha256').update(`${engine}\0${containerId}\0${generation}`).digest('hex');
111
+ }
112
+
113
+ function assertPrivateDirectory(directory: string): void {
114
+ const home = path.resolve(os.homedir());
115
+ const resolved = path.resolve(directory);
116
+ const relative = path.relative(home, resolved);
117
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
118
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
119
+ }
120
+ let current = home;
121
+ for (const segment of relative.split(path.sep).filter(Boolean)) {
122
+ current = path.join(current, segment);
123
+ const stat = fs.lstatSync(current);
124
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
125
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
126
+ }
127
+ if (process.platform !== 'win32' && typeof process.getuid === 'function' && stat.uid !== process.getuid()) {
128
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
129
+ }
130
+ }
131
+ }
132
+
133
+ function removalJournalDomain(lockDomain: string, create = true): string {
134
+ const namespace = resolveSandboxLockNamespace('sandbox-removal-journal', { lockDomain });
135
+ const root = path.join(path.dirname(namespace.lockRoot), REMOVAL_JOURNAL_ROOT.split(path.sep).at(-1)!);
136
+ if (create) fs.mkdirSync(root, { recursive: true, mode: 0o700 });
137
+ assertPrivateDirectory(root);
138
+ const domain = path.join(root, lockDomain);
139
+ if (create) fs.mkdirSync(domain, { recursive: true, mode: 0o700 });
140
+ assertPrivateDirectory(domain);
141
+ return domain;
142
+ }
143
+
144
+ function removalJournalPath(manifest: SandboxControlManifest): string {
145
+ return path.join(
146
+ removalJournalDomain(manifest.authorityEvidence.lockDomain),
147
+ `${removalCarrierDigest(manifest.engine, manifest.containerIdentity.id, manifest.generation)}.json`
148
+ );
149
+ }
150
+
151
+ function removalJournalPathForRecord(record: SandboxRemovalJournal): string {
152
+ return path.join(
153
+ removalJournalDomain(record.lockDomain, false),
154
+ `${record.carrierIdentityDigest}.json`
155
+ );
156
+ }
157
+
158
+ function isRecord(value: unknown): value is Record<string, unknown> {
159
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
160
+ }
161
+
162
+ function hasExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
163
+ const actual = Object.keys(value).sort();
164
+ return actual.length === keys.length && actual.every((key, index) => key === keys[index]);
165
+ }
166
+
167
+ function isRemovalPermitCommit(value: unknown): boolean {
168
+ if (!isRecord(value) || typeof value.path !== 'string' || !path.isAbsolute(value.path)
169
+ || (value.mode !== 'clean' && value.mode !== 'discard') || !isRecord(value.snapshot)
170
+ || typeof value.snapshot.worktree !== 'string' || !path.isAbsolute(value.snapshot.worktree)
171
+ || typeof value.snapshot.branch !== 'string' || typeof value.snapshot.head !== 'string'
172
+ || typeof value.snapshot.identity !== 'string' || !Array.isArray(value.snapshot.changes)
173
+ || value.snapshot.changes.some((change) => !isRecord(change)
174
+ || typeof change.indexStatus !== 'string' || typeof change.worktreeStatus !== 'string'
175
+ || typeof change.path !== 'string'
176
+ || (change.originalPath !== undefined && typeof change.originalPath !== 'string'))
177
+ || (value.snapshot.source !== undefined
178
+ && value.snapshot.source !== 'registered' && value.snapshot.source !== 'recovered')) return false;
179
+ if (value.snapshot.recovery === undefined) return true;
180
+ const recovery = value.snapshot.recovery;
181
+ return isRecord(recovery)
182
+ && typeof recovery.repoRoot === 'string' && path.isAbsolute(recovery.repoRoot)
183
+ && typeof recovery.worktreeBase === 'string' && path.isAbsolute(recovery.worktreeBase)
184
+ && typeof recovery.branch === 'string'
185
+ && (recovery.identitySource === 'branch-only' || recovery.identitySource === 'task-bound')
186
+ && (recovery.taskId === null || typeof recovery.taskId === 'string');
187
+ }
188
+
189
+ function parseRemovalJournal(raw: string): SandboxRemovalJournal {
190
+ let value: unknown;
191
+ try {
192
+ value = JSON.parse(raw);
193
+ } catch {
194
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
195
+ }
196
+ if (!isRecord(value) || !hasExactKeys(value, [
197
+ 'authorityFingerprint', 'carrierIdentityDigest', 'containerId', 'engine', 'expectedOldJournalRevision',
198
+ 'generation', 'handoffId', 'lockDomain', 'newOperation', 'oldOperation', 'operation', 'owner', 'phase',
199
+ 'recordedAt', 'revision', 'target', 'transitionId', 'version'
200
+ ]) || !isRecord(value.owner) || !hasExactKeys(value.owner, ['leaseNonce', 'pid', 'startTime'])
201
+ || !isRecord(value.target) || !hasExactKeys(value.target, [
202
+ 'branch', 'controlRoot', 'permitDigest', 'permits', 'project', 'removeBranch', 'removeShare',
203
+ 'removeWorktree', 'sharePath', 'shellPaths', 'targetDigest', 'toolPaths', 'workspaceViewPaths',
204
+ 'worktreePaths'
205
+ ]) || value.version !== 2 || value.operation !== 'sandbox-rm'
206
+ || value.oldOperation !== 'sandbox-rm' || value.newOperation !== 'sandbox-rm'
207
+ || typeof value.handoffId !== 'string' || value.handoffId.length === 0
208
+ || typeof value.transitionId !== 'string' || value.transitionId.length === 0
209
+ || typeof value.engine !== 'string' || value.engine.length === 0
210
+ || typeof value.containerId !== 'string' || value.containerId.length === 0
211
+ || typeof value.generation !== 'string' || value.generation.length === 0
212
+ || typeof value.authorityFingerprint !== 'string' || !/^[a-f0-9]{64}$/u.test(value.authorityFingerprint)
213
+ || typeof value.carrierIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(value.carrierIdentityDigest)
214
+ || typeof value.lockDomain !== 'string' || !/^[a-f0-9]{64}$/u.test(value.lockDomain)
215
+ || !Number.isSafeInteger(value.owner.pid) || (value.owner.pid as number) <= 0
216
+ || !Number.isSafeInteger(value.owner.startTime) || typeof value.owner.leaseNonce !== 'string'
217
+ || value.owner.leaseNonce.length === 0
218
+ || !SANDBOX_REMOVAL_JOURNAL_PHASES.includes(value.phase as SandboxRemovalJournalPhase)
219
+ || !(value.expectedOldJournalRevision === null
220
+ || (Number.isSafeInteger(value.expectedOldJournalRevision) && (value.expectedOldJournalRevision as number) >= 0))
221
+ || !Number.isSafeInteger(value.revision) || (value.revision as number) <= 0
222
+ || typeof value.target.branch !== 'string' || value.target.branch.length === 0
223
+ || typeof value.target.project !== 'string' || value.target.project.length === 0
224
+ || typeof value.target.controlRoot !== 'string' || path.isAbsolute(value.target.controlRoot) === false
225
+ || typeof value.target.targetDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(value.target.targetDigest)
226
+ || typeof value.target.permitDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(value.target.permitDigest)
227
+ || typeof value.target.removeWorktree !== 'boolean'
228
+ || typeof value.target.removeBranch !== 'boolean'
229
+ || typeof value.target.removeShare !== 'boolean'
230
+ || typeof value.target.sharePath !== 'string' || !path.isAbsolute(value.target.sharePath)
231
+ || !Array.isArray(value.target.worktreePaths) || value.target.worktreePaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
232
+ || !Array.isArray(value.target.workspaceViewPaths) || value.target.workspaceViewPaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
233
+ || !Array.isArray(value.target.toolPaths) || value.target.toolPaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
234
+ || !Array.isArray(value.target.shellPaths) || value.target.shellPaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
235
+ || !Array.isArray(value.target.permits)
236
+ || value.target.permits.some((permit) => !isRemovalPermitCommit(permit))
237
+ || !Number.isSafeInteger(value.recordedAt)) {
238
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
239
+ }
240
+ const expected = value.expectedOldJournalRevision as number | null;
241
+ const revision = value.revision as number;
242
+ if ((expected === null && revision !== 1) || (expected !== null && revision !== expected + 1)) {
243
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
244
+ }
245
+ return {
246
+ version: 2,
247
+ operation: 'sandbox-rm',
248
+ oldOperation: 'sandbox-rm',
249
+ newOperation: 'sandbox-rm',
250
+ handoffId: value.handoffId as string,
251
+ transitionId: value.transitionId as string,
252
+ engine: value.engine as string,
253
+ containerId: value.containerId as string,
254
+ generation: value.generation as string,
255
+ authorityFingerprint: value.authorityFingerprint as string,
256
+ carrierIdentityDigest: value.carrierIdentityDigest as string,
257
+ lockDomain: value.lockDomain as string,
258
+ owner: {
259
+ pid: value.owner.pid as number,
260
+ startTime: value.owner.startTime as number,
261
+ leaseNonce: value.owner.leaseNonce as string
262
+ },
263
+ phase: value.phase as SandboxRemovalJournalPhase,
264
+ expectedOldJournalRevision: expected,
265
+ revision,
266
+ target: {
267
+ branch: value.target.branch as string,
268
+ project: value.target.project as string,
269
+ controlRoot: value.target.controlRoot as string,
270
+ targetDigest: value.target.targetDigest as string,
271
+ permitDigest: value.target.permitDigest as string,
272
+ removeWorktree: value.target.removeWorktree as boolean,
273
+ removeBranch: value.target.removeBranch as boolean,
274
+ removeShare: value.target.removeShare as boolean,
275
+ worktreePaths: value.target.worktreePaths as string[],
276
+ workspaceViewPaths: value.target.workspaceViewPaths as string[],
277
+ toolPaths: value.target.toolPaths as string[],
278
+ shellPaths: value.target.shellPaths as string[],
279
+ sharePath: value.target.sharePath as string,
280
+ permits: value.target.permits as SandboxRemovalTargetCommit['permits']
281
+ },
282
+ recordedAt: value.recordedAt as number
283
+ };
284
+ }
285
+
286
+ function readRemovalJournalAt(target: string): SandboxRemovalJournal | null {
287
+ if (!fs.existsSync(target)) return null;
288
+ const stat = fs.lstatSync(target);
289
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
290
+ return parseRemovalJournal(fs.readFileSync(target, 'utf8'));
291
+ }
292
+
293
+ export function readSandboxRemovalJournal(manifest: SandboxControlManifest): SandboxRemovalJournal | null {
294
+ return readRemovalJournalAt(removalJournalPath(manifest));
295
+ }
296
+
297
+ export function listSandboxRemovalJournals(
298
+ filter: Readonly<{ branch?: string; project?: string; targetDigest?: string }> = {}
299
+ ): SandboxRemovalJournal[] {
300
+ const namespace = resolveSandboxLockNamespace('sandbox-removal-journal');
301
+ const root = path.join(path.dirname(namespace.lockRoot), REMOVAL_JOURNAL_ROOT.split(path.sep).at(-1)!);
302
+ if (!fs.existsSync(root)) return [];
303
+ assertPrivateDirectory(root);
304
+ const journals: SandboxRemovalJournal[] = [];
305
+ for (const domainEntry of fs.readdirSync(root, { withFileTypes: true })) {
306
+ if (!domainEntry.isDirectory() || domainEntry.isSymbolicLink()) {
307
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
308
+ }
309
+ const domain = path.join(root, domainEntry.name);
310
+ assertPrivateDirectory(domain);
311
+ for (const entry of fs.readdirSync(domain, { withFileTypes: true })) {
312
+ if (!entry.isFile() || entry.isSymbolicLink() || !entry.name.endsWith('.json')) {
313
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
314
+ }
315
+ const record = readRemovalJournalAt(path.join(domain, entry.name));
316
+ if (!record || record.lockDomain !== domainEntry.name
317
+ || `${record.carrierIdentityDigest}.json` !== entry.name) {
318
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
319
+ }
320
+ if ((!filter.branch || record.target.branch === filter.branch)
321
+ && (!filter.project || record.target.project === filter.project)
322
+ && (!filter.targetDigest || record.target.targetDigest === filter.targetDigest)) {
323
+ journals.push(record);
324
+ }
325
+ }
326
+ }
327
+ return journals;
328
+ }
329
+
330
+ export function clearSandboxRemovalJournalRecord(record: SandboxRemovalJournal): void {
331
+ const target = removalJournalPathForRecord(record);
332
+ if (fs.existsSync(target)) {
333
+ const current = readRemovalJournalAt(target);
334
+ if (!current || current.revision !== record.revision || current.handoffId !== record.handoffId) {
335
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
336
+ }
337
+ fs.rmSync(target, { force: true });
338
+ }
339
+ }
340
+
341
+ export function clearSandboxRemovalJournal(manifest: SandboxControlManifest): void {
342
+ const target = removalJournalPath(manifest);
343
+ if (fs.existsSync(target)) fs.rmSync(target, { force: true });
344
+ }
345
+
346
+ type RemovalJournalCursor = Readonly<{
347
+ record: SandboxRemovalJournal;
348
+ write(phase: SandboxRemovalJournalPhase): SandboxRemovalJournal;
349
+ }>;
350
+
351
+ function defaultRemovalTarget(manifest: SandboxControlManifest): SandboxRemovalTargetCommit {
352
+ const targetDigest = createHash('sha256')
353
+ .update(`${manifest.project}\0${manifest.branch}\0${manifest.container}\0${manifest.channelDir}`)
354
+ .digest('hex');
355
+ return {
356
+ branch: manifest.branch,
357
+ project: manifest.project,
358
+ controlRoot: path.dirname(manifest.channelDir),
359
+ targetDigest,
360
+ permitDigest: createHash('sha256').update('none').digest('hex'),
361
+ removeWorktree: false,
362
+ removeBranch: false,
363
+ removeShare: false,
364
+ worktreePaths: [],
365
+ workspaceViewPaths: [],
366
+ toolPaths: [],
367
+ shellPaths: [],
368
+ sharePath: path.join(path.dirname(manifest.channelDir), 'share'),
369
+ permits: []
370
+ };
371
+ }
372
+
373
+ function sameRemovalTarget(left: SandboxRemovalTargetCommit, right: SandboxRemovalTargetCommit): boolean {
374
+ return JSON.stringify(left) === JSON.stringify(right);
375
+ }
376
+
377
+ function phaseIndex(phase: SandboxRemovalJournalPhase): number {
378
+ return SANDBOX_REMOVAL_JOURNAL_PHASES.indexOf(phase);
379
+ }
380
+
381
+ export function sandboxRemovalPhaseIndex(phase: SandboxRemovalJournalPhase): number {
382
+ return phaseIndex(phase);
383
+ }
384
+
385
+ function assertNextRemovalPhase(current: SandboxRemovalJournalPhase, next: SandboxRemovalJournalPhase): boolean {
386
+ const currentIndex = phaseIndex(current);
387
+ const nextIndex = phaseIndex(next);
388
+ if (nextIndex === currentIndex) return false;
389
+ if (current === 'target-committed' && next === 'container-absent') return true;
390
+ if (nextIndex !== currentIndex + 1) {
391
+ throw new Error('SANDBOX_CONTROL_REMOVAL_PHASE_TRANSITION_INVALID');
392
+ }
393
+ return true;
394
+ }
395
+
396
+ function assertRemovalJournalLock(
397
+ record: SandboxRemovalJournal,
398
+ resourceLock: SandboxResourceLock
399
+ ): void {
400
+ const namespace = resolveSandboxLockNamespace(`${record.engine}:${record.containerId}`, {
401
+ lockDomain: record.lockDomain
402
+ });
403
+ if (resourceLock.lockDomain !== record.lockDomain || resourceLock.path !== namespace.lockPath) {
404
+ throw new Error('SANDBOX_CONTROL_REMOVAL_LOCK_MISMATCH');
405
+ }
406
+ }
407
+
408
+ function writeRemovalJournalAt(
409
+ target: string,
410
+ base: Omit<SandboxRemovalJournal, 'phase' | 'expectedOldJournalRevision' | 'revision' | 'recordedAt'>,
411
+ phase: SandboxRemovalJournalPhase,
412
+ expectedOldJournalRevision: number | null
413
+ ): SandboxRemovalJournal {
414
+ const existing = readRemovalJournalAt(target);
415
+ if (expectedOldJournalRevision === null
416
+ ? existing !== null
417
+ : existing?.revision !== expectedOldJournalRevision) {
418
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
419
+ }
420
+ const record: SandboxRemovalJournal = {
421
+ ...base,
422
+ phase,
423
+ expectedOldJournalRevision,
424
+ revision: expectedOldJournalRevision === null ? 1 : expectedOldJournalRevision + 1,
425
+ recordedAt: Date.now()
426
+ };
427
+ const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
428
+ try {
429
+ fs.writeFileSync(temporary, `${JSON.stringify(record)}\n`, { mode: 0o600, flag: 'wx' });
430
+ if (expectedOldJournalRevision === null && fs.existsSync(target)) {
431
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
432
+ }
433
+ fs.renameSync(temporary, target);
434
+ } finally {
435
+ fs.rmSync(temporary, { force: true });
436
+ }
437
+ return record;
438
+ }
439
+
440
+ function writeRemovalJournal(
441
+ manifest: SandboxControlManifest,
442
+ base: Omit<SandboxRemovalJournal, 'phase' | 'expectedOldJournalRevision' | 'revision' | 'recordedAt'>,
443
+ phase: SandboxRemovalJournalPhase,
444
+ expectedOldJournalRevision: number | null
445
+ ): SandboxRemovalJournal {
446
+ return writeRemovalJournalAt(removalJournalPath(manifest), base, phase, expectedOldJournalRevision);
447
+ }
448
+
449
+ function startRemovalJournal(
450
+ manifest: SandboxControlManifest,
451
+ options: Readonly<{
452
+ target?: SandboxRemovalTargetCommit;
453
+ identityProbe: ProcessIdentityProbe;
454
+ }>
455
+ ): RemovalJournalCursor {
456
+ const target = removalJournalPath(manifest);
457
+ const existing = readRemovalJournalAt(target);
458
+ if (existing && (existing.engine !== manifest.engine || existing.containerId !== manifest.containerIdentity.id
459
+ || existing.generation !== manifest.generation
460
+ || existing.authorityFingerprint !== manifest.authorityEvidence.authorityFingerprint
461
+ || existing.lockDomain !== manifest.authorityEvidence.lockDomain)) {
462
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_IDENTITY_MISMATCH');
463
+ }
464
+ const targetCommit = options.target ?? existing?.target ?? defaultRemovalTarget(manifest);
465
+ if (existing && options.target && !sameRemovalTarget(existing.target, options.target)) {
466
+ throw new Error('SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH');
467
+ }
468
+ if (existing) {
469
+ const state = options.identityProbe(existing.owner);
470
+ if (state === 'alive') throw new Error('SANDBOX_CONTROL_REMOVE_RETRY_IN_PROGRESS');
471
+ if (state === 'unknown') throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNKNOWN');
472
+ }
473
+ const startTime = getProcessStartTime(process.pid);
474
+ if (startTime === null) throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNAVAILABLE');
475
+ const base = {
476
+ version: 2 as const,
477
+ operation: 'sandbox-rm' as const,
478
+ oldOperation: 'sandbox-rm' as const,
479
+ newOperation: 'sandbox-rm' as const,
480
+ handoffId: randomUUID(),
481
+ transitionId: randomUUID(),
482
+ engine: manifest.engine,
483
+ containerId: manifest.containerIdentity.id,
484
+ generation: manifest.generation,
485
+ authorityFingerprint: manifest.authorityEvidence.authorityFingerprint,
486
+ carrierIdentityDigest: removalCarrierDigest(manifest.engine, manifest.containerIdentity.id, manifest.generation),
487
+ lockDomain: manifest.authorityEvidence.lockDomain,
488
+ owner: { pid: process.pid, startTime, leaseNonce: randomUUID() },
489
+ target: targetCommit
490
+ };
491
+ const initialPhase = existing?.phase ?? 'prepared';
492
+ let record = writeRemovalJournal(manifest, base, initialPhase, existing?.revision ?? null);
493
+ return {
494
+ get record() { return record; },
495
+ write(phase) {
496
+ if (!assertNextRemovalPhase(record.phase, phase)) return record;
497
+ record = writeRemovalJournal(manifest, base, phase, record.revision);
498
+ return record;
499
+ }
500
+ };
501
+ }
502
+
503
+ export function claimSandboxRemovalJournal(
504
+ journal: SandboxRemovalJournal,
505
+ options: Readonly<{ identityProbe?: ProcessIdentityProbe; resourceLock?: SandboxResourceLock }> = {}
506
+ ): SandboxRemovalJournal {
507
+ const target = removalJournalPathForRecord(journal);
508
+ const resourceLock = options.resourceLock
509
+ ?? acquireSandboxResourceLock(`${journal.engine}:${journal.containerId}`, { lockDomain: journal.lockDomain });
510
+ const ownedLock = options.resourceLock ? null : resourceLock;
511
+ try {
512
+ assertRemovalJournalLock(journal, resourceLock);
513
+ const current = readRemovalJournalAt(target);
514
+ if (!current || current.revision !== journal.revision || current.handoffId !== journal.handoffId) {
515
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
516
+ }
517
+ const state = (options.identityProbe ?? getProcessIdentityState)(current.owner);
518
+ if (state === 'alive') throw new Error('SANDBOX_CONTROL_REMOVE_RETRY_IN_PROGRESS');
519
+ if (state === 'unknown') throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNKNOWN');
520
+ const startTime = getProcessStartTime(process.pid);
521
+ if (startTime === null) throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNAVAILABLE');
522
+ const base = {
523
+ ...current,
524
+ handoffId: randomUUID(),
525
+ transitionId: randomUUID(),
526
+ owner: { pid: process.pid, startTime, leaseNonce: randomUUID() }
527
+ };
528
+ return writeRemovalJournalAt(target, base, current.phase, current.revision);
529
+ } finally {
530
+ ownedLock?.release();
531
+ }
532
+ }
533
+
534
+ export function advanceSandboxRemovalJournalPhase(
535
+ journal: SandboxRemovalJournal,
536
+ phase: SandboxRemovalJournalPhase,
537
+ options: Readonly<{ resourceLock?: SandboxResourceLock }> = {}
538
+ ): SandboxRemovalJournal {
539
+ const target = removalJournalPathForRecord(journal);
540
+ const resourceLock = options.resourceLock
541
+ ?? acquireSandboxResourceLock(`${journal.engine}:${journal.containerId}`, { lockDomain: journal.lockDomain });
542
+ const ownedLock = options.resourceLock ? null : resourceLock;
543
+ try {
544
+ assertRemovalJournalLock(journal, resourceLock);
545
+ const current = readRemovalJournalAt(target);
546
+ if (!current || current.revision !== journal.revision || current.handoffId !== journal.handoffId) {
547
+ throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
548
+ }
549
+ if (!assertNextRemovalPhase(current.phase, phase)) return current;
550
+ const {
551
+ phase: _phase,
552
+ expectedOldJournalRevision: _expected,
553
+ revision: _revision,
554
+ recordedAt: _recordedAt,
555
+ ...base
556
+ } = current;
557
+ return writeRemovalJournalAt(target, base, phase, current.revision);
558
+ } finally {
559
+ ownedLock?.release();
560
+ }
561
+ }
33
562
 
34
563
  async function awaitWithDeadline<T>(
35
564
  operation: () => Promise<T>,
@@ -82,6 +611,30 @@ function markSandboxControlRootQuiescing(root: string): void {
82
611
  }
83
612
  }
84
613
 
614
+ function recordSandboxRemovalPending(
615
+ root: string,
616
+ manifest: SandboxControlManifest,
617
+ phase: 'container-removal' | 'container-verification',
618
+ journal: RemovalJournalCursor
619
+ ): void {
620
+ const pendingPath = path.join(root, 'removal-pending.json');
621
+ const temporary = `${pendingPath}.${process.pid}.${randomUUID()}.tmp`;
622
+ try {
623
+ fs.writeFileSync(temporary, `${JSON.stringify({
624
+ version: 1,
625
+ phase,
626
+ containerId: manifest.containerIdentity.id,
627
+ generation: manifest.generation,
628
+ authorityFingerprint: manifest.authorityEvidence.authorityFingerprint,
629
+ recordedAt: Date.now()
630
+ })}\n`, { mode: 0o600, flag: 'wx' });
631
+ fs.renameSync(temporary, pendingPath);
632
+ } finally {
633
+ fs.rmSync(temporary, { force: true });
634
+ }
635
+ journal.write(phase === 'container-verification' ? 'container-absent' : 'container-removal');
636
+ }
637
+
85
638
  function readStartupOwner(filePath: string): { raw: string; owner: OwnerIdentity } | null {
86
639
  if (!fs.existsSync(filePath)) return null;
87
640
  if (!regularFile(filePath)) throw new Error('SANDBOX_CONTROL_BROKER_START_TRANSITION');
@@ -498,9 +1051,10 @@ function readSandboxControlManifestValue(manifestPath: string): SandboxControlMa
498
1051
  const expectedKeys = [
499
1052
  'branch', 'channelDir', 'container', 'containerIdentity', 'engine', 'generation',
500
1053
  'mode', 'processingDir', 'project', 'publicStatusDir', 'repoRoot', 'runtimeDir',
501
- 'taskId', 'token', 'worktreeRoot'
1054
+ 'taskId', 'token', 'worktreeRoot', 'authorityEvidence'
502
1055
  ];
503
- if (Object.keys(manifest).sort().join(',') !== expectedKeys.sort().join(',')) {
1056
+ const actualKeys = Object.keys(manifest).sort().join(',');
1057
+ if (actualKeys !== expectedKeys.sort().join(',')) {
504
1058
  throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
505
1059
  }
506
1060
  if (typeof candidate.containerIdentity !== 'object' || candidate.containerIdentity === null
@@ -522,7 +1076,8 @@ function readSandboxControlManifestValue(manifestPath: string): SandboxControlMa
522
1076
  || typeof candidate.token !== 'string'
523
1077
  || typeof candidate.generation !== 'string'
524
1078
  || (candidate.mode === 'task-bound' && (typeof candidate.taskId !== 'string' || candidate.taskId.length === 0))
525
- || (candidate.mode === 'branch-only' && candidate.taskId !== null)) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
1079
+ || (candidate.mode === 'branch-only' && candidate.taskId !== null)
1080
+ || !isSandboxAuthorityEvidence(candidate.authorityEvidence)) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
526
1081
  const root = path.dirname(path.resolve(manifestPath));
527
1082
  if (path.resolve(candidate.channelDir) !== path.join(root, 'channel')
528
1083
  || path.resolve(candidate.publicStatusDir) !== path.join(root, 'public')
@@ -542,6 +1097,7 @@ function readSandboxControlManifestValue(manifestPath: string): SandboxControlMa
542
1097
  id: containerIdentity.id as string,
543
1098
  labels: { ...(containerIdentity.labels as Record<string, string>) }
544
1099
  },
1100
+ authorityEvidence: candidate.authorityEvidence,
545
1101
  branch: candidate.branch,
546
1102
  mode: candidate.mode,
547
1103
  taskId: candidate.taskId,
@@ -816,6 +1372,9 @@ export type RemoveSandboxControlOptions = Readonly<{
816
1372
  removeContainer: (timeoutMs: number) => Promise<void>;
817
1373
  requireAbsent?: boolean;
818
1374
  selfOwner?: BrokerOwner;
1375
+ resourceLock?: SandboxResourceLock;
1376
+ retainRemovalJournal?: boolean;
1377
+ removalTarget?: SandboxRemovalTargetCommit;
819
1378
  }>;
820
1379
 
821
1380
  export async function removeSandboxControlRoot(
@@ -827,7 +1386,16 @@ export async function removeSandboxControlRoot(
827
1386
  const stat = fs.lstatSync(resolvedRoot);
828
1387
  if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
829
1388
  const manifestPath = path.join(resolvedRoot, 'manifest.json');
1389
+ const manifestRawBeforeLock = fs.readFileSync(manifestPath, 'utf8');
830
1390
  const manifest = readSandboxControlManifest(manifestPath);
1391
+ const resourceLock = options.resourceLock ?? acquireSandboxResourceLock(
1392
+ `${manifest.engine}:${manifest.containerIdentity.id}`,
1393
+ { lockDomain: manifest.authorityEvidence.lockDomain }
1394
+ );
1395
+ try {
1396
+ if (fs.readFileSync(manifestPath, 'utf8') !== manifestRawBeforeLock) {
1397
+ throw new Error('SANDBOX_CONTROL_MANIFEST_CHANGED');
1398
+ }
831
1399
  const inspectContainer = options.inspectContainer
832
1400
  ?? ((timeoutMs) => inspectSandboxControlContainer(manifest, { timeoutMs }));
833
1401
  const timeoutMs = options.timeoutMs ?? options.timing?.quiesceDeadlineMs ?? DEFAULT_QUIESCE_TIMEOUT_MS;
@@ -835,10 +1403,30 @@ export async function removeSandboxControlRoot(
835
1403
  const deadlineAt = Date.now() + timeoutMs;
836
1404
  const forceAt = forceDeadline(deadlineAt, timeoutMs);
837
1405
  const remaining = (): number => Math.max(0, deadlineAt - Date.now());
1406
+ const journal = startRemovalJournal(manifest, {
1407
+ target: options.removalTarget,
1408
+ identityProbe
1409
+ });
1410
+ if (journal.record.phase === 'carrier-finalizing') {
1411
+ if (fs.readFileSync(manifestPath, 'utf8') !== manifestRawBeforeLock
1412
+ || !isSandboxControlRootQuiescing(resolvedRoot)) {
1413
+ throw new Error('SANDBOX_CONTROL_MANIFEST_CHANGED');
1414
+ }
1415
+ fs.rmSync(resolvedRoot, { recursive: true, force: true });
1416
+ if (fs.existsSync(resolvedRoot)) throw new Error('SANDBOX_CONTROL_TARGET_MISMATCH');
1417
+ journal.write('carrier-removed');
1418
+ if (!options.retainRemovalJournal) clearSandboxRemovalJournal(manifest);
1419
+ return;
1420
+ }
1421
+ if (sandboxRemovalPhaseIndex(journal.record.phase) >= sandboxRemovalPhaseIndex('carrier-removed')) {
1422
+ throw new Error('SANDBOX_CONTROL_TARGET_MISMATCH');
1423
+ }
1424
+ if (journal.record.phase === 'prepared') journal.write('target-committed');
838
1425
  const observation = await awaitWithDeadline(
839
1426
  () => inspectContainer(remaining()), deadlineAt, 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED'
840
1427
  );
841
1428
  if (observation.state === 'unknown') throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${observation.reason}`);
1429
+ journal.write(observation.state === 'absent' ? 'container-absent' : 'container-removal');
842
1430
  if (options.requireAbsent && observation.state !== 'absent') {
843
1431
  throw new Error('SANDBOX_CONTROL_CONTAINER_REAPPEARED');
844
1432
  }
@@ -880,28 +1468,46 @@ export async function removeSandboxControlRoot(
880
1468
  }
881
1469
 
882
1470
  if (observation.state === 'found') {
883
- if (Date.now() >= deadlineAt) throw new Error('SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED');
884
- await awaitWithDeadline(
885
- () => options.removeContainer(remaining()),
886
- deadlineAt,
887
- 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED'
888
- );
889
- const afterRemoval = await awaitWithDeadline(
890
- () => inspectContainer(remaining()),
891
- deadlineAt,
892
- 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED'
893
- );
1471
+ if (Date.now() >= deadlineAt) {
1472
+ recordSandboxRemovalPending(resolvedRoot, manifest, 'container-removal', journal);
1473
+ throw new Error('SANDBOX_CONTROL_REMOVE_PENDING');
1474
+ }
1475
+ let afterRemoval: ContainerObservation;
1476
+ try {
1477
+ await awaitWithDeadline(
1478
+ () => options.removeContainer(remaining()),
1479
+ deadlineAt,
1480
+ 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED'
1481
+ );
1482
+ afterRemoval = await awaitWithDeadline(
1483
+ () => inspectContainer(remaining()),
1484
+ deadlineAt,
1485
+ 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED'
1486
+ );
1487
+ } catch (error) {
1488
+ if (error instanceof Error && error.message === 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED') {
1489
+ recordSandboxRemovalPending(resolvedRoot, manifest, 'container-removal', journal);
1490
+ throw new Error('SANDBOX_CONTROL_REMOVE_PENDING');
1491
+ }
1492
+ throw error;
1493
+ }
894
1494
  if (afterRemoval.state !== 'absent') {
895
1495
  throw new Error(afterRemoval.state === 'unknown'
896
1496
  ? `SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${afterRemoval.reason}`
897
1497
  : 'SANDBOX_CONTROL_CONTAINER_STILL_EXISTS');
898
1498
  }
1499
+ journal.write('container-absent');
899
1500
  }
900
1501
 
1502
+ const currentManifestRaw = fs.readFileSync(manifestPath, 'utf8');
901
1503
  const currentManifest = readSandboxControlManifest(manifestPath);
902
- if (currentManifest.token !== manifest.token || currentManifest.generation !== manifest.generation
1504
+ if (currentManifestRaw !== manifestRawBeforeLock
1505
+ || currentManifest.engine !== manifest.engine
1506
+ || currentManifest.containerIdentity.id !== manifest.containerIdentity.id
1507
+ || currentManifest.authorityEvidence.lockDomain !== manifest.authorityEvidence.lockDomain
1508
+ || currentManifest.token !== manifest.token || currentManifest.generation !== manifest.generation
903
1509
  || !isSandboxControlRootQuiescing(resolvedRoot)) {
904
- throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
1510
+ throw new Error('SANDBOX_CONTROL_MANIFEST_CHANGED');
905
1511
  }
906
1512
  const currentBroker = readBrokerOwner(brokerPath);
907
1513
  if (selfOwned && (!currentBroker || !selfOwner || !sameOwner(selfOwner, currentBroker)
@@ -970,7 +1576,13 @@ export async function removeSandboxControlRoot(
970
1576
  if (fs.existsSync(brokerPath) || fs.existsSync(statusPath)) {
971
1577
  throw new Error('SANDBOX_CONTROL_OWNER_EVIDENCE_REMAINS');
972
1578
  }
1579
+ journal.write('carrier-finalizing');
973
1580
  fs.rmSync(resolvedRoot, { recursive: true, force: true });
1581
+ journal.write('carrier-removed');
1582
+ if (!options.retainRemovalJournal) clearSandboxRemovalJournal(manifest);
1583
+ } finally {
1584
+ if (!options.resourceLock) resourceLock.release();
1585
+ }
974
1586
  }
975
1587
 
976
1588
  export type GarbageCollectSandboxControlOptions = Omit<RemoveSandboxControlOptions, 'removeContainer' | 'requireAbsent'>;