@fitlab-ai/agent-infra 0.9.9 → 0.9.11

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 (309) hide show
  1. package/README.md +13 -3
  2. package/README.zh-CN.md +13 -3
  3. package/bin/cli.ts +17 -4
  4. package/dist/bin/cli.js +19 -4
  5. package/dist/bin/internal-cli.js +55 -4
  6. package/dist/lib/agent-client.js +0 -1
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +17 -10
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +11 -31
  9. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +15 -33
  10. package/dist/lib/agent-clients/adapters/codex-sandbox.js +72 -11
  11. package/dist/lib/agent-clients/adapters/index.js +3 -1
  12. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +0 -4
  13. package/dist/lib/agent-clients/adapters/traecli.js +65 -0
  14. package/dist/lib/agent-clients/config.js +14 -99
  15. package/dist/lib/agent-clients/reconcile.js +1 -11
  16. package/dist/lib/agent-clients/schema.js +5 -4
  17. package/dist/lib/agent-clients/tokens.js +7 -17
  18. package/dist/lib/agent-clients/types.js +2 -1
  19. package/dist/lib/decide.js +14 -30
  20. package/dist/lib/defaults.json +9 -0
  21. package/dist/lib/init.js +2 -2
  22. package/dist/lib/internal/codex-lifecycle.js +2 -2
  23. package/dist/lib/internal/codex-sandbox-controller.js +4 -3
  24. package/dist/lib/internal/release-workflow.js +0 -1
  25. package/dist/lib/internal/sandbox-control.js +2 -2
  26. package/dist/lib/internal/task-finalization.js +19 -9
  27. package/dist/lib/internal/task-ledger.js +8 -2
  28. package/dist/lib/internal/task-lifecycle.js +17 -93
  29. package/dist/lib/internal/task-orchestration.js +30 -216
  30. package/dist/lib/{task/commands/validate.js → internal/task-validate.js} +77 -43
  31. package/dist/lib/merge.js +205 -103
  32. package/dist/lib/process-data/normalize.js +2 -9
  33. package/dist/lib/run/host.js +46 -16
  34. package/dist/lib/run/tui.js +5 -2
  35. package/dist/lib/sandbox/agent-client-reconciler.js +4 -12
  36. package/dist/lib/sandbox/commands/create.js +3 -3
  37. package/dist/lib/sandbox/commands/enter.js +18 -4
  38. package/dist/lib/sandbox/config.js +3 -12
  39. package/dist/lib/sandbox/control/controller-registration.js +4 -2
  40. package/dist/lib/sandbox/control/executor.js +135 -40
  41. package/dist/lib/sandbox/control/lifecycle.js +29 -20
  42. package/dist/lib/sandbox/control/server.js +18 -16
  43. package/dist/lib/sandbox/mounts.js +3 -2
  44. package/dist/lib/sandbox/recovery.js +123 -22
  45. package/dist/lib/sandbox/workspace-identity.js +14 -0
  46. package/dist/lib/sandbox/workspace-view.js +8 -9
  47. package/dist/lib/self-update.js +278 -0
  48. package/dist/lib/server/protocol.js +5 -5
  49. package/dist/lib/task/activity-log.js +4 -1
  50. package/dist/lib/task/artifact-lifecycle.js +4 -2
  51. package/dist/lib/task/codex-orchestration.js +32 -35
  52. package/dist/lib/task/command-options.js +1 -3
  53. package/dist/lib/task/commands/cat.js +0 -5
  54. package/dist/lib/task/commands/decisions.js +76 -92
  55. package/dist/lib/task/commands/files.js +6 -6
  56. package/dist/lib/task/commands/grep.js +25 -61
  57. package/dist/lib/task/commands/issue-body.js +6 -6
  58. package/dist/lib/task/commands/log.js +25 -10
  59. package/dist/lib/task/commands/show.js +5 -6
  60. package/dist/lib/task/commands/status.js +8 -8
  61. package/dist/lib/task/commit-operation.js +21 -25
  62. package/dist/lib/task/commit-policy.js +1 -1
  63. package/dist/lib/task/control-authority.js +431 -0
  64. package/dist/lib/task/create.js +6 -1
  65. package/dist/lib/task/current-contract.js +35 -0
  66. package/dist/lib/task/decision-details.js +263 -0
  67. package/dist/lib/task/decision-intents.js +5 -2
  68. package/dist/lib/task/delegation-receipts.js +8 -7
  69. package/dist/lib/task/events.js +11 -5
  70. package/dist/lib/task/finalization.js +72 -46
  71. package/dist/lib/task/index.js +19 -29
  72. package/dist/lib/task/ledger-intents.js +8 -9
  73. package/dist/lib/task/ledger.js +13 -6
  74. package/dist/lib/task/lifecycle.js +16 -0
  75. package/dist/lib/task/orchestration.js +51 -14
  76. package/dist/lib/task/review-artifacts.js +31 -12
  77. package/dist/lib/task/review-finalization.js +19 -6
  78. package/dist/lib/task/verification-engine.js +38 -18
  79. package/dist/lib/task/verification-review.js +1 -1
  80. package/dist/lib/task/workspace-snapshot.js +5 -11
  81. package/dist/lib/task/write.js +11 -1
  82. package/dist/lib/update.js +6 -37
  83. package/dist/lib/version.js +5 -1
  84. package/dist/lifecycle-build-manifest.json +3 -3
  85. package/lib/agent-client.ts +0 -1
  86. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +25 -10
  87. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +16 -33
  88. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +16 -35
  89. package/lib/agent-clients/adapters/codex-sandbox.ts +89 -14
  90. package/lib/agent-clients/adapters/index.ts +3 -1
  91. package/lib/agent-clients/adapters/opencode-sandbox.ts +0 -5
  92. package/lib/agent-clients/adapters/traecli.ts +66 -0
  93. package/lib/agent-clients/config.ts +14 -144
  94. package/lib/agent-clients/reconcile.ts +1 -12
  95. package/lib/agent-clients/schema.ts +5 -4
  96. package/lib/agent-clients/tokens.ts +7 -20
  97. package/lib/agent-clients/types.ts +2 -1
  98. package/lib/decide.ts +12 -22
  99. package/lib/defaults.json +9 -0
  100. package/lib/init.ts +2 -2
  101. package/lib/internal/codex-lifecycle.ts +2 -2
  102. package/lib/internal/codex-sandbox-controller.ts +4 -3
  103. package/lib/internal/release-workflow.ts +0 -1
  104. package/lib/internal/sandbox-control.ts +2 -2
  105. package/lib/internal/task-finalization.ts +27 -13
  106. package/lib/internal/task-ledger.ts +8 -2
  107. package/lib/internal/task-lifecycle.ts +33 -76
  108. package/lib/internal/task-orchestration.ts +33 -190
  109. package/lib/{task/commands/validate.ts → internal/task-validate.ts} +77 -41
  110. package/lib/merge.ts +236 -120
  111. package/lib/process-data/normalize.ts +2 -8
  112. package/lib/run/host.ts +63 -14
  113. package/lib/run/tui.ts +5 -3
  114. package/lib/sandbox/agent-client-reconciler.ts +5 -15
  115. package/lib/sandbox/commands/create.ts +4 -4
  116. package/lib/sandbox/commands/enter.ts +24 -4
  117. package/lib/sandbox/config.ts +2 -14
  118. package/lib/sandbox/control/container-identity.ts +2 -2
  119. package/lib/sandbox/control/controller-registration.ts +8 -3
  120. package/lib/sandbox/control/executor.ts +156 -53
  121. package/lib/sandbox/control/lifecycle.ts +34 -28
  122. package/lib/sandbox/control/protocol.ts +1 -3
  123. package/lib/sandbox/control/server.ts +15 -12
  124. package/lib/sandbox/control/state.ts +7 -7
  125. package/lib/sandbox/mounts.ts +4 -3
  126. package/lib/sandbox/recovery.ts +162 -34
  127. package/lib/sandbox/tools.ts +1 -1
  128. package/lib/sandbox/workspace-identity.ts +28 -0
  129. package/lib/sandbox/workspace-view.ts +14 -10
  130. package/lib/self-update.ts +341 -0
  131. package/lib/server/protocol.ts +5 -5
  132. package/lib/task/activity-log.ts +6 -1
  133. package/lib/task/artifact-lifecycle.ts +5 -3
  134. package/lib/task/codex-orchestration.ts +40 -45
  135. package/lib/task/command-options.ts +1 -2
  136. package/lib/task/commands/cat.ts +0 -4
  137. package/lib/task/commands/decisions.ts +80 -75
  138. package/lib/task/commands/files.ts +6 -6
  139. package/lib/task/commands/grep.ts +23 -51
  140. package/lib/task/commands/issue-body.ts +6 -6
  141. package/lib/task/commands/log.ts +22 -10
  142. package/lib/task/commands/show.ts +5 -6
  143. package/lib/task/commands/status.ts +8 -8
  144. package/lib/task/commit-operation.ts +25 -30
  145. package/lib/task/commit-policy.ts +1 -2
  146. package/lib/task/control-authority.ts +563 -0
  147. package/lib/task/create.ts +6 -1
  148. package/lib/task/current-contract.ts +49 -0
  149. package/lib/task/decision-details.ts +340 -0
  150. package/lib/task/decision-intents.ts +7 -2
  151. package/lib/task/delegation-receipts.ts +8 -6
  152. package/lib/task/events.ts +10 -5
  153. package/lib/task/finalization.ts +84 -42
  154. package/lib/task/index.ts +19 -28
  155. package/lib/task/ledger-intents.ts +6 -8
  156. package/lib/task/ledger.ts +17 -7
  157. package/lib/task/lifecycle.ts +20 -0
  158. package/lib/task/orchestration.ts +42 -16
  159. package/lib/task/review-artifacts.ts +53 -15
  160. package/lib/task/review-finalization.ts +33 -7
  161. package/lib/task/verification-engine.ts +33 -21
  162. package/lib/task/verification-review.ts +1 -1
  163. package/lib/task/workspace-snapshot.ts +4 -13
  164. package/lib/task/write.ts +20 -1
  165. package/lib/update.ts +6 -40
  166. package/lib/version.ts +6 -1
  167. package/package.json +1 -1
  168. package/templates/.agents/QUICKSTART.en.md +2 -3
  169. package/templates/.agents/QUICKSTART.zh-CN.md +2 -3
  170. package/templates/.agents/README.en.md +5 -3
  171. package/templates/.agents/README.zh-CN.md +5 -3
  172. package/templates/.agents/rules/README.en.md +1 -0
  173. package/templates/.agents/rules/README.zh-CN.md +1 -0
  174. package/templates/.agents/rules/compatibility-policy.en.md +40 -0
  175. package/templates/.agents/rules/compatibility-policy.zh-CN.md +40 -0
  176. package/templates/.agents/rules/create-issue.github.en.md +1 -1
  177. package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
  178. package/templates/.agents/rules/human-decision-context.en.md +9 -7
  179. package/templates/.agents/rules/human-decision-context.zh-CN.md +9 -7
  180. package/templates/.agents/rules/issue-fields.github.en.md +1 -1
  181. package/templates/.agents/rules/issue-fields.github.zh-CN.md +1 -1
  182. package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
  183. package/templates/.agents/rules/issue-pr-commands.github.en.md +1 -1
  184. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +1 -1
  185. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  186. package/templates/.agents/rules/issue-sync.github.en.md +1 -1
  187. package/templates/.agents/rules/issue-sync.github.zh-CN.md +2 -2
  188. package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -4
  189. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -4
  190. package/templates/.agents/rules/milestone-inference.github.en.md +1 -1
  191. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +1 -1
  192. package/templates/.agents/rules/next-step-output.en.md +3 -3
  193. package/templates/.agents/rules/next-step-output.zh-CN.md +3 -3
  194. package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -1
  195. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -1
  196. package/templates/.agents/rules/pr-sync.en.md +2 -2
  197. package/templates/.agents/rules/pr-sync.github.en.md +3 -2
  198. package/templates/.agents/rules/pr-sync.github.zh-CN.md +3 -1
  199. package/templates/.agents/rules/pr-sync.zh-CN.md +2 -1
  200. package/templates/.agents/rules/release-commands.github.en.md +6 -0
  201. package/templates/.agents/rules/release-commands.github.zh-CN.md +6 -0
  202. package/templates/.agents/rules/review-handshake.en.md +1 -1
  203. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  204. package/templates/.agents/rules/review-method.en.md +1 -0
  205. package/templates/.agents/rules/review-method.zh-CN.md +1 -0
  206. package/templates/.agents/rules/task-management.en.md +1 -2
  207. package/templates/.agents/rules/task-management.zh-CN.md +1 -2
  208. package/templates/.agents/skills/analyze-task/SKILL.en.md +4 -3
  209. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -3
  210. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  211. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  212. package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
  213. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  214. package/templates/.agents/skills/check-task/SKILL.en.md +2 -2
  215. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +2 -2
  216. package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
  217. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +1 -1
  218. package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
  219. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +1 -1
  220. package/templates/.agents/skills/code-task/SKILL.en.md +4 -3
  221. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +5 -3
  222. package/templates/.agents/skills/commit/SKILL.en.md +27 -7
  223. package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -4
  224. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -5
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -5
  226. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  227. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  228. package/templates/.agents/skills/create-pr/SKILL.en.md +15 -11
  229. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +15 -11
  230. package/templates/.agents/skills/create-pr/reference/change-report.en.md +46 -0
  231. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +46 -0
  232. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +203 -0
  233. package/templates/.agents/skills/create-release-note/SKILL.en.md +5 -6
  234. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +5 -6
  235. package/templates/.agents/skills/create-task/SKILL.en.md +1 -1
  236. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
  237. package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
  238. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  239. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -4
  240. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -4
  241. package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
  242. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
  243. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -3
  244. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -3
  245. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -0
  246. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -0
  247. package/templates/.agents/skills/review-code/SKILL.en.md +5 -3
  248. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -3
  249. package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
  250. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
  251. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +9 -6
  252. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +9 -6
  253. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -3
  254. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -3
  255. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -0
  256. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -0
  257. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +7 -3
  258. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +7 -3
  259. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +1 -1
  260. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +1 -1
  261. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
  262. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
  263. package/templates/.agents/skills/run-task/SKILL.en.md +6 -2
  264. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -2
  265. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  266. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
  267. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
  268. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
  269. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +159 -104
  270. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  271. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  272. package/templates/.agents/templates/task.en.md +1 -1
  273. package/templates/.agents/templates/task.zh-CN.md +1 -1
  274. package/templates/.claude/commands/analyze-task.en.md +1 -1
  275. package/templates/.claude/commands/analyze-task.zh-CN.md +1 -1
  276. package/templates/.claude/commands/block-task.en.md +1 -1
  277. package/templates/.claude/commands/block-task.zh-CN.md +1 -1
  278. package/templates/.claude/commands/cancel-task.en.md +1 -1
  279. package/templates/.claude/commands/cancel-task.zh-CN.md +1 -1
  280. package/templates/.claude/commands/check-task.en.md +1 -1
  281. package/templates/.claude/commands/check-task.zh-CN.md +1 -1
  282. package/templates/.claude/commands/code-task.en.md +1 -1
  283. package/templates/.claude/commands/code-task.zh-CN.md +1 -1
  284. package/templates/.claude/commands/commit.en.md +1 -1
  285. package/templates/.claude/commands/commit.zh-CN.md +1 -1
  286. package/templates/.claude/commands/complete-manual-validation.en.md +1 -1
  287. package/templates/.claude/commands/complete-manual-validation.zh-CN.md +1 -1
  288. package/templates/.claude/commands/complete-task.en.md +1 -1
  289. package/templates/.claude/commands/complete-task.zh-CN.md +1 -1
  290. package/templates/.claude/commands/create-pr.en.md +1 -1
  291. package/templates/.claude/commands/create-pr.zh-CN.md +1 -1
  292. package/templates/.claude/commands/plan-task.en.md +1 -1
  293. package/templates/.claude/commands/plan-task.zh-CN.md +1 -1
  294. package/templates/.claude/commands/review-analysis.en.md +1 -1
  295. package/templates/.claude/commands/review-analysis.zh-CN.md +1 -1
  296. package/templates/.claude/commands/review-code.en.md +1 -1
  297. package/templates/.claude/commands/review-code.zh-CN.md +1 -1
  298. package/templates/.claude/commands/review-plan.en.md +1 -1
  299. package/templates/.claude/commands/review-plan.zh-CN.md +1 -1
  300. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  301. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  302. package/templates/.claude/commands/run-task.en.md +1 -1
  303. package/templates/.claude/commands/run-task.zh-CN.md +1 -1
  304. package/templates/.claude/commands/watch-pr.en.md +1 -1
  305. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  306. package/templates/.traecli/commands/update-agent-infra.en.md +7 -0
  307. package/templates/.traecli/commands/update-agent-infra.zh-CN.md +7 -0
  308. package/dist/lib/builtin-tuis.js +0 -40
  309. package/lib/builtin-tuis.ts +0 -56
package/dist/lib/merge.js CHANGED
@@ -1,11 +1,10 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { info, ok } from './log.js';
4
3
  import { removeDirRecursive } from './remove-dir.js';
4
+ import { validateCurrentTaskContract } from './task/current-contract.js';
5
5
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
6
6
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
7
7
  const TITLE_RE = /^# (.+)$/m;
8
- const DATE_FROM_PATH_RE = /(?:^|[/\\])(\d{4})[/\\](\d{2})[/\\](\d{2})(?:[/\\]|$)/;
9
8
  const MUTABLE_SECTIONS = ['active', 'blocked', 'completed'];
10
9
  const ALL_SECTIONS = [...MUTABLE_SECTIONS, 'archive'];
11
10
  const SECTION_LABELS = {
@@ -64,36 +63,6 @@ function normalizeTaskRecord(taskDir, taskFile, dateParts) {
64
63
  completedAt: taskDate
65
64
  };
66
65
  }
67
- function fallbackDateParts(taskDir, content) {
68
- const pathMatch = taskDir.match(DATE_FROM_PATH_RE);
69
- if (pathMatch) {
70
- const [, year, month, day] = pathMatch;
71
- if (!year || !month || !day) {
72
- return null;
73
- }
74
- return {
75
- year,
76
- month,
77
- day
78
- };
79
- }
80
- const completedAt = extractField(content, 'completed_at');
81
- const updatedAt = extractField(content, 'updated_at');
82
- const source = completedAt || updatedAt;
83
- const dateMatch = source?.match(/^(\d{4})-(\d{2})-(\d{2})/);
84
- if (dateMatch) {
85
- const [, year, month, day] = dateMatch;
86
- if (!year || !month || !day) {
87
- return null;
88
- }
89
- return {
90
- year,
91
- month,
92
- day
93
- };
94
- }
95
- return null;
96
- }
97
66
  function scanSourceTasks(sourceDir) {
98
67
  const tasks = [];
99
68
  const years = fs.existsSync(sourceDir) ? fs.readdirSync(sourceDir, { withFileTypes: true }) : [];
@@ -130,37 +99,6 @@ function scanSourceTasks(sourceDir) {
130
99
  }
131
100
  }
132
101
  }
133
- if (tasks.length > 0) {
134
- return tasks;
135
- }
136
- // Fall back to a deeper scan if the source layout is unusual but still contains archived tasks.
137
- const stack = [sourceDir];
138
- while (stack.length > 0) {
139
- const currentDir = stack.pop();
140
- if (!currentDir || !fs.existsSync(currentDir)) {
141
- continue;
142
- }
143
- for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
144
- const entryPath = path.join(currentDir, entry.name);
145
- if (!entry.isDirectory()) {
146
- continue;
147
- }
148
- if (TASK_ID_RE.test(entry.name)) {
149
- const taskFile = path.join(entryPath, 'task.md');
150
- if (!fs.existsSync(taskFile)) {
151
- continue;
152
- }
153
- const content = fs.readFileSync(taskFile, 'utf8');
154
- const dateParts = fallbackDateParts(entryPath, content);
155
- if (!dateParts) {
156
- continue;
157
- }
158
- tasks.push(normalizeTaskRecord(entryPath, taskFile, dateParts));
159
- continue;
160
- }
161
- stack.push(entryPath);
162
- }
163
- }
164
102
  return tasks.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
165
103
  }
166
104
  function findTaskDirById(rootDir, taskId) {
@@ -470,14 +408,200 @@ function copyTaskToSection(sourceTask, workspaceDir) {
470
408
  fs.cpSync(sourceTask.taskDir, destinationDir, { recursive: true });
471
409
  return destinationDir;
472
410
  }
411
+ function invalidSource(message) {
412
+ throw new Error(`Invalid merge source: ${message}`);
413
+ }
414
+ function sourceRelativePath(sourceRoot, targetPath) {
415
+ return path.relative(sourceRoot, targetPath).split(path.sep).join('/') || '.';
416
+ }
417
+ function isMissingPathError(error) {
418
+ return Boolean(error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT');
419
+ }
420
+ function assertSourcePathContained(sourceRoot, targetPath) {
421
+ let sourceRealPath;
422
+ let targetRealPath;
423
+ try {
424
+ sourceRealPath = fs.realpathSync(sourceRoot);
425
+ targetRealPath = fs.realpathSync(targetPath);
426
+ }
427
+ catch (error) {
428
+ invalidSource(`${sourceRelativePath(sourceRoot, targetPath)} cannot be resolved: ${error instanceof Error ? error.message : String(error)}`);
429
+ }
430
+ const relativePath = path.relative(sourceRealPath, targetRealPath);
431
+ if (relativePath === '..' || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) {
432
+ invalidSource(`${sourceRelativePath(sourceRoot, targetPath)} resolves outside the source workspace`);
433
+ }
434
+ }
435
+ function sourcePathStat(sourceRoot, targetPath) {
436
+ let stat;
437
+ try {
438
+ stat = fs.lstatSync(targetPath);
439
+ }
440
+ catch (error) {
441
+ if (isMissingPathError(error)) {
442
+ return null;
443
+ }
444
+ throw error;
445
+ }
446
+ if (stat.isSymbolicLink()) {
447
+ invalidSource(`${sourceRelativePath(sourceRoot, targetPath)} must not be a symbolic link`);
448
+ }
449
+ assertSourcePathContained(sourceRoot, targetPath);
450
+ return stat;
451
+ }
452
+ function validateTaskTree(sourceRoot, taskDir) {
453
+ for (const entry of fs.readdirSync(taskDir, { withFileTypes: true })) {
454
+ const entryPath = path.join(taskDir, entry.name);
455
+ const entryStat = sourcePathStat(sourceRoot, entryPath);
456
+ if (!entryStat) {
457
+ invalidSource(`${sourceRelativePath(sourceRoot, entryPath)} disappeared during validation`);
458
+ }
459
+ if (entryStat.isDirectory()) {
460
+ validateTaskTree(sourceRoot, entryPath);
461
+ continue;
462
+ }
463
+ if (!entryStat.isFile()) {
464
+ invalidSource(`${sourceRelativePath(sourceRoot, entryPath)} must be a regular file or directory`);
465
+ }
466
+ }
467
+ }
468
+ function validateSourceRoot(sourceRoot) {
469
+ const stat = sourcePathStat(sourceRoot, sourceRoot);
470
+ if (!stat || !stat.isDirectory()) {
471
+ invalidSource('source path must be a real directory');
472
+ }
473
+ }
474
+ function validateTaskDirectory(sourceRoot, taskDir, seenTaskIds) {
475
+ const taskId = path.basename(taskDir);
476
+ const taskFile = path.join(taskDir, 'task.md');
477
+ const taskDirStat = sourcePathStat(sourceRoot, taskDir);
478
+ if (!taskDirStat || !taskDirStat.isDirectory()) {
479
+ invalidSource(`${sourceRelativePath(sourceRoot, taskDir)} must be a directory`);
480
+ }
481
+ const taskFileStat = sourcePathStat(sourceRoot, taskFile);
482
+ if (!taskFileStat || !taskFileStat.isFile()) {
483
+ invalidSource(`${sourceRelativePath(sourceRoot, taskDir)} requires a regular task.md file`);
484
+ }
485
+ const content = fs.readFileSync(taskFile, 'utf8');
486
+ const contract = validateCurrentTaskContract(content);
487
+ if (!contract.ok) {
488
+ invalidSource(`${sourceRelativePath(sourceRoot, taskFile)} is not a valid current task: ${contract.message}`);
489
+ }
490
+ if (contract.metadata.id !== taskId) {
491
+ invalidSource(`${sourceRelativePath(sourceRoot, taskFile)} id '${String(contract.metadata.id ?? 'missing')}' does not match directory '${taskId}'`);
492
+ }
493
+ validateTaskTree(sourceRoot, taskDir);
494
+ const previousPath = seenTaskIds.get(taskId);
495
+ if (previousPath) {
496
+ invalidSource(`duplicate task ID ${taskId} at ${previousPath} and ${sourceRelativePath(sourceRoot, taskDir)}`);
497
+ }
498
+ seenTaskIds.set(taskId, sourceRelativePath(sourceRoot, taskDir));
499
+ }
500
+ function validateMutableSection(sourceRoot, section, seenTaskIds) {
501
+ const sectionDir = path.join(sourceRoot, section);
502
+ const sectionStat = sourcePathStat(sourceRoot, sectionDir);
503
+ if (!sectionStat) {
504
+ return;
505
+ }
506
+ if (!sectionStat.isDirectory()) {
507
+ invalidSource(`${section} must be a directory`);
508
+ }
509
+ for (const entry of fs.readdirSync(sectionDir, { withFileTypes: true })) {
510
+ const entryPath = path.join(sectionDir, entry.name);
511
+ const entryStat = sourcePathStat(sourceRoot, entryPath);
512
+ if (!entryStat) {
513
+ continue;
514
+ }
515
+ if (!entryStat.isDirectory()) {
516
+ if (TASK_ID_RE.test(entry.name)) {
517
+ invalidSource(`${sourceRelativePath(sourceRoot, entryPath)} is not a directory`);
518
+ }
519
+ continue;
520
+ }
521
+ if (!TASK_ID_RE.test(entry.name)) {
522
+ invalidSource(`${sourceRelativePath(sourceRoot, entryPath)} is not a direct TASK-YYYYMMDD-HHMMSS directory`);
523
+ }
524
+ validateTaskDirectory(sourceRoot, entryPath, seenTaskIds);
525
+ }
526
+ }
527
+ function validateArchiveSection(sourceRoot, seenTaskIds) {
528
+ const archiveDir = path.join(sourceRoot, 'archive');
529
+ const archiveStat = sourcePathStat(sourceRoot, archiveDir);
530
+ if (!archiveStat) {
531
+ return;
532
+ }
533
+ if (!archiveStat.isDirectory()) {
534
+ invalidSource('archive must be a directory');
535
+ }
536
+ const allowManifest = (directory, entry) => {
537
+ const entryPath = path.join(directory, entry.name);
538
+ const entryStat = sourcePathStat(sourceRoot, entryPath);
539
+ return Boolean(entryStat?.isFile() && entry.name === 'manifest.md');
540
+ };
541
+ const years = fs.readdirSync(archiveDir, { withFileTypes: true });
542
+ for (const yearEntry of years) {
543
+ const yearPath = path.join(archiveDir, yearEntry.name);
544
+ if (allowManifest(archiveDir, yearEntry)) {
545
+ continue;
546
+ }
547
+ const yearStat = sourcePathStat(sourceRoot, yearPath);
548
+ if (!yearStat?.isDirectory() || !/^\d{4}$/.test(yearEntry.name)) {
549
+ invalidSource(`${sourceRelativePath(sourceRoot, yearPath)} is not a YYYY directory or manifest.md`);
550
+ }
551
+ const yearDir = yearPath;
552
+ for (const monthEntry of fs.readdirSync(yearDir, { withFileTypes: true })) {
553
+ const monthPath = path.join(yearDir, monthEntry.name);
554
+ if (allowManifest(yearDir, monthEntry)) {
555
+ continue;
556
+ }
557
+ const monthStat = sourcePathStat(sourceRoot, monthPath);
558
+ if (!monthStat?.isDirectory() || !/^\d{2}$/.test(monthEntry.name)) {
559
+ invalidSource(`${sourceRelativePath(sourceRoot, monthPath)} is not a MM directory or manifest.md`);
560
+ }
561
+ const monthDir = monthPath;
562
+ for (const dayEntry of fs.readdirSync(monthDir, { withFileTypes: true })) {
563
+ const dayPath = path.join(monthDir, dayEntry.name);
564
+ if (allowManifest(monthDir, dayEntry)) {
565
+ continue;
566
+ }
567
+ const dayStat = sourcePathStat(sourceRoot, dayPath);
568
+ if (!dayStat?.isDirectory() || !/^\d{2}$/.test(dayEntry.name)) {
569
+ invalidSource(`${sourceRelativePath(sourceRoot, dayPath)} is not a DD directory or manifest.md`);
570
+ }
571
+ const dayDir = dayPath;
572
+ for (const taskEntry of fs.readdirSync(dayDir, { withFileTypes: true })) {
573
+ const taskPath = path.join(dayDir, taskEntry.name);
574
+ const taskStat = sourcePathStat(sourceRoot, taskPath);
575
+ if (!taskStat?.isDirectory() || !TASK_ID_RE.test(taskEntry.name)) {
576
+ invalidSource(`${sourceRelativePath(sourceRoot, taskPath)} is not a TASK-YYYYMMDD-HHMMSS directory`);
577
+ }
578
+ validateTaskDirectory(sourceRoot, taskPath, seenTaskIds);
579
+ }
580
+ }
581
+ }
582
+ }
583
+ }
584
+ function validateSourceWorkspace(sourcePath) {
585
+ validateSourceRoot(sourcePath);
586
+ const seenTaskIds = new Map();
587
+ for (const section of MUTABLE_SECTIONS) {
588
+ validateMutableSection(sourcePath, section, seenTaskIds);
589
+ }
590
+ validateArchiveSection(sourcePath, seenTaskIds);
591
+ }
473
592
  function detectSourceMode(sourcePath) {
593
+ validateSourceRoot(sourcePath);
474
594
  for (const section of ALL_SECTIONS) {
475
595
  const sectionDir = path.join(sourcePath, section);
476
- if (fs.existsSync(sectionDir) && fs.statSync(sectionDir).isDirectory()) {
596
+ const sectionStat = sourcePathStat(sourcePath, sectionDir);
597
+ if (sectionStat?.isDirectory()) {
477
598
  return 'workspace';
478
599
  }
600
+ if (sectionStat) {
601
+ invalidSource(`${section} must be a directory`);
602
+ }
479
603
  }
480
- return 'legacy-archive';
604
+ invalidSource('expected a current workspace containing active, blocked, completed, or archive sections');
481
605
  }
482
606
  function createReport(sourcePath, backupRoot) {
483
607
  return {
@@ -606,24 +730,6 @@ function mergeArchiveSection(sourceArchive, localArchive, report) {
606
730
  }
607
731
  return sourceTasks.length;
608
732
  }
609
- function printLegacyArchiveMessages(report, sourcePath) {
610
- const merged = report.sections.archive.copied;
611
- const skipped = report.sections.archive.skipped;
612
- if (merged.length === 0 && skipped.length === 0) {
613
- info(`No archived tasks found in ${sourcePath}`);
614
- }
615
- for (const task of merged) {
616
- ok(`Merged ${task.taskId} -> ${task.relativePath}`);
617
- }
618
- for (const task of skipped) {
619
- info(`Skipped ${task.taskId} (already exists at ${task.relativePath})`);
620
- }
621
- process.stdout.write('\n');
622
- info('Merge summary');
623
- info(`- Merged: ${merged.length}`);
624
- info(`- Skipped: ${skipped.length}`);
625
- process.stdout.write('\n');
626
- }
627
733
  function printSection(lines, name, counts) {
628
734
  const title = `${SECTION_LABELS[name].padEnd(9, ' ')} (.agents/workspace/${name}/):`;
629
735
  lines.push(title);
@@ -709,7 +815,10 @@ async function cmdMerge(args) {
709
815
  if (!fs.existsSync(resolvedSource)) {
710
816
  throw new Error(`Source path does not exist: ${sourcePath}`);
711
817
  }
712
- if (!fs.statSync(resolvedSource).isDirectory()) {
818
+ if (fs.lstatSync(resolvedSource).isSymbolicLink()) {
819
+ throw new Error(`Invalid merge source: source path must be a real directory: ${sourcePath}`);
820
+ }
821
+ if (!fs.lstatSync(resolvedSource).isDirectory()) {
713
822
  throw new Error(`Source path is not a directory: ${sourcePath}`);
714
823
  }
715
824
  const workspaceDir = path.join(process.cwd(), '.agents', 'workspace');
@@ -718,30 +827,23 @@ async function cmdMerge(args) {
718
827
  const backupRootRelative = `.agents/workspace/.merge-backup/${backupStamp}/`;
719
828
  const backupRoot = path.join(workspaceDir, '.merge-backup', backupStamp);
720
829
  const report = createReport(resolvedSource, backupRootRelative);
721
- const mode = detectSourceMode(resolvedSource);
830
+ detectSourceMode(resolvedSource);
831
+ validateSourceWorkspace(resolvedSource);
722
832
  for (const section of ALL_SECTIONS) {
723
833
  fs.mkdirSync(path.join(workspaceDir, section), { recursive: true });
724
834
  }
725
- if (mode === 'legacy-archive') {
726
- info('Detected legacy archive source; treating the input as archive-only for backward compatibility.');
727
- mergeArchiveSection(resolvedSource, archiveDir, report);
728
- }
729
- else {
730
- mergeMutableSections({
731
- sourceWorkspace: resolvedSource,
732
- localWorkspace: workspaceDir,
733
- backupRoot,
734
- report
735
- });
736
- const sourceArchive = path.join(resolvedSource, 'archive');
737
- if (fs.existsSync(sourceArchive) && fs.statSync(sourceArchive).isDirectory()) {
738
- mergeArchiveSection(sourceArchive, archiveDir, report);
739
- }
835
+ mergeMutableSections({
836
+ sourceWorkspace: resolvedSource,
837
+ localWorkspace: workspaceDir,
838
+ backupRoot,
839
+ report
840
+ });
841
+ const sourceArchive = path.join(resolvedSource, 'archive');
842
+ const sourceArchiveStat = sourcePathStat(resolvedSource, sourceArchive);
843
+ if (sourceArchiveStat?.isDirectory()) {
844
+ mergeArchiveSection(sourceArchive, archiveDir, report);
740
845
  }
741
846
  rebuildManifests(archiveDir);
742
- if (mode === 'legacy-archive') {
743
- printLegacyArchiveMessages(report, sourcePath);
744
- }
745
847
  printReport(report);
746
848
  }
747
849
  export { cmdMerge, compareTimestamps, detectSourceMode, extractField, extractTitle, formatBackupTimestamp, getTaskTimestamp, rebuildManifests, scanSourceTasks, scanWorkspaceSection, taskExistsInArchive };
@@ -1,4 +1,5 @@
1
1
  import { sha256 } from './store.js';
2
+ import { normalizeAgentToken } from '../agent-clients/tokens.js';
2
3
  const TASK_ID_RE = /TASK-\d{8}-\d{6}/;
3
4
  const ACTIVITY_RE = /^- (\d{4}-\d{2}-\d{2} [^ ]+) — \*\*(.+?)\*\* by ([^ ]+) — (.+)$/gm;
4
5
  const LEDGER_RE = /^\|\s*((?:AN|PL|CD)-\d+)\s*\|\s*(analysis|plan|code)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|$/gm;
@@ -9,15 +10,7 @@ function recordId(kind, identity, suffix = '') {
9
10
  }
10
11
  function canonicalActor(raw) {
11
12
  const lowered = raw.toLowerCase();
12
- const aliases = {
13
- 'claude-code': 'claude',
14
- 'antigravity-cli': 'antigravity',
15
- 'gemini-cli': 'gemini',
16
- gemini: 'gemini'
17
- };
18
- if (['claude', 'codex', 'antigravity', 'opencode', 'cursor', 'human'].includes(lowered))
19
- return lowered;
20
- return aliases[lowered] ?? null;
13
+ return normalizeAgentToken(lowered);
21
14
  }
22
15
  function nextOccurrence(occurrences, identity) {
23
16
  const occurrence = (occurrences.get(identity) ?? 0) + 1;
@@ -1,26 +1,55 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- function resolveCommand(file) {
5
- if (process.platform !== 'win32' || path.extname(file)) {
6
- return file;
7
- }
8
- const pathValue = process.env.Path || process.env.PATH || '';
9
- const extensions = (process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean);
10
- for (const dir of pathValue.split(path.delimiter).filter(Boolean)) {
4
+ function resolveCommandDetails(file, platform = process.platform, env = process.env) {
5
+ if (path.isAbsolute(file))
6
+ return { path: file, source: 'absolute' };
7
+ const pathValue = platform === 'win32'
8
+ ? env.Path || env.PATH || ''
9
+ : env.PATH || '';
10
+ const extensions = platform === 'win32'
11
+ ? (path.extname(file)
12
+ ? ['']
13
+ : (env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean))
14
+ : [''];
15
+ for (const dir of pathValue.split(path.delimiter)) {
11
16
  for (const extension of extensions) {
12
- const lowerCandidate = path.join(dir, `${file}${extension.toLowerCase()}`);
13
- if (fs.existsSync(lowerCandidate))
14
- return lowerCandidate;
15
- const upperCandidate = path.join(dir, `${file}${extension.toUpperCase()}`);
16
- if (fs.existsSync(upperCandidate))
17
- return upperCandidate;
17
+ const candidates = extension
18
+ ? [`${file}${extension.toLowerCase()}`, `${file}${extension.toUpperCase()}`]
19
+ : [file];
20
+ for (const candidateName of candidates) {
21
+ const candidate = path.resolve(dir || '.', candidateName);
22
+ try {
23
+ const stat = fs.statSync(candidate);
24
+ if (!stat.isFile())
25
+ continue;
26
+ if (platform !== 'win32' && (stat.mode & 0o111) === 0)
27
+ continue;
28
+ return {
29
+ path: candidate,
30
+ source: dir ? 'path' : 'current-directory',
31
+ pathEntry: dir
32
+ };
33
+ }
34
+ catch {
35
+ // Keep searching the remaining PATH entries.
36
+ }
37
+ }
18
38
  }
19
39
  }
20
- return file;
40
+ return { path: file, source: 'unresolved' };
41
+ }
42
+ function resolveCommand(file, platform = process.platform, env = process.env) {
43
+ return resolveCommandDetails(file, platform, env).path;
44
+ }
45
+ function resolveCommandFromAbsolutePath(file, platform = process.platform, env = process.env) {
46
+ const resolution = resolveCommandDetails(file, platform, env);
47
+ return resolution.source === 'path' && resolution.pathEntry && path.isAbsolute(resolution.pathEntry)
48
+ ? resolution.path
49
+ : null;
21
50
  }
22
- function needsShell(file) {
23
- return process.platform === 'win32' && /\.(?:bat|cmd)$/i.test(file);
51
+ function needsShell(file, platform = process.platform) {
52
+ return platform === 'win32' && /\.(?:bat|cmd)$/i.test(file);
24
53
  }
25
54
  export async function runHostCommand(command) {
26
55
  const [file, ...args] = command;
@@ -36,4 +65,5 @@ export async function runHostCommand(command) {
36
65
  child.on('close', (exitCode, signal) => resolve({ exitCode, signal }));
37
66
  });
38
67
  }
68
+ export { needsShell, resolveCommand, resolveCommandFromAbsolutePath };
39
69
  //# sourceMappingURL=host.js.map
@@ -1,11 +1,12 @@
1
1
  import { renderAgentClientInvocation } from '../agent-clients/invocation.js';
2
2
  import { getAgentClientAdapter } from '../agent-clients/registry.js';
3
- const TUI_NAMES = new Set(['claude', 'codex', 'antigravity', 'opencode']);
3
+ const TUI_NAMES = new Set(['claude', 'codex', 'antigravity', 'opencode', 'traecli']);
4
4
  const TUI_AGENT_CLIENT_IDS = Object.freeze({
5
5
  claude: 'claude-code',
6
6
  codex: 'codex',
7
7
  antigravity: 'antigravity-cli',
8
- opencode: 'opencode'
8
+ opencode: 'opencode',
9
+ traecli: 'traecli'
9
10
  });
10
11
  function isTuiName(value) {
11
12
  return typeof value === 'string' && TUI_NAMES.has(value);
@@ -33,6 +34,8 @@ export function buildTuiCommand(tui, prompt) {
33
34
  return ['agy', ['--dangerously-skip-permissions', '--print', prompt]];
34
35
  if (tui === 'opencode')
35
36
  return ['opencode', ['run', '--dangerously-skip-permissions', prompt]];
37
+ if (tui === 'traecli')
38
+ return ['traecli', ['exec', '--dangerously-bypass-approvals-and-sandbox', prompt]];
36
39
  return ['codex', ['exec', '--dangerously-bypass-approvals-and-sandbox', prompt]];
37
40
  }
38
41
  //# sourceMappingURL=tui.js.map
@@ -15,15 +15,6 @@ function agentInfraTool(home) {
15
15
  setupHint: 'Provides the ai and agent-infra CLI commands inside the sandbox.'
16
16
  };
17
17
  }
18
- function compatAgentClientState(tools) {
19
- return Object.fromEntries(listAgentClientAdapters().map((adapter) => [
20
- adapter.id,
21
- {
22
- enabled: true,
23
- installInSandbox: tools.includes(adapter.id)
24
- }
25
- ]));
26
- }
27
18
  function stableValue(value) {
28
19
  if (Array.isArray(value)) {
29
20
  return value.map(stableValue);
@@ -96,7 +87,7 @@ function hooksByPhase(adapters) {
96
87
  ])));
97
88
  }
98
89
  function createSandboxCapabilityPlan(config) {
99
- const state = config.agentClientState ?? compatAgentClientState(config.tools);
90
+ const state = config.agentClientState;
100
91
  const selectedAgentClients = listInstalledAgentClientAdapters(state);
101
92
  const context = { home: config.home, project: config.project };
102
93
  const selectedTools = selectedAgentClients.map((adapter) => adapter.sandbox.createTool(context));
@@ -113,8 +104,9 @@ function createSandboxCapabilityPlan(config) {
113
104
  const nonClientTools = [];
114
105
  const selectedCustomTools = [];
115
106
  for (const id of config.tools) {
116
- if (isAgentClientId(id))
117
- continue;
107
+ if (isAgentClientId(id)) {
108
+ throw new Error(`Agent Client '${id}' must be provided by agentClientState`);
109
+ }
118
110
  if (id === 'agent-infra') {
119
111
  if (!nonClientTools.some((tool) => tool.id === id)) {
120
112
  nonClientTools.push(agentInfraTool(config.home));
@@ -17,7 +17,7 @@ import { parseSandboxWorkspaceIdentity, resolveSandboxTarget, sameSandboxWorkspa
17
17
  import { createSandboxCapabilityPlan, runSandboxHooks, runBoundedSandboxHookCommand } from '../agent-client-reconciler.js';
18
18
  import { resolveTools, tmpfsSeedStagingPath, tmpfsSeedTargetPath, toolConfigDirCandidates } from '../tools.js';
19
19
  import { assertFreshSandboxReady, hydrateTmpfsSeedEntries, prepareTmpfsMounts, startSandboxControlBroker } from '../recovery.js';
20
- import { acquireSandboxControlReplacement, assertSandboxControlCutoverSnapshot, beginSandboxControlReplacement, captureSandboxControlCutoverSnapshot, commitSandboxControlReplacement, quiesceSandboxControlRoot, recoverSandboxControlReplacement, readSandboxControlManifestForTransition } from '../control/lifecycle.js';
20
+ import { acquireSandboxControlReplacement, assertSandboxControlCutoverSnapshot, beginSandboxControlReplacement, captureSandboxControlCutoverSnapshot, commitSandboxControlReplacement, quiesceSandboxControlRoot, recoverSandboxControlReplacement, readSandboxControlManifest } from '../control/lifecycle.js';
21
21
  import { inspectSandboxControlContainer } from '../control/container-identity.js';
22
22
  import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.js';
23
23
  import { sandboxCoreBindMounts } from '../mounts.js';
@@ -879,7 +879,7 @@ export async function create(args) {
879
879
  const recoveryResult = await recoverSandboxControlReplacement(controlPaths.root, replacementLease);
880
880
  const hadExistingControlRoot = hadControlRootBeforeAcquire || recoveryResult === 'restored';
881
881
  const previousManifest = fs.existsSync(controlPaths.manifestPath)
882
- ? readSandboxControlManifestForTransition(controlPaths.manifestPath)
882
+ ? readSandboxControlManifest(controlPaths.manifestPath)
883
883
  : null;
884
884
  if (hadExistingControlRoot) {
885
885
  await quiesceSandboxControlRoot(controlPaths.root);
@@ -1031,7 +1031,7 @@ export async function create(args) {
1031
1031
  ...(target.workspace.mode === 'task-bound' ? { runtimeDir: control.runtimeDir } : {}),
1032
1032
  ...(target.workspace.mode === 'task-bound'
1033
1033
  ? {
1034
- taskSource: assertSandboxTaskSource(effectiveConfig.repoRoot, target.workspace.taskId),
1034
+ taskSources: [assertSandboxTaskSource(effectiveConfig.repoRoot, target.workspace.taskId)],
1035
1035
  taskId: target.workspace.taskId
1036
1036
  }
1037
1037
  : {})
@@ -1,5 +1,5 @@
1
1
  import { loadConfig } from '../config.js';
2
- import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel } from '../constants.js';
2
+ import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel, sandboxTaskIdLabel, sandboxWorkspaceModeLabel } from '../constants.js';
3
3
  import { detectEngine } from '../engine.js';
4
4
  import { ensureSandboxReady } from '../recovery.js';
5
5
  import { createSandboxCapabilityPlan, runBoundedSandboxHookCommand, runSandboxHooks } from '../agent-client-reconciler.js';
@@ -9,7 +9,7 @@ import { redactCommandError } from '../redaction.js';
9
9
  import { runInteractiveWithClipboardBridge } from '../clipboard/bridge.js';
10
10
  import { detectHostTimezone } from '../host-timezone.js';
11
11
  import { fetchSandboxRows, selectSandboxContainer, } from './list-running.js';
12
- import { resolveSandboxTarget } from '../workspace-identity.js';
12
+ import { resolveSandboxReentryContext, resolveSandboxTarget } from '../workspace-identity.js';
13
13
  const USAGE = `Usage: ai sandbox exec [--recreate] <branch | TASK-id | N> [cmd...]
14
14
 
15
15
  N references an active task short id from
@@ -81,16 +81,30 @@ export async function enter(args) {
81
81
  const target = resolveSandboxTarget(parsed.target, config.repoRoot);
82
82
  const branch = target.branch;
83
83
  assertValidBranchName(branch);
84
- const { running, nonRunning } = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
84
+ const { running, nonRunning } = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config), {
85
+ mode: sandboxWorkspaceModeLabel(config),
86
+ taskId: sandboxTaskIdLabel(config)
87
+ });
85
88
  const found = selectSandboxContainer([...running, ...nonRunning], containerNameCandidates(config, branch));
86
89
  if (!found) {
87
90
  throw new Error(`No sandbox found for branch '${branch}'. Run 'ai sandbox create ${branch}' to create one.`);
88
91
  }
92
+ const containerWorkspace = found.workspaceMode === 'task-bound' && found.taskId
93
+ ? { mode: 'task-bound', taskId: found.taskId }
94
+ : found.workspaceMode === 'branch-only'
95
+ ? { mode: 'branch-only' }
96
+ : { mode: 'legacy-invalid' };
97
+ const reentry = resolveSandboxReentryContext({
98
+ target,
99
+ containerWorkspace,
100
+ repoRoot: config.repoRoot
101
+ });
89
102
  const ready = await ensureSandboxReady({
90
103
  config,
91
104
  engine,
92
105
  branch,
93
- workspace: target.workspace,
106
+ workspace: reentry.workspace,
107
+ reentry: reentry.reentry,
94
108
  row: found,
95
109
  allowRecreate: parsed.recreate,
96
110
  recreate: async () => {
@@ -4,7 +4,6 @@ import { homedir, platform } from 'node:os';
4
4
  import { execFileSync } from 'node:child_process';
5
5
  import pc from 'picocolors';
6
6
  import { normalizeAgentClients } from '../agent-clients/config.js';
7
- import { isAgentClientId } from '../agent-clients/types.js';
8
7
  import { validateSandboxEngine } from './engine.js';
9
8
  import { hostJoin } from './engines/wsl2-paths.js';
10
9
  import { findRuntimeEngineMismatches } from './runtime-engines.js';
@@ -48,15 +47,9 @@ function cloneDefaults() {
48
47
  vm: { ...DEFAULTS.vm }
49
48
  };
50
49
  }
51
- function resolveSandboxToolIds(sandbox, agentClients, defaults) {
50
+ function resolveSandboxToolIds(sandbox, defaults) {
52
51
  if (Array.isArray(sandbox.tools)) {
53
- const tools = sandbox.tools.filter((tool) => typeof tool === 'string');
54
- return agentClients.source === 'canonical'
55
- ? tools.filter((tool) => !isAgentClientId(tool))
56
- : tools;
57
- }
58
- if (agentClients.remainingSandboxTools !== undefined) {
59
- return [...agentClients.remainingSandboxTools];
52
+ return sandbox.tools.filter((tool) => typeof tool === 'string');
60
53
  }
61
54
  return [...defaults];
62
55
  }
@@ -71,7 +64,6 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
71
64
  throw new Error('No .agents/.airc.json found. Run "ai init" first.');
72
65
  }
73
66
  const airc = JSON.parse(fs.readFileSync(configPath, 'utf8'));
74
- const sandboxInput = airc.sandbox;
75
67
  const agentClients = normalizeAgentClients(airc);
76
68
  const defaults = cloneDefaults();
77
69
  const sandbox = airc.sandbox ?? {};
@@ -99,7 +91,7 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
99
91
  }
100
92
  }
101
93
  const customTools = parseCustomTools(sandbox.customTools, { home });
102
- const tools = resolveSandboxToolIds(sandbox, agentClients, defaults.tools);
94
+ const tools = resolveSandboxToolIds(sandbox, defaults.tools);
103
95
  return {
104
96
  repoRoot,
105
97
  configPath,
@@ -119,7 +111,6 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
119
111
  tools,
120
112
  customTools,
121
113
  agentClientState: agentClients.state,
122
- agentClientSource: agentClients.source,
123
114
  refreshIntervalDays: asNonNegativeIntegerOrDefault(sandbox.refreshIntervalDays, defaults.refreshIntervalDays),
124
115
  dockerfile,
125
116
  vm: {