@fitlab-ai/agent-infra 0.12.0 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/README.md +0 -2
  2. package/README.zh-CN.md +0 -2
  3. package/dist/bin/internal-cli.js +2 -17
  4. package/dist/lib/agent-clients/adapter.js +0 -13
  5. package/dist/lib/agent-clients/adapters/antigravity-cli.js +0 -5
  6. package/dist/lib/agent-clients/adapters/claude-code-credentials.js +0 -17
  7. package/dist/lib/agent-clients/adapters/claude-code.js +0 -9
  8. package/dist/lib/agent-clients/adapters/codex.js +0 -52
  9. package/dist/lib/agent-clients/adapters/opencode.js +0 -5
  10. package/dist/lib/agent-clients/adapters/traecli.js +0 -5
  11. package/dist/lib/agent-clients/config.js +19 -54
  12. package/dist/lib/agent-clients/registry.js +1 -12
  13. package/dist/lib/agent-clients/schema.js +0 -18
  14. package/dist/lib/agent-clients/tokens.js +1 -2
  15. package/dist/lib/agent-clients/types.js +0 -1
  16. package/dist/lib/decide.js +2 -0
  17. package/dist/lib/git/worktree-identity.js +1 -9
  18. package/dist/lib/internal/agent-client.js +2 -62
  19. package/dist/lib/internal/cli-route-inventory.js +1 -5
  20. package/dist/lib/internal/manual-validation.js +29 -27
  21. package/dist/lib/internal/platform-comment.js +56 -7
  22. package/dist/lib/internal/task-create.js +1 -1
  23. package/dist/lib/internal/task-event.js +1 -8
  24. package/dist/lib/internal/task-finalization.js +0 -1
  25. package/dist/lib/internal/task-lifecycle.js +2 -2
  26. package/dist/lib/internal/task-operation-registry.js +4 -20
  27. package/dist/lib/internal/task-review.js +4 -4
  28. package/dist/lib/platform/comment-safety.js +1 -7
  29. package/dist/lib/platform/in-label-sync.js +1 -14
  30. package/dist/lib/platform/issue-comments.js +373 -282
  31. package/dist/lib/platform/issue-summary-content.js +112 -0
  32. package/dist/lib/platform/issues.js +1 -1
  33. package/dist/lib/platform/operation-coordinator.js +4 -2
  34. package/dist/lib/platform/pr-change-report.js +176 -30
  35. package/dist/lib/platform/pr-checks.js +2 -8
  36. package/dist/lib/platform/pr-summary-content.js +54 -0
  37. package/dist/lib/platform/pr-summary.js +318 -284
  38. package/dist/lib/platform/provider-validation.js +0 -20
  39. package/dist/lib/platform/pull-requests.js +73 -84
  40. package/dist/lib/platform/releases.js +1 -43
  41. package/dist/lib/platform/summary-content-pipeline.js +18 -0
  42. package/dist/lib/platform/verification-required.js +0 -2
  43. package/dist/lib/platform/verification-sync.js +1 -79
  44. package/dist/lib/process-data/normalize.js +1 -185
  45. package/dist/lib/process-data/privacy.js +1 -16
  46. package/dist/lib/process-data/sources.js +2 -279
  47. package/dist/lib/render.js +0 -12
  48. package/dist/lib/run/index.js +15 -10
  49. package/dist/lib/run/skills.js +0 -3
  50. package/dist/lib/sandbox/commands/create.js +40 -53
  51. package/dist/lib/sandbox/commands/list-running.js +0 -18
  52. package/dist/lib/sandbox/commands/start.js +7 -8
  53. package/dist/lib/sandbox/constants.js +0 -6
  54. package/dist/lib/sandbox/control/client.js +0 -1
  55. package/dist/lib/sandbox/control/container-identity.js +1 -16
  56. package/dist/lib/sandbox/control/executor.js +0 -4
  57. package/dist/lib/sandbox/control/identity-sentinel.js +0 -7
  58. package/dist/lib/sandbox/control/lifecycle.js +21 -37
  59. package/dist/lib/sandbox/control/native-file-lock.js +0 -3
  60. package/dist/lib/sandbox/control/server.js +18 -38
  61. package/dist/lib/sandbox/control/state.js +1 -9
  62. package/dist/lib/sandbox/dockerfile.js +0 -6
  63. package/dist/lib/sandbox/engine.js +0 -14
  64. package/dist/lib/sandbox/engines/docker-exact-discovery.js +1 -4
  65. package/dist/lib/sandbox/engines/wsl2-paths.js +0 -3
  66. package/dist/lib/sandbox/recovery.js +24 -26
  67. package/dist/lib/sandbox/removal.js +67 -518
  68. package/dist/lib/sandbox/task-resolver.js +0 -41
  69. package/dist/lib/sandbox/tools.js +1 -4
  70. package/dist/lib/sandbox/workspace-view.js +1 -2
  71. package/dist/lib/server/display.js +0 -6
  72. package/dist/lib/task/activity-intent.js +45 -40
  73. package/dist/lib/task/activity-log.js +9 -16
  74. package/dist/lib/task/artifact-command.js +1 -1
  75. package/dist/lib/task/artifact-lifecycle.js +61 -63
  76. package/dist/lib/task/artifact-name.js +0 -6
  77. package/dist/lib/task/artifact-operations.js +3 -3
  78. package/dist/lib/task/capabilities.js +4 -5
  79. package/dist/lib/task/commands/log.js +0 -6
  80. package/dist/lib/task/commands/status.js +17 -60
  81. package/dist/lib/task/commit-operation.js +28 -39
  82. package/dist/lib/task/completion-facts.js +56 -43
  83. package/dist/lib/task/control-authority.js +23 -256
  84. package/dist/lib/task/control-recovery.js +3 -5
  85. package/dist/lib/task/create.js +2 -4
  86. package/dist/lib/task/decision-intents.js +8 -9
  87. package/dist/lib/task/delivery-target.js +1 -40
  88. package/dist/lib/task/events.js +84 -107
  89. package/dist/lib/task/finalization.js +60 -15
  90. package/dist/lib/task/ledger-intents.js +1 -1
  91. package/dist/lib/task/lifecycle-execution.js +38 -33
  92. package/dist/lib/task/lifecycle.js +49 -41
  93. package/dist/lib/task/local-artifact-finalization.js +1 -4
  94. package/dist/lib/task/manual-validation-evidence.js +1 -37
  95. package/dist/lib/task/platform-operation-recovery.js +151 -108
  96. package/dist/lib/task/pr-delivery-fact.js +2 -5
  97. package/dist/lib/task/qualification-confirmation.js +5 -7
  98. package/dist/lib/task/resolve-ref.js +1 -25
  99. package/dist/lib/task/review-command.js +2 -2
  100. package/dist/lib/task/review-finalization.js +1 -21
  101. package/dist/lib/task/review-fingerprint.js +0 -4
  102. package/dist/lib/task/secure-fs.js +0 -56
  103. package/dist/lib/task/short-id.js +1 -39
  104. package/dist/lib/task/skill-scope.js +0 -1
  105. package/dist/lib/task/verification-engine.js +58 -282
  106. package/dist/lib/task/verification.js +6 -3
  107. package/dist/lib/task/write.js +11 -1
  108. package/lib/agent-clients/adapter.ts +0 -76
  109. package/lib/agent-clients/adapters/antigravity-cli.ts +0 -5
  110. package/lib/agent-clients/adapters/claude-code-credentials.ts +0 -19
  111. package/lib/agent-clients/adapters/claude-code.ts +0 -9
  112. package/lib/agent-clients/adapters/codex.ts +0 -56
  113. package/lib/agent-clients/adapters/opencode.ts +0 -5
  114. package/lib/agent-clients/adapters/traecli.ts +0 -5
  115. package/lib/agent-clients/config.ts +20 -61
  116. package/lib/agent-clients/registry.ts +1 -34
  117. package/lib/agent-clients/schema.ts +0 -18
  118. package/lib/agent-clients/tokens.ts +1 -2
  119. package/lib/agent-clients/types.ts +0 -15
  120. package/lib/decide.ts +1 -0
  121. package/lib/git/worktree-identity.ts +0 -10
  122. package/lib/internal/agent-client.ts +2 -70
  123. package/lib/internal/cli-route-inventory.ts +1 -4
  124. package/lib/internal/manual-validation.ts +28 -27
  125. package/lib/internal/platform-comment.ts +46 -4
  126. package/lib/internal/task-create.ts +0 -1
  127. package/lib/internal/task-event.ts +1 -5
  128. package/lib/internal/task-finalization.ts +0 -1
  129. package/lib/internal/task-lifecycle.ts +3 -5
  130. package/lib/internal/task-operation-registry.ts +3 -19
  131. package/lib/internal/task-review.ts +4 -4
  132. package/lib/platform/comment-safety.ts +0 -10
  133. package/lib/platform/github-provider.ts +0 -2
  134. package/lib/platform/in-label-sync.ts +0 -16
  135. package/lib/platform/issue-comments.ts +478 -301
  136. package/lib/platform/issue-summary-content.ts +132 -0
  137. package/lib/platform/issues.ts +0 -2
  138. package/lib/platform/operation-coordinator.ts +4 -2
  139. package/lib/platform/pr-change-report.ts +165 -25
  140. package/lib/platform/pr-checks.ts +1 -10
  141. package/lib/platform/pr-review.ts +1 -1
  142. package/lib/platform/pr-summary-content.ts +60 -0
  143. package/lib/platform/pr-summary.ts +364 -257
  144. package/lib/platform/provider-bridge.ts +0 -1
  145. package/lib/platform/provider-loader.ts +1 -5
  146. package/lib/platform/provider-validation.ts +1 -49
  147. package/lib/platform/pull-requests.ts +64 -99
  148. package/lib/platform/releases.ts +1 -40
  149. package/lib/platform/summary-content-pipeline.ts +23 -0
  150. package/lib/platform/verification-required.ts +0 -2
  151. package/lib/platform/verification-sync.ts +0 -94
  152. package/lib/process-data/normalize.ts +1 -186
  153. package/lib/process-data/privacy.ts +2 -19
  154. package/lib/process-data/sources.ts +3 -283
  155. package/lib/render.ts +0 -15
  156. package/lib/run/index.ts +13 -7
  157. package/lib/run/skills.ts +0 -4
  158. package/lib/sandbox/commands/create.ts +37 -74
  159. package/lib/sandbox/commands/list-running.ts +0 -19
  160. package/lib/sandbox/commands/start.ts +7 -10
  161. package/lib/sandbox/constants.ts +0 -8
  162. package/lib/sandbox/control/client.ts +0 -1
  163. package/lib/sandbox/control/container-identity.ts +1 -16
  164. package/lib/sandbox/control/executor.ts +1 -5
  165. package/lib/sandbox/control/identity-sentinel.ts +0 -8
  166. package/lib/sandbox/control/lifecycle.ts +18 -51
  167. package/lib/sandbox/control/native-file-lock.ts +0 -8
  168. package/lib/sandbox/control/protocol.ts +0 -2
  169. package/lib/sandbox/control/server.ts +13 -39
  170. package/lib/sandbox/control/state.ts +1 -10
  171. package/lib/sandbox/dockerfile.ts +0 -11
  172. package/lib/sandbox/engine.ts +0 -17
  173. package/lib/sandbox/engines/docker-exact-discovery.ts +1 -5
  174. package/lib/sandbox/engines/wsl2-paths.ts +0 -4
  175. package/lib/sandbox/recovery.ts +24 -26
  176. package/lib/sandbox/removal.ts +58 -687
  177. package/lib/sandbox/shell.ts +1 -1
  178. package/lib/sandbox/task-resolver.ts +0 -48
  179. package/lib/sandbox/tools.ts +1 -5
  180. package/lib/sandbox/workspace-view.ts +2 -4
  181. package/lib/server/display.ts +0 -14
  182. package/lib/server/process-control.ts +1 -1
  183. package/lib/task/activity-intent.ts +36 -44
  184. package/lib/task/activity-log.ts +9 -22
  185. package/lib/task/artifact-command.ts +1 -1
  186. package/lib/task/artifact-lifecycle.ts +58 -67
  187. package/lib/task/artifact-name.ts +0 -7
  188. package/lib/task/artifact-operations.ts +3 -3
  189. package/lib/task/capabilities.ts +5 -6
  190. package/lib/task/commands/log.ts +0 -7
  191. package/lib/task/commands/status.ts +14 -79
  192. package/lib/task/commit-operation.ts +29 -51
  193. package/lib/task/completion-facts.ts +45 -42
  194. package/lib/task/control-authority.ts +27 -317
  195. package/lib/task/control-recovery.ts +2 -5
  196. package/lib/task/create.ts +2 -5
  197. package/lib/task/decision-intents.ts +5 -5
  198. package/lib/task/delivery-target.ts +0 -40
  199. package/lib/task/events.ts +89 -123
  200. package/lib/task/finalization.ts +52 -15
  201. package/lib/task/ledger-intents.ts +1 -1
  202. package/lib/task/lifecycle-execution.ts +37 -89
  203. package/lib/task/lifecycle.ts +48 -44
  204. package/lib/task/local-artifact-finalization.ts +0 -7
  205. package/lib/task/manual-validation-evidence.ts +1 -47
  206. package/lib/task/platform-operation-recovery.ts +117 -104
  207. package/lib/task/pr-delivery-fact.ts +2 -11
  208. package/lib/task/qualification-confirmation.ts +6 -6
  209. package/lib/task/resolve-ref.ts +0 -23
  210. package/lib/task/review-command.ts +2 -2
  211. package/lib/task/review-finalization.ts +1 -27
  212. package/lib/task/review-fingerprint.ts +0 -8
  213. package/lib/task/secure-fs.ts +0 -42
  214. package/lib/task/short-id.ts +0 -43
  215. package/lib/task/skill-scope.ts +0 -1
  216. package/lib/task/verification-engine.ts +56 -287
  217. package/lib/task/verification.ts +5 -5
  218. package/lib/task/write.ts +12 -1
  219. package/package.json +2 -2
  220. package/templates/.agents/README.en.md +1 -3
  221. package/templates/.agents/README.zh-CN.md +0 -2
  222. package/templates/.agents/rules/README.en.md +0 -1
  223. package/templates/.agents/rules/README.zh-CN.md +0 -1
  224. package/templates/.agents/rules/commit-and-pr.en.md +1 -1
  225. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -1
  226. package/templates/.agents/rules/no-mid-flow-questions.en.md +0 -10
  227. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +0 -10
  228. package/templates/.agents/rules/pr-sync.en.md +4 -2
  229. package/templates/.agents/rules/pr-sync.zh-CN.md +6 -9
  230. package/templates/.agents/rules/task-management.en.md +3 -3
  231. package/templates/.agents/rules/task-management.zh-CN.md +3 -3
  232. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  233. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  234. package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
  235. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  236. package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
  237. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
  238. package/templates/.agents/skills/commit/SKILL.en.md +2 -2
  239. package/templates/.agents/skills/commit/SKILL.zh-CN.md +2 -2
  240. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +6 -6
  241. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +6 -6
  242. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  243. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  244. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +2 -2
  245. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +2 -2
  246. package/templates/.agents/skills/complete-task/SKILL.en.md +10 -25
  247. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -26
  248. package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
  249. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  250. package/templates/.agents/skills/create-pr/reference/change-report.en.md +9 -2
  251. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +9 -2
  252. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
  253. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
  254. package/templates/.agents/skills/create-task/SKILL.en.md +0 -18
  255. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +0 -18
  256. package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
  257. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
  258. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  259. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  260. package/templates/.agents/skills/review-code/SKILL.en.md +5 -5
  261. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
  262. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  263. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  264. package/templates/.agents/skills/review-pr/SKILL.en.md +4 -4
  265. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +4 -4
  266. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  267. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  268. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +3 -75
  269. package/templates/.agents/skills/watch-pr/SKILL.en.md +1 -1
  270. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -1
  271. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +3 -3
  272. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +3 -3
  273. package/templates/.agents/templates/task.en.md +1 -0
  274. package/templates/.agents/templates/task.zh-CN.md +1 -0
  275. package/templates/.claude/settings.json +0 -24
  276. package/templates/.codex/hooks.json +0 -50
  277. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +0 -582
  278. package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +0 -114
  279. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +0 -237
  280. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +0 -443
  281. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +0 -236
  282. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +0 -292
  283. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +0 -267
  284. package/dist/lib/agent-clients/adapters/codex-orchestration.js +0 -466
  285. package/dist/lib/internal/task-orchestration.js +0 -71
  286. package/dist/lib/platform/verification.js +0 -6
  287. package/dist/lib/run/prompt.js +0 -2
  288. package/dist/lib/sandbox/engines/authority.js +0 -289
  289. package/dist/lib/task/delegation-receipts.js +0 -410
  290. package/dist/lib/task/orchestration.js +0 -1233
  291. package/dist/lib/task/verification-checks.js +0 -9
  292. package/dist/lib/task/workspace-snapshot.js +0 -121
  293. package/dist/lifecycle-build-manifest.json +0 -6
  294. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +0 -688
  295. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +0 -147
  296. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +0 -349
  297. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +0 -506
  298. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +0 -385
  299. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -41
  300. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +0 -418
  301. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +0 -354
  302. package/lib/agent-clients/adapters/codex-orchestration.ts +0 -630
  303. package/lib/internal/task-orchestration.ts +0 -74
  304. package/lib/platform/verification.ts +0 -11
  305. package/lib/run/prompt.ts +0 -1
  306. package/lib/sandbox/engines/authority.ts +0 -365
  307. package/lib/task/delegation-receipts.ts +0 -547
  308. package/lib/task/orchestration.ts +0 -1651
  309. package/lib/task/verification-checks.ts +0 -10
  310. package/lib/task/workspace-snapshot.ts +0 -144
  311. package/templates/.agents/hooks/lifecycle-delegation.js +0 -140
  312. package/templates/.agents/rules/lifecycle-orchestration.en.md +0 -44
  313. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +0 -44
  314. package/templates/.agents/skills/run-task/SKILL.en.md +0 -29
  315. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +0 -29
  316. package/templates/.agents/skills/run-task/config/verify.en.json +0 -10
  317. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +0 -10
  318. package/templates/.agents/skills/run-task/reference/host-validation.en.md +0 -24
  319. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +0 -24
  320. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +0 -6
  321. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +0 -6
  322. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +0 -6
  323. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +0 -6
  324. package/templates/.claude/commands/run-task.en.md +0 -8
  325. package/templates/.claude/commands/run-task.zh-CN.md +0 -8
  326. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +0 -3
  327. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +0 -3
  328. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +0 -3
  329. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +0 -3
  330. package/templates/.opencode/commands/run-task.en.md +0 -11
  331. package/templates/.opencode/commands/run-task.zh-CN.md +0 -11
@@ -1,25 +1,22 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { createHash } from 'node:crypto';
4
3
  import * as p from '@clack/prompts';
5
4
  import pc from 'picocolors';
6
5
  import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel, sandboxTaskIdLabel, sandboxWorkspaceModeLabel, shareBranchDir, shellConfigDirCandidates, worktreeDirCandidates } from './constants.js';
7
- import { ENGINES, detectEngine, engineDisplayName, isManagedEngine, stopManagedVm } from './engine.js';
8
- import { pruneSandboxDanglingImages } from './image-prune.js';
9
- import { assertManagedPath, removeManagedDir, removeWorktreeDir } from './managed-fs.js';
10
- import { run, runEngine, runOk, runOkEngine, runSafe, runSafeEngine } from './shell.js';
11
- import { parseSandboxWorkspaceIdentity, resolveSandboxCleanupTarget, resolveSandboxTarget, sameSandboxWorkspaceIdentity } from './workspace-identity.js';
6
+ import { detectEngine } from './engine.js';
7
+ import './image-prune.js';
8
+ import { assertManagedPath, removeWorktreeDir } from './managed-fs.js';
9
+ import { runEngine, runOk, runSafe, runSafeEngine } from './shell.js';
10
+ import { resolveSandboxCleanupTarget, resolveSandboxTarget } from './workspace-identity.js';
12
11
  import { sandboxControlPaths, sandboxWorkspaceViewPaths } from './workspace-view.js';
13
- import { advanceSandboxRemovalJournalToPhase, advanceSandboxRemovalJournalPhase, claimSandboxRemovalJournal, clearSandboxRemovalJournalRecord, isDefaultSandboxRemovalJournal, listSandboxRemovalJournals, removeSandboxControlRoot, readSandboxControlManifest, sandboxRemovalPhaseIndex } from './control/lifecycle.js';
14
- import { inspectSandboxControlContainer } from './control/container-identity.js';
15
- import { commandForSandboxAuthority } from './engines/authority.js';
16
- import { acquireSandboxResourceLock } from './control/native-file-lock.js';
12
+ import './control/lifecycle.js';
13
+ import './control/container-identity.js';
17
14
  import { toolConfigDirCandidates, toolProjectDirCandidates } from './tools.js';
18
- import { getProcessStartTime } from '../server/process-state.js';
15
+ import '../server/process-state.js';
19
16
  import { releaseStaleShortIdRegistry } from '../task/short-id.js';
20
17
  import { fetchSandboxRows } from './commands/list-running.js';
21
- import { createSandboxControlBindingEvidence } from './task-cleanup.js';
22
- import { createCleanPermit, createDiscardPermit, formatWorktreeSnapshot, inspectRecoveredWorktree, inspectWorktrees, verifyWorktreePermit } from './worktree-safety.js';
18
+ import './task-cleanup.js';
19
+ import { createCleanPermit, createDiscardPermit, formatWorktreeSnapshot, inspectRecoveredWorktree, inspectWorktrees } from './worktree-safety.js';
23
20
  function projectToolDirs(config, tools) {
24
21
  return tools.flatMap((tool) => toolProjectDirCandidates(tool, config.project));
25
22
  }
@@ -27,104 +24,39 @@ function isMissingPathError(error) {
27
24
  return Boolean(error && typeof error === 'object' && 'code' in error
28
25
  && error.code === 'ENOENT');
29
26
  }
30
- function canonicalPath(input) {
31
- try {
32
- return fs.realpathSync.native(input);
33
- }
34
- catch {
35
- return path.resolve(input);
36
- }
37
- }
38
- function isMissingTaskRecordError(error) {
39
- return error instanceof Error && /^Task not found: TASK-\d{8}-\d{6}$/.test(error.message);
40
- }
41
- function digestCleanupValue(value) {
42
- return createHash('sha256').update(JSON.stringify(value)).digest('hex');
43
- }
44
- function removalTargetDigest(config, target) {
45
- return digestCleanupValue({
46
- project: config.project,
47
- branch: target.effectiveBranch,
48
- workspace: target.workspace,
49
- controlRoots: target.controlRoots.map((candidate) => path.resolve(candidate)),
50
- workspaceViewRoots: target.workspaceViewRoots.map((candidate) => path.resolve(candidate)),
51
- managedPathCandidates: (target.managedPathCandidates ?? []).map((candidate) => path.resolve(candidate)).sort()
52
- });
53
- }
54
- function removalPermitDigest(permits) {
55
- return digestCleanupValue([...permits.entries()]
56
- .map(([worktree, permit]) => [path.resolve(worktree), permit.mode, permit.snapshot.identity])
57
- .sort(([left], [right]) => String(left).localeCompare(String(right))));
58
- }
59
- function removalPermitCommits(permits) {
60
- return [...permits.entries()]
61
- .map(([worktree, permit]) => ({
62
- path: path.resolve(worktree),
63
- mode: permit.mode,
64
- snapshot: permit.snapshot
65
- }))
66
- .sort((left, right) => left.path.localeCompare(right.path));
67
- }
68
- function removalTargetCommit(config, target, permits, removeWorktree, removeBranch, removeShare) {
69
- return {
70
- branch: target.effectiveBranch,
71
- project: config.project,
72
- controlRoot: path.resolve(target.controlRoots[0] ?? path.join(config.controlBase, config.project)),
73
- targetDigest: removalTargetDigest(config, target),
74
- permitDigest: removalPermitDigest(permits),
75
- removeWorktree,
76
- removeBranch,
77
- removeShare,
78
- worktreePaths: target.existingWorktrees.map((candidate) => path.resolve(candidate)).sort(),
79
- workspaceViewPaths: target.workspaceViewRoots.map((candidate) => path.resolve(candidate)).sort(),
80
- toolPaths: target.toolCandidates.flatMap(({ candidates }) => candidates.map((candidate) => path.resolve(candidate))).sort(),
81
- shellPaths: shellConfigDirCandidates(config, target.effectiveBranch).map((candidate) => path.resolve(candidate)).sort(),
82
- sharePath: path.resolve(shareBranchDir(config, target.effectiveBranch)),
83
- permits: removalPermitCommits(permits)
84
- };
85
- }
86
- function assertRemovalSelectionMatches(expected, actual) {
87
- const normalize = (value) => ({
88
- ...value,
89
- controlRoot: '',
90
- permits: value.permits
91
- });
92
- if (JSON.stringify(normalize(expected)) !== JSON.stringify(normalize(actual))) {
93
- throw new Error('SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH');
27
+ function removalErrorCode(error) {
28
+ return error && typeof error === 'object' && 'code' in error
29
+ && typeof error.code === 'string'
30
+ ? error.code
31
+ : null;
32
+ }
33
+ function removalErrorMessage(error) {
34
+ const code = removalErrorCode(error);
35
+ const message = error instanceof Error ? error.message : String(error);
36
+ return code ? `${code}: ${message}` : message;
37
+ }
38
+ function addOwnerWriteToDirectories(root) {
39
+ const pending = [root];
40
+ while (pending.length > 0) {
41
+ const current = pending.pop();
42
+ let stat;
43
+ try {
44
+ stat = fs.lstatSync(current);
45
+ }
46
+ catch (error) {
47
+ if (isMissingPathError(error))
48
+ continue;
49
+ throw error;
50
+ }
51
+ if (!stat.isDirectory())
52
+ continue;
53
+ fs.chmodSync(current, stat.mode | 0o200);
54
+ for (const entry of fs.readdirSync(current))
55
+ pending.push(path.join(current, entry));
94
56
  }
95
57
  }
96
- function advanceRemovalJournals(project, target, targetDigest, phase, resourceLocks) {
97
- return listSandboxRemovalJournals({ branch: target.effectiveBranch, project, targetDigest }).map((journal) => {
98
- if (sandboxRemovalPhaseIndex(journal.phase) >= sandboxRemovalPhaseIndex(phase))
99
- return journal;
100
- const lock = resourceLocks.get(journal.target.controlRoot);
101
- if (!lock)
102
- throw new Error('SANDBOX_CONTROL_REMOVAL_LOCK_MISMATCH');
103
- return advanceSandboxRemovalJournalToPhase(journal, phase, lock);
104
- });
105
- }
106
- function prepareRemovalAction(project, target, targetDigest, startPhase, completedPhase, resourceLocks) {
107
- const journals = listSandboxRemovalJournals({
108
- branch: target.effectiveBranch,
109
- project,
110
- targetDigest
111
- });
112
- const startIndex = sandboxRemovalPhaseIndex(startPhase);
113
- const completedIndex = sandboxRemovalPhaseIndex(completedPhase);
114
- const shouldRun = journals.length === 0
115
- || journals.some((journal) => sandboxRemovalPhaseIndex(journal.phase) < completedIndex);
116
- const recovering = journals.some((journal) => {
117
- const index = sandboxRemovalPhaseIndex(journal.phase);
118
- return index >= startIndex && index < completedIndex;
119
- });
120
- advanceRemovalJournals(project, target, targetDigest, startPhase, resourceLocks);
121
- return { run: shouldRun, recovering };
122
- }
123
- function assertRemovalPathsAbsent(paths) {
124
- const remaining = paths.filter((candidate) => fs.existsSync(candidate));
125
- if (remaining.length > 0) {
126
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${remaining[0]}`);
127
- }
58
+ function isPermissionRemovalError(error) {
59
+ return ['EACCES', 'EPERM', 'ENOTEMPTY'].includes(removalErrorCode(error) ?? '');
128
60
  }
129
61
  function assertBranchRemovalIdentity(config, branch, permits) {
130
62
  if (!runOk('git', ['-C', config.repoRoot, 'show-ref', '--verify', `refs/heads/${branch}`]))
@@ -140,412 +72,6 @@ function assertBranchRemovalIdentity(config, branch, permits) {
140
72
  throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
141
73
  }
142
74
  }
143
- function removalTombstonePath(targetDigest, kind, source) {
144
- const sourceDigest = createHash('sha256')
145
- .update(`${kind}\0${path.resolve(source)}`)
146
- .digest('hex')
147
- .slice(0, 24);
148
- return path.join(path.dirname(path.resolve(source)), `.agent-infra-removal-${targetDigest.slice(0, 16)}-${kind}-${sourceDigest}`);
149
- }
150
- const REMOVAL_TOMBSTONE_MARKER = '.agent-infra-removal-ownership.json';
151
- const REMOVAL_TOMBSTONE_PAYLOAD = 'payload';
152
- function removalSourceClaimPath(source, ownership) {
153
- const claimDigest = createHash('sha256')
154
- .update(JSON.stringify(ownership))
155
- .digest('hex')
156
- .slice(0, 24);
157
- return path.join(source, `.agent-infra-removal-source-${claimDigest}.json`);
158
- }
159
- function removalSourceIdentity(source) {
160
- const stat = fs.lstatSync(source, { bigint: true });
161
- if (!stat.isDirectory() || stat.isSymbolicLink() || stat.dev === 0n || stat.ino === 0n) {
162
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
163
- }
164
- return { dev: String(stat.dev), ino: String(stat.ino) };
165
- }
166
- function sameRemovalSourceIdentity(left, right) {
167
- return left.dev === right.dev && left.ino === right.ino;
168
- }
169
- function removalSourceClaimMatches(source, ownership, identity) {
170
- try {
171
- const claimPath = removalSourceClaimPath(source, ownership);
172
- const claimStat = fs.lstatSync(claimPath);
173
- if (!claimStat.isFile() || claimStat.isSymbolicLink())
174
- return false;
175
- const record = JSON.parse(fs.readFileSync(claimPath, 'utf8'));
176
- return record.version === ownership.version
177
- && record.targetDigest === ownership.targetDigest
178
- && record.permitDigest === ownership.permitDigest
179
- && record.kind === ownership.kind
180
- && typeof record.source === 'string'
181
- && path.resolve(record.source) === path.resolve(ownership.source)
182
- && record.sourceIdentity?.dev === identity.dev
183
- && record.sourceIdentity?.ino === identity.ino;
184
- }
185
- catch {
186
- return false;
187
- }
188
- }
189
- function isOwnedRemovalSourceClaim(source, ownership) {
190
- try {
191
- const claimPath = removalSourceClaimPath(source, ownership);
192
- const claimStat = fs.lstatSync(claimPath);
193
- if (!claimStat.isFile() || claimStat.isSymbolicLink())
194
- return false;
195
- const record = JSON.parse(fs.readFileSync(claimPath, 'utf8'));
196
- return record.version === ownership.version
197
- && record.targetDigest === ownership.targetDigest
198
- && record.permitDigest === ownership.permitDigest
199
- && record.kind === ownership.kind
200
- && typeof record.source === 'string'
201
- && path.resolve(record.source) === path.resolve(ownership.source);
202
- }
203
- catch {
204
- return false;
205
- }
206
- }
207
- function removeOwnedSourceClaim(source, ownership) {
208
- try {
209
- const claimPath = removalSourceClaimPath(source, ownership);
210
- if (isOwnedRemovalSourceClaim(source, ownership))
211
- fs.unlinkSync(claimPath);
212
- }
213
- catch {
214
- // Preserve the source when the claim cannot be revalidated.
215
- }
216
- }
217
- function readRemovalTombstoneRecord(root, tombstone) {
218
- assertManagedPath(root, tombstone);
219
- try {
220
- const tombstoneStat = fs.lstatSync(tombstone);
221
- const markerStat = fs.lstatSync(removalTombstoneMarker(tombstone));
222
- if (!tombstoneStat.isDirectory() || tombstoneStat.isSymbolicLink()
223
- || !markerStat.isFile() || markerStat.isSymbolicLink())
224
- return null;
225
- const record = JSON.parse(fs.readFileSync(removalTombstoneMarker(tombstone), 'utf8'));
226
- if (record.version !== 1
227
- || typeof record.targetDigest !== 'string'
228
- || typeof record.permitDigest !== 'string'
229
- || typeof record.kind !== 'string'
230
- || typeof record.source !== 'string'
231
- || record.sourceIdentity?.dev === undefined
232
- || record.sourceIdentity.ino === undefined)
233
- return null;
234
- return record;
235
- }
236
- catch {
237
- return null;
238
- }
239
- }
240
- function removalTombstonePayload(tombstone) {
241
- return path.join(tombstone, REMOVAL_TOMBSTONE_PAYLOAD);
242
- }
243
- function removalTombstoneMarker(tombstone) {
244
- return path.join(tombstone, REMOVAL_TOMBSTONE_MARKER);
245
- }
246
- function isOwnedRemovalTombstone(root, tombstone, ownership) {
247
- const record = readRemovalTombstoneRecord(root, tombstone);
248
- return record !== null
249
- && record.version === 1
250
- && record.targetDigest === ownership.targetDigest
251
- && record.permitDigest === ownership.permitDigest
252
- && record.kind === ownership.kind
253
- && typeof record.source === 'string'
254
- && path.resolve(record.source) === path.resolve(ownership.source);
255
- }
256
- function isOwnedRemovalPayload(root, tombstone, ownership) {
257
- const record = readRemovalTombstoneRecord(root, tombstone);
258
- if (!record || record.targetDigest !== ownership.targetDigest
259
- || record.permitDigest !== ownership.permitDigest
260
- || record.kind !== ownership.kind
261
- || path.resolve(record.source) !== path.resolve(ownership.source))
262
- return false;
263
- try {
264
- const payload = removalTombstonePayload(tombstone);
265
- const payloadIdentity = removalSourceIdentity(payload);
266
- return sameRemovalSourceIdentity(payloadIdentity, record.sourceIdentity)
267
- && removalSourceClaimMatches(payload, ownership, record.sourceIdentity);
268
- }
269
- catch {
270
- return false;
271
- }
272
- }
273
- function assertOwnedRemovalTombstone(root, tombstone, ownership) {
274
- if (!isOwnedRemovalTombstone(root, tombstone, ownership)) {
275
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${ownership.source}`);
276
- }
277
- }
278
- function shouldStageManagedRemoval(source, tombstone, recovering) {
279
- return fs.existsSync(source) || (recovering && fs.existsSync(tombstone));
280
- }
281
- function stageManagedRemoval(root, source, tombstone, recovering, ownership) {
282
- assertManagedPath(root, source);
283
- assertManagedPath(root, tombstone);
284
- const sourceExists = fs.existsSync(source);
285
- const tombstoneExists = fs.existsSync(tombstone);
286
- if (tombstoneExists) {
287
- assertOwnedRemovalTombstone(root, tombstone, ownership);
288
- if (sourceExists) {
289
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
290
- }
291
- if (recovering) {
292
- if (!isOwnedRemovalPayload(root, tombstone, ownership)) {
293
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
294
- }
295
- return;
296
- }
297
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
298
- }
299
- if (recovering) {
300
- throw new Error(`SANDBOX_CONTROL_REMOVAL_ACTION_OUTCOME_UNKNOWN: ${source}`);
301
- }
302
- if (!sourceExists)
303
- return;
304
- // The operator authorizes cleanup of this managed target after execution stops.
305
- // Path checks detect conflicts; they do not provide an atomic source-instance move.
306
- const expectedIdentity = removalSourceIdentity(source);
307
- const claimPath = removalSourceClaimPath(source, ownership);
308
- try {
309
- fs.writeFileSync(claimPath, `${JSON.stringify({ ...ownership, sourceIdentity: expectedIdentity })}\n`, {
310
- encoding: 'utf8',
311
- mode: 0o600,
312
- flag: 'wx'
313
- });
314
- }
315
- catch {
316
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
317
- }
318
- if (!sameRemovalSourceIdentity(expectedIdentity, removalSourceIdentity(source))) {
319
- removeOwnedSourceClaim(source, ownership);
320
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
321
- }
322
- let moved = false;
323
- try {
324
- fs.mkdirSync(tombstone, { mode: 0o700 });
325
- fs.writeFileSync(removalTombstoneMarker(tombstone), `${JSON.stringify({ ...ownership, sourceIdentity: expectedIdentity })}\n`, {
326
- encoding: 'utf8',
327
- mode: 0o600,
328
- flag: 'wx'
329
- });
330
- fs.renameSync(source, removalTombstonePayload(tombstone));
331
- moved = true;
332
- if (!isOwnedRemovalPayload(root, tombstone, ownership)) {
333
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
334
- }
335
- }
336
- catch (error) {
337
- if (!moved)
338
- removeOwnedSourceClaim(source, ownership);
339
- throw error;
340
- }
341
- }
342
- function cleanupManagedTombstones(root, targetDigest, permitDigest, kind, sources) {
343
- for (const source of sources) {
344
- const tombstone = removalTombstonePath(targetDigest, kind, source);
345
- if (!fs.existsSync(tombstone))
346
- continue;
347
- const ownership = { version: 1, targetDigest, permitDigest, kind, source };
348
- if (!isOwnedRemovalTombstone(root, tombstone, ownership))
349
- continue;
350
- if (!isOwnedRemovalPayload(root, tombstone, ownership)) {
351
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${source}`);
352
- }
353
- removeManagedDir(root, tombstone);
354
- }
355
- }
356
- function branchRemovalTombstoneRef(targetDigest) {
357
- return `refs/agent-infra/sandbox-removal/${targetDigest}/branch`;
358
- }
359
- function branchPermitHead(branch, permits) {
360
- const heads = new Set([...permits.values()]
361
- .filter((permit) => permit.snapshot.branch === branch)
362
- .map((permit) => permit.snapshot.head));
363
- return heads.size === 1 ? [...heads][0] : null;
364
- }
365
- function stageBranchRemoval(config, branch, targetDigest, permits, recovering) {
366
- const expectedHead = branchPermitHead(branch, permits);
367
- if (!expectedHead)
368
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
369
- const branchName = `refs/heads/${branch}`;
370
- const tombstone = branchRemovalTombstoneRef(targetDigest);
371
- const branchExists = runOk('git', ['-C', config.repoRoot, 'show-ref', '--verify', branchName]);
372
- const tombstoneExists = runOk('git', ['-C', config.repoRoot, 'show-ref', '--verify', tombstone]);
373
- if (branchExists && tombstoneExists) {
374
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
375
- }
376
- if (recovering) {
377
- if (branchExists && !tombstoneExists) {
378
- throw new Error(`SANDBOX_CONTROL_REMOVAL_ACTION_OUTCOME_UNKNOWN: ${branch}`);
379
- }
380
- if (tombstoneExists && runSafe('git', ['-C', config.repoRoot, 'rev-parse', tombstone]) !== expectedHead) {
381
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
382
- }
383
- return;
384
- }
385
- if (!branchExists)
386
- return;
387
- assertBranchRemovalIdentity(config, branch, permits);
388
- const actualHead = runSafe('git', ['-C', config.repoRoot, 'rev-parse', branchName]);
389
- if (!runOk('git', ['-C', config.repoRoot, 'update-ref', tombstone, actualHead, '0'.repeat(40)])) {
390
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
391
- }
392
- if (!runOk('git', ['-C', config.repoRoot, 'update-ref', '-d', branchName, actualHead])) {
393
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
394
- }
395
- }
396
- function cleanupBranchTombstone(config, branch, targetDigest, permits) {
397
- const tombstone = branchRemovalTombstoneRef(targetDigest);
398
- if (!runOk('git', ['-C', config.repoRoot, 'show-ref', '--verify', tombstone]))
399
- return;
400
- const expectedHead = branchPermitHead(branch, permits);
401
- if (!expectedHead || runSafe('git', ['-C', config.repoRoot, 'rev-parse', tombstone]) !== expectedHead) {
402
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
403
- }
404
- if (!runOk('git', ['-C', config.repoRoot, 'update-ref', '-d', tombstone, expectedHead])) {
405
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${branch}`);
406
- }
407
- }
408
- function worktreeRegistration(config, worktree) {
409
- const lines = runSafe('git', ['-C', config.repoRoot, 'worktree', 'list', '--porcelain']).split('\n');
410
- let current = null;
411
- const flush = () => {
412
- if (!current || path.resolve(current.path) !== path.resolve(worktree))
413
- return null;
414
- return { head: current.head, branch: current.branch };
415
- };
416
- for (const line of lines) {
417
- if (line.startsWith('worktree ')) {
418
- const previous = flush();
419
- if (previous)
420
- return previous;
421
- current = { path: line.slice('worktree '.length), head: '', branch: null };
422
- }
423
- else if (current && line.startsWith('HEAD ')) {
424
- current.head = line.slice('HEAD '.length);
425
- }
426
- else if (current && line.startsWith('branch ')) {
427
- current.branch = line.slice('branch '.length);
428
- }
429
- }
430
- return flush();
431
- }
432
- function assertWorktreeRegistrationIdentity(config, worktree, permit) {
433
- const registration = worktreeRegistration(config, worktree);
434
- if (!registration)
435
- return;
436
- if (registration.head !== permit.snapshot.head
437
- || registration.branch !== `refs/heads/${permit.snapshot.branch}`) {
438
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${worktree}`);
439
- }
440
- }
441
- function pruneWorktreeRegistration(config, worktree) {
442
- runSafe('git', ['-C', config.repoRoot, 'worktree', 'prune']);
443
- if (worktreeRegistration(config, worktree)) {
444
- throw new Error(`SANDBOX_CONTROL_REMOVAL_ACTION_OUTCOME_UNKNOWN: ${worktree}`);
445
- }
446
- }
447
- function stageWorktreeRemoval(config, worktree, permit, tombstone, recovering, ownership) {
448
- assertManagedPath(config.worktreeBase, worktree);
449
- assertManagedPath(config.worktreeBase, tombstone);
450
- const worktreeExists = fs.existsSync(worktree);
451
- const tombstoneExists = fs.existsSync(tombstone);
452
- if (worktreeExists || !recovering)
453
- verifyWorktreePermit(permit);
454
- if (recovering) {
455
- if (worktreeExists || !tombstoneExists) {
456
- if (worktreeExists && tombstoneExists) {
457
- throw new Error(`SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH: ${worktree}`);
458
- }
459
- throw new Error(`SANDBOX_CONTROL_REMOVAL_ACTION_OUTCOME_UNKNOWN: ${worktree}`);
460
- }
461
- assertOwnedRemovalTombstone(config.worktreeBase, tombstone, ownership);
462
- assertWorktreeRegistrationIdentity(config, worktree, permit);
463
- pruneWorktreeRegistration(config, worktree);
464
- return;
465
- }
466
- if (!worktreeExists)
467
- return;
468
- stageManagedRemoval(config.worktreeBase, worktree, tombstone, false, ownership);
469
- pruneWorktreeRegistration(config, worktree);
470
- }
471
- function cleanupCompletedRemovalArtifacts(config, target, committedTarget, targetDigest) {
472
- const journals = listSandboxRemovalJournals({
473
- branch: target.effectiveBranch,
474
- project: config.project,
475
- targetDigest
476
- });
477
- if (journals.length === 0)
478
- return;
479
- const completed = (phase) => journals.every((journal) => (sandboxRemovalPhaseIndex(journal.phase) >= sandboxRemovalPhaseIndex(phase)));
480
- if (completed('workspace-removed')) {
481
- cleanupManagedTombstones(config.workspaceViewBase, targetDigest, committedTarget.permitDigest, 'workspace', target.workspaceViewRoots);
482
- cleanupManagedTombstones(path.join(config.controlBase, config.project), targetDigest, committedTarget.permitDigest, 'workspace', target.controlRoots);
483
- cleanupManagedTombstones(config.worktreeBase, targetDigest, committedTarget.permitDigest, 'worktree', committedTarget.worktreePaths);
484
- }
485
- if (completed('branch-removed')) {
486
- cleanupBranchTombstone(config, target.effectiveBranch, targetDigest, new Map(committedTarget.permits.map((permit) => [path.resolve(permit.path), {
487
- mode: permit.mode,
488
- snapshot: permit.snapshot
489
- }])));
490
- }
491
- if (completed('tool-removed')) {
492
- cleanupManagedTombstones(config.home, targetDigest, committedTarget.permitDigest, 'tool', committedTarget.toolPaths);
493
- }
494
- if (completed('shell-removed')) {
495
- cleanupManagedTombstones(config.shellConfigBase, targetDigest, committedTarget.permitDigest, 'shell', committedTarget.shellPaths);
496
- }
497
- if (completed('share-removed')) {
498
- cleanupManagedTombstones(config.shareBase, targetDigest, committedTarget.permitDigest, 'share', [committedTarget.sharePath]);
499
- }
500
- }
501
- function removalActionPaths(target, config, phase) {
502
- if (phase === 'workspace-removed') {
503
- return [...target.workspaceViewRoots, ...target.controlRoots];
504
- }
505
- if (phase === 'tool-removed') {
506
- return target.toolCandidates.flatMap(({ candidates }) => candidates);
507
- }
508
- if (phase === 'shell-removed') {
509
- return shellConfigDirCandidates(config, target.effectiveBranch);
510
- }
511
- if (phase === 'share-removed') {
512
- return [shareBranchDir(config, target.effectiveBranch)];
513
- }
514
- return [];
515
- }
516
- function permitsFromJournal(journals, target, expectedTargetDigest) {
517
- const permits = new Map();
518
- for (const journal of journals) {
519
- if (journal.target.targetDigest !== expectedTargetDigest)
520
- continue;
521
- if (journal.target.branch !== target.effectiveBranch) {
522
- throw new Error('SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH');
523
- }
524
- for (const entry of journal.target.permits) {
525
- if (path.resolve(entry.path) !== path.resolve(entry.snapshot.worktree)) {
526
- throw new Error('SANDBOX_CONTROL_REMOVAL_PERMIT_MISMATCH');
527
- }
528
- permits.set(path.resolve(entry.path), {
529
- mode: entry.mode,
530
- snapshot: entry.snapshot
531
- });
532
- }
533
- }
534
- return permits;
535
- }
536
- export function sandboxManagedPathKey(candidate, platform = process.platform, resolveExistingPath = (resolved) => fs.realpathSync.native(resolved)) {
537
- const resolved = path.resolve(candidate);
538
- let identity = resolved;
539
- try {
540
- identity = resolveExistingPath(resolved);
541
- }
542
- catch (error) {
543
- if (!isMissingPathError(error)) {
544
- throw new Error(`SANDBOX_CLEANUP_BATCH_PREFLIGHT_FAILED: unable to determine managed path identity for '${resolved}'`);
545
- }
546
- }
547
- return platform === 'win32' ? identity.toLowerCase() : identity;
548
- }
549
75
  function resolveRmTarget(config, tools, cleanupTarget, options = {}) {
550
76
  assertValidBranchName(cleanupTarget.branch);
551
77
  const engine = detectEngine(config);
@@ -718,8 +244,28 @@ async function removeUncheckedSandbox(config, target, options) {
718
244
  fs.rmSync(root, { recursive: true, force: true });
719
245
  removeEmptyManagedParent(path.join(config.workspaceViewBase, config.project), root);
720
246
  }
721
- for (const candidate of toolCandidates.flatMap(({ candidates }) => candidates))
722
- fs.rmSync(candidate, { recursive: true, force: true });
247
+ const toolRemovalFailures = [];
248
+ for (const candidate of toolCandidates.flatMap(({ candidates }) => candidates)) {
249
+ try {
250
+ fs.rmSync(candidate, { recursive: true, force: true });
251
+ continue;
252
+ }
253
+ catch (initialError) {
254
+ if (isPermissionRemovalError(initialError)) {
255
+ try {
256
+ addOwnerWriteToDirectories(candidate);
257
+ fs.rmSync(candidate, { recursive: true, force: true });
258
+ continue;
259
+ }
260
+ catch (retryError) {
261
+ toolRemovalFailures.push(`${candidate}: ${removalErrorMessage(initialError)}; retry failed: ${removalErrorMessage(retryError)}`);
262
+ }
263
+ }
264
+ else {
265
+ toolRemovalFailures.push(`${candidate}: ${removalErrorMessage(initialError)}`);
266
+ }
267
+ }
268
+ }
723
269
  for (const shell of shellConfigDirCandidates(config, effectiveBranch))
724
270
  fs.rmSync(shell, { recursive: true, force: true });
725
271
  if (shouldRemoveShare)
@@ -736,6 +282,9 @@ async function removeUncheckedSandbox(config, target, options) {
736
282
  assertBranchRemovalIdentity(config, effectiveBranch, worktreePermits);
737
283
  runSafe('git', ['-C', config.repoRoot, 'branch', '-D', effectiveBranch]);
738
284
  }
285
+ if (toolRemovalFailures.length > 0) {
286
+ throw new Error(`SANDBOX_TOOL_CLEANUP_INCOMPLETE:\n${toolRemovalFailures.map((failure) => ` - ${failure}`).join('\n')}`);
287
+ }
739
288
  releaseTaskBoundSandboxIdentity(config.repoRoot, target.workspace);
740
289
  if (!options.quiet)
741
290
  p.outro('Sandbox removed');
@@ -884,7 +433,7 @@ async function rmUnbound(config, tools, options) {
884
433
  p.outro(`Dry run: ${targets.length} sandbox(es) found, nothing deleted`);
885
434
  return;
886
435
  }
887
- for (const { row, cleanupTarget, target } of targets) {
436
+ for (const { cleanupTarget, target } of targets) {
888
437
  await runRmOne(config, tools, cleanupTarget.branch, {
889
438
  assumeYes: options.assumeYes,
890
439
  target,