@co0ontty/wand 4.106.0 → 4.106.1

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 (279) hide show
  1. package/README.md +2 -2
  2. package/dist/ai-text-runner.js +24 -14
  3. package/dist/build-info.d.ts +2 -0
  4. package/dist/build-info.js +18 -0
  5. package/dist/build-info.json +3 -3
  6. package/dist/cli.js +8 -2
  7. package/dist/daemon-maintenance-state.js +1 -1
  8. package/dist/daemon-maintenance-targets.d.ts +1 -0
  9. package/dist/daemon-maintenance-targets.js +25 -9
  10. package/dist/daemon-maintenance.d.ts +3 -0
  11. package/dist/daemon-maintenance.js +60 -13
  12. package/dist/employee-im-types.d.ts +1 -1
  13. package/dist/employee-input-scheduling.d.ts +4 -0
  14. package/dist/employee-knowledge-cli.js +5 -5
  15. package/dist/employee-knowledge.d.ts +3 -1
  16. package/dist/employee-knowledge.js +24 -8
  17. package/dist/git-quick-commit.d.ts +2 -0
  18. package/dist/git-quick-commit.js +15 -2
  19. package/dist/git-worktree-batch.d.ts +26 -0
  20. package/dist/git-worktree-batch.js +139 -0
  21. package/dist/global-knowledge.d.ts +12 -0
  22. package/dist/global-knowledge.js +124 -0
  23. package/dist/iteration-log.d.ts +13 -5
  24. package/dist/iteration-log.js +120 -59
  25. package/dist/knowledge-types.d.ts +12 -1
  26. package/dist/knowledge-types.js +4 -0
  27. package/dist/middleware/api-scope.js +1 -0
  28. package/dist/model-groups.js +6 -3
  29. package/dist/models/parsers/opencode.js +31 -1
  30. package/dist/models.js +15 -14
  31. package/dist/npm-update-utils.d.ts +5 -2
  32. package/dist/npm-update-utils.js +34 -4
  33. package/dist/opencode-cli.d.ts +6 -0
  34. package/dist/opencode-cli.js +51 -0
  35. package/dist/opencode-system-fallback.d.ts +1 -0
  36. package/dist/opencode-system-fallback.js +16 -4
  37. package/dist/pi-native-event-bridge.js +55 -3
  38. package/dist/pi-native-input.d.ts +22 -0
  39. package/dist/pi-native-input.js +77 -0
  40. package/dist/pi-tmux-host.d.ts +14 -5
  41. package/dist/pi-tmux-host.js +68 -17
  42. package/dist/process-manager.js +17 -3
  43. package/dist/pty-tmux-backend.d.ts +27 -2
  44. package/dist/pty-tmux-backend.js +471 -70
  45. package/dist/pty-tmux-log.d.ts +1 -1
  46. package/dist/pty-tmux-log.js +23 -2
  47. package/dist/retention-protection.d.ts +1 -1
  48. package/dist/retention-protection.js +3 -2
  49. package/dist/retention.d.ts +5 -0
  50. package/dist/retention.js +11 -0
  51. package/dist/server-knowledge-routes.js +2 -2
  52. package/dist/server-session-input-routes.js +38 -19
  53. package/dist/server-session-lifecycle-routes.js +14 -1
  54. package/dist/server-session-worktree-routes.js +105 -12
  55. package/dist/server-task-routes.js +41 -12
  56. package/dist/server-workspace-routes.js +77 -9
  57. package/dist/server.js +67 -35
  58. package/dist/session-registry.d.ts +17 -3
  59. package/dist/session-registry.js +66 -13
  60. package/dist/session-service.js +1 -1
  61. package/dist/storage/internal.d.ts +4 -3
  62. package/dist/storage/internal.js +3 -3
  63. package/dist/storage/knowledge-repository.d.ts +5 -0
  64. package/dist/storage/knowledge-repository.js +75 -16
  65. package/dist/storage/mappers.d.ts +1 -0
  66. package/dist/storage/mappers.js +6 -1
  67. package/dist/storage/migrations.js +60 -0
  68. package/dist/storage/session-input-repository.d.ts +19 -0
  69. package/dist/storage/session-input-repository.js +35 -0
  70. package/dist/storage/task-workflow-repository.d.ts +29 -0
  71. package/dist/storage/task-workflow-repository.js +110 -0
  72. package/dist/storage/tasks-repository.d.ts +6 -1
  73. package/dist/storage/tasks-repository.js +94 -34
  74. package/dist/storage/workspaces-repository.d.ts +7 -0
  75. package/dist/storage/workspaces-repository.js +28 -2
  76. package/dist/storage-cleanup.d.ts +7 -2
  77. package/dist/storage-cleanup.js +16 -0
  78. package/dist/storage-schema.js +2 -0
  79. package/dist/storage.d.ts +5 -1
  80. package/dist/storage.js +12 -0
  81. package/dist/structured-exec-host.d.ts +4 -0
  82. package/dist/structured-exec-pump.d.ts +1 -0
  83. package/dist/structured-exec-pump.js +18 -11
  84. package/dist/structured-opencode-adapter.d.ts +2 -1
  85. package/dist/structured-opencode-adapter.js +32 -8
  86. package/dist/structured-pi-adapter.js +13 -2
  87. package/dist/structured-runner.d.ts +6 -0
  88. package/dist/structured-session-manager-replay.d.ts +2 -0
  89. package/dist/structured-session-manager-replay.js +27 -0
  90. package/dist/structured-session-manager.d.ts +32 -21
  91. package/dist/structured-session-manager.js +395 -118
  92. package/dist/structured-tmux-host.d.ts +23 -2
  93. package/dist/structured-tmux-host.js +143 -20
  94. package/dist/task-actions.js +5 -3
  95. package/dist/task-archive-policy.js +2 -1
  96. package/dist/task-comment-dispatch.d.ts +2 -1
  97. package/dist/task-comment-dispatch.js +19 -6
  98. package/dist/task-execution-queue.d.ts +2 -0
  99. package/dist/task-execution-queue.js +10 -10
  100. package/dist/task-hierarchy.js +1 -1
  101. package/dist/task-read-model.d.ts +1 -1
  102. package/dist/task-read-model.js +2 -1
  103. package/dist/task-types.d.ts +34 -3
  104. package/dist/task-types.js +21 -5
  105. package/dist/task-workflow-cli.d.ts +2 -0
  106. package/dist/task-workflow-cli.js +29 -0
  107. package/dist/task-workflow-runner.d.ts +4 -0
  108. package/dist/task-workflow-runner.js +36 -0
  109. package/dist/task-workflow.d.ts +13 -0
  110. package/dist/task-workflow.js +48 -0
  111. package/dist/terminal-daemon-server.js +12 -4
  112. package/dist/tmux-policy.d.ts +9 -0
  113. package/dist/tmux-policy.js +46 -0
  114. package/dist/tmux-terminal-host.js +5 -2
  115. package/dist/tui/commands.js +2 -1
  116. package/dist/types.d.ts +5 -0
  117. package/dist/wand-task-sync.js +5 -0
  118. package/dist/web-ui/content/ai-teams.js +9 -9
  119. package/dist/web-ui/content/module-manifest.json +1 -1
  120. package/dist/web-ui/content/modules/{52574891ef08e29b → 014fa5c6f973badf}/host.js +3 -3
  121. package/dist/web-ui/content/modules/0818b1d31a11d006/employee-profile.js +1 -0
  122. package/dist/web-ui/content/modules/{31157ceb6d23b809 → 0a58d9ec273078e4}/chunk.js +3 -3
  123. package/dist/web-ui/content/modules/{bf3ee58e0da44822 → 0a979dcd165cd0cc}/chunk.js +1 -1
  124. package/dist/web-ui/content/modules/{5f298a8f43dd0589 → 0c143928e8f96abc}/chunk.js +1 -1
  125. package/dist/web-ui/content/modules/0e9e42a319f6b8fc/workspace-window.js +1 -0
  126. package/dist/web-ui/content/modules/{b713cad51ca4c512 → 0f0d1769b732c0bd}/chunk.js +1 -1
  127. package/dist/web-ui/content/modules/{4970319a83432ec7 → 1447886e6a1e74a4}/chunk.js +1 -1
  128. package/dist/web-ui/content/modules/151c734cbca31da5/host.js +1 -0
  129. package/dist/web-ui/content/modules/{e5174201f9d918fb → 16e4ab9c5263fcfd}/chunk.js +1 -1
  130. package/dist/web-ui/content/modules/17e752fb53952753/host.js +1 -0
  131. package/dist/web-ui/content/modules/{913187c92a98a1ac → 20ed1454fc09fa6f}/chunk.js +1 -1
  132. package/dist/web-ui/content/modules/255a3baf59d2b734/home.js +6 -0
  133. package/dist/web-ui/content/modules/{2f801e53dda62770 → 296dd81886c42b45}/chunk.js +1 -1
  134. package/dist/web-ui/content/modules/{8891ca8165435ca1 → 2a01f4e525c0725e}/chunk.js +1 -1
  135. package/dist/web-ui/content/modules/2ab4409c82b44fba/markdown.js +1 -0
  136. package/dist/web-ui/content/modules/2c955eced11610c2/chunk.js +1 -0
  137. package/dist/web-ui/content/modules/2d7ee4f9e2c0aca4/panel.js +1 -0
  138. package/dist/web-ui/content/modules/{3f8830f8ad28decc → 33ae550e4a295c85}/chunk.js +1 -1
  139. package/dist/web-ui/content/modules/35f4aa4443696bd0/chunk.js +1 -0
  140. package/dist/web-ui/content/modules/388b37ffcce9d4ff/host.js +1 -0
  141. package/dist/web-ui/content/modules/39279604dcdac7c7/runtime.js +338 -0
  142. package/dist/web-ui/content/modules/{57c357d321db5df9 → 3a3cdff2a662b7d3}/chunk.js +1 -1
  143. package/dist/web-ui/content/modules/{d49f106c4d106057 → 3ab646be55514243}/chunk.js +1 -1
  144. package/dist/web-ui/content/modules/{16dab1297efd71a7 → 3ae88a5c5e1949d2}/chunk.js +1 -1
  145. package/dist/web-ui/content/modules/3e3b0e71d6ce80d8/chunk.js +1 -0
  146. package/dist/web-ui/content/modules/{ec33e17c00c1eb24 → 434aaaaf9604dc34}/chunk.js +1 -1
  147. package/dist/web-ui/content/modules/4385cd48771e1d4f/host.js +1 -0
  148. package/dist/web-ui/content/modules/{face3dbf117d63e3 → 4638129d60f549a0}/chunk.js +1 -1
  149. package/dist/web-ui/content/modules/47407209931b7011/host.js +13 -0
  150. package/dist/web-ui/content/modules/4829dd712d8b366b/chunk.js +1 -0
  151. package/dist/web-ui/content/modules/488d5b747bf374c8/chunk.js +1 -0
  152. package/dist/web-ui/content/modules/{ed7405f064944215 → 4ab085c195f7d76a}/sha256.js +1 -1
  153. package/dist/web-ui/content/modules/{f27a5d3c6c02a71c → 4c485bd44740fec7}/chunk.js +1 -1
  154. package/dist/web-ui/content/modules/{771439a41e737ee2 → 4ca0494308ab2bb3}/chunk.js +1 -1
  155. package/dist/web-ui/content/modules/4ec571218779ba3e/composer-surface-adapter.js +1 -0
  156. package/dist/web-ui/content/modules/{5933ce8965aa4004 → 5023bf0294c2e7cb}/chunk.js +1 -1
  157. package/dist/web-ui/content/modules/{e63582a3bce48f7d → 5456e3417600bf15}/chunk.js +1 -1
  158. package/dist/web-ui/content/modules/55c588b0f5b41a52/host-bridge.js +1 -0
  159. package/dist/web-ui/content/modules/{9d941d6ff39861b8 → 5c3c8237ba7d8460}/chunk.js +1 -1
  160. package/dist/web-ui/content/modules/624b858da9834757/host.js +1 -0
  161. package/dist/web-ui/content/modules/6290be936320d4b1/host.js +1 -0
  162. package/dist/web-ui/content/modules/66be408793048cc3/progress.js +1 -0
  163. package/dist/web-ui/content/modules/67a11176a563317b/chunk.js +1 -0
  164. package/dist/web-ui/content/modules/{e060508ecd50fa7e → 68ef6ec7d26d78cd}/chunk.js +1 -1
  165. package/dist/web-ui/content/modules/{41445c6589dba325 → 6ec3e41df31927d9}/host.js +2 -2
  166. package/dist/web-ui/content/modules/{97d7f267959fd385 → 6f10f369445bbcbe}/chunk.js +1 -1
  167. package/dist/web-ui/content/modules/{d47d75a78ae598e3 → 7295d9ff92d5e1e7}/chunk.js +1 -1
  168. package/dist/web-ui/content/modules/{f383d892fef73483 → 72cd613de1a5f69b}/chunk.js +1 -1
  169. package/dist/web-ui/content/modules/73aae1daafeea7e7/workspaces-panel.js +4 -0
  170. package/dist/web-ui/content/modules/{c2f09c8a472f5d83 → 73d2689f13b9a3e9}/dialog-modal.js +1 -1
  171. package/dist/web-ui/content/modules/{2f97ddba3842adaa → 75cf0c048e9b92ab}/chunk.js +1 -1
  172. package/dist/web-ui/content/modules/{4ba1e8c74bed7ef2 → 75dabda3b141296e}/chunk.js +1 -1
  173. package/dist/web-ui/content/modules/{f3cff7792ba39df8 → 76c10b58d5f92d02}/login-visual.js +1 -1
  174. package/dist/web-ui/content/modules/{b63e075f222a1266 → 783583a12f5010f8}/library-bridge.js +1 -1
  175. package/dist/web-ui/content/modules/7e13a8a4a981f2f1/group-editor.js +1 -0
  176. package/dist/web-ui/content/modules/{0d76e582a7a71b2f → 7e3034fea34c6dae}/chunk.js +1 -1
  177. package/dist/web-ui/content/modules/80bf3291f2577a68/host.js +1 -0
  178. package/dist/web-ui/content/modules/{cba6f13c0b2aec1c → 82c3e08f34a6b828}/chunk.js +1 -1
  179. package/dist/web-ui/content/modules/{dedd4a19bb70e327 → 83d1f4cf82c65f19}/page.js +3 -3
  180. package/dist/web-ui/content/modules/844f97958f25240c/shell-file-panel-drawer.js +1 -0
  181. package/dist/web-ui/content/modules/{62e0023451f428cf → 899cdc467044d418}/chunk.js +1 -1
  182. package/dist/web-ui/content/modules/{5083a5dbb5b155b7 → 8c7ca1fe72741c07}/chunk.js +1 -1
  183. package/dist/web-ui/content/modules/8dae4ca2261525e4/task-board-host.js +27 -0
  184. package/dist/web-ui/content/modules/92195c14c74929da/list-filter-panel.js +1 -0
  185. package/dist/web-ui/content/modules/{a61393bfa976d8d5 → 93793696af76bcbf}/chunk.js +1 -1
  186. package/dist/web-ui/content/modules/{db521a246e14e5e4 → 97cd6af3da5622b8}/chunk.js +4 -4
  187. package/dist/web-ui/content/modules/{8de8368570ab089d → 9d3179258249ec77}/chunk.js +1 -1
  188. package/dist/web-ui/content/modules/{579d486763d47c0d → a234c484078c0568}/chunk.js +1 -1
  189. package/dist/web-ui/content/modules/a4d215448a089a7d/host.js +1 -0
  190. package/dist/web-ui/content/modules/{c0a446100d969f3e → ae003f89901c5687}/chunk.js +1 -1
  191. package/dist/web-ui/content/modules/ae712bdd45b9917e/employee-card.js +1 -0
  192. package/dist/web-ui/content/modules/{a2caed0139df4b21 → b2a15293f7965d09}/chunk.js +1 -1
  193. package/dist/web-ui/content/modules/b4ee65e9998f0088/host.js +61 -0
  194. package/dist/web-ui/content/modules/{bd3daf04a8bf4422 → b666137d17f83be9}/chunk.js +1 -1
  195. package/dist/web-ui/content/modules/{831b6c7b43e800be → b66beb6c0185256b}/host.js +1 -1
  196. package/dist/web-ui/content/modules/{816eb9b3a000fb2c → bb03b17e877657de}/chunk.js +1 -1
  197. package/dist/web-ui/content/modules/{1eab7410d5586038 → bc343afc991db1a1}/chunk.js +1 -1
  198. package/dist/web-ui/content/modules/bca45976ff8fe2ac/host.js +69 -0
  199. package/dist/web-ui/content/modules/{d434edbb2f4672e5 → c2715dbfc9eaf510}/chunk.js +1 -1
  200. package/dist/web-ui/content/modules/{d6fffe2c2a3a738d → c30c220281f35d74}/toast-region.js +1 -1
  201. package/dist/web-ui/content/modules/c4cc46fa9b4adddc/dropdown-surface.js +1 -0
  202. package/dist/web-ui/content/modules/c6dd19ee3d3ffd59/brand-identity.js +1 -0
  203. package/dist/web-ui/content/modules/c7ea6a114e6ff8ae/host.js +1 -0
  204. package/dist/web-ui/content/modules/{a59734be368daa7e → c916d82c8acef9c8}/chunk.js +1 -1
  205. package/dist/web-ui/content/modules/{182929e188c1ab3a → cceaedda619a737a}/host.js +1 -1
  206. package/dist/web-ui/content/modules/cf67f52b076b9d2d/panel-host.js +1 -0
  207. package/dist/web-ui/content/modules/{9e163d54ff8fd1aa → d26c86558d422313}/chunk.js +1 -1
  208. package/dist/web-ui/content/modules/d2b8c6c6f3706032/queue-view-adapter.js +1 -0
  209. package/dist/web-ui/content/modules/d52ebe2ef12a9fa8/chunk.js +3 -0
  210. package/dist/web-ui/content/modules/{3fe11a75284ad8ae → d552b3bff2151eb6}/host.js +2 -2
  211. package/dist/web-ui/content/modules/d5d6b4511ac76a6e/chunk.js +3 -0
  212. package/dist/web-ui/content/modules/{bd7dc4bb46731ee7 → df42e151d625463b}/chunk.js +1 -1
  213. package/dist/web-ui/content/modules/{2a791cd2e14aa1e0 → e0a04f5ae56c68aa}/notice-view.js +1 -1
  214. package/dist/web-ui/content/modules/{438807b8bf112cd0 → e28e607c95d6f63a}/chunk.js +1 -1
  215. package/dist/web-ui/content/modules/{50da446c1377c220 → e3880df97ea62d0c}/chunk.js +1 -1
  216. package/dist/web-ui/content/modules/{87c7f50d9455d55e → e66196f1ece3e3ad}/chunk.js +1 -1
  217. package/dist/web-ui/content/modules/{9b12c2301f3a18f3 → eb76307444ac9d71}/chunk.js +1 -1
  218. package/dist/web-ui/content/modules/{c33935155289c22d → ebff7ca22cee1468}/chunk.js +1 -1
  219. package/dist/web-ui/content/modules/{4861eca9a8a87ad0 → ed1c712c569960d6}/chunk.js +1 -1
  220. package/dist/web-ui/content/modules/ee8b9c2d4bec63d4/chunk.js +1 -0
  221. package/dist/web-ui/content/modules/f458a194ce4208e2/chunk.js +1 -0
  222. package/dist/web-ui/content/modules/{11af5d14b30e57b6 → f938e826420cfe1b}/chunk.js +1 -1
  223. package/dist/web-ui/content/modules/fa7b9bf6cc84bfc1/chat-render.js +81 -0
  224. package/dist/web-ui/content/modules/{ea8fe4b9999fd2aa → fd1fb1834f5f06df}/chunk.js +1 -1
  225. package/dist/web-ui/content/modules/fd3084e562a3dbd6/host.js +1 -0
  226. package/dist/web-ui/content/styles.css +1 -1
  227. package/dist/web-ui/embedded-assets.d.ts +416 -413
  228. package/dist/web-ui/embedded-assets.js +5 -5
  229. package/dist/web-ui/page-route.js +2 -2
  230. package/dist/web-ui/react/workspaces/types.d.ts +3 -0
  231. package/dist/web-ui/thinking-efforts.d.ts +2 -0
  232. package/dist/web-ui/thinking-efforts.js +8 -0
  233. package/package.json +1 -2
  234. package/scripts/stage-render-binaries.js +0 -1
  235. package/skills/wand-knowledge/SKILL.md +5 -3
  236. package/dist/structured-bounded-keys.d.ts +0 -19
  237. package/dist/structured-bounded-keys.js +0 -47
  238. package/dist/web-ui/content/modules/00b168a19664439e/dropdown-surface.js +0 -1
  239. package/dist/web-ui/content/modules/059c5fb7f128d340/task-board-host.js +0 -27
  240. package/dist/web-ui/content/modules/07494f5a623e4471/chat-render.js +0 -76
  241. package/dist/web-ui/content/modules/0a743ad3b4db6874/chunk.js +0 -3
  242. package/dist/web-ui/content/modules/1858670c0f84fec5/employee-profile.js +0 -1
  243. package/dist/web-ui/content/modules/20c47dc0442f5a04/host.js +0 -1
  244. package/dist/web-ui/content/modules/226241b999d12008/host.js +0 -1
  245. package/dist/web-ui/content/modules/23c4b8d223ec64c5/host.js +0 -61
  246. package/dist/web-ui/content/modules/2882c97388ff4f7b/employee-card.js +0 -1
  247. package/dist/web-ui/content/modules/2c90c3be260e6cce/progress.js +0 -1
  248. package/dist/web-ui/content/modules/2d7be9935394f397/workspace-window.js +0 -1
  249. package/dist/web-ui/content/modules/389a5112332ec23a/host.js +0 -1
  250. package/dist/web-ui/content/modules/3ea13e8b3c2deaa4/panel-host.js +0 -1
  251. package/dist/web-ui/content/modules/42e5c34002fcffb7/chunk.js +0 -1
  252. package/dist/web-ui/content/modules/4a5d669c90f545cb/host.js +0 -1
  253. package/dist/web-ui/content/modules/5705d25468274534/host.js +0 -1
  254. package/dist/web-ui/content/modules/5961ab2947c86ca1/shell-file-panel-drawer.js +0 -1
  255. package/dist/web-ui/content/modules/5b6e1369df7dea69/chunk.js +0 -1
  256. package/dist/web-ui/content/modules/6787bdcf56f743a5/host.js +0 -1
  257. package/dist/web-ui/content/modules/67c39b0398186a9f/host.js +0 -1
  258. package/dist/web-ui/content/modules/6b16f35c0627bafb/chunk.js +0 -1
  259. package/dist/web-ui/content/modules/6dd4f3e2f619f7a0/chunk.js +0 -1
  260. package/dist/web-ui/content/modules/70a2e13e0ccc8549/panel.js +0 -1
  261. package/dist/web-ui/content/modules/710db590e9707f73/runtime.js +0 -338
  262. package/dist/web-ui/content/modules/7249950f8771dbeb/workspaces-panel.js +0 -4
  263. package/dist/web-ui/content/modules/87dcd516ffd9f688/composer-surface-adapter.js +0 -1
  264. package/dist/web-ui/content/modules/8b7cd7bffdfd69e7/brand-identity.js +0 -1
  265. package/dist/web-ui/content/modules/8bf9917f6c3429f6/host.js +0 -1
  266. package/dist/web-ui/content/modules/8e612d4881fa4118/home.js +0 -6
  267. package/dist/web-ui/content/modules/8e6178262ea63dec/markdown.js +0 -1
  268. package/dist/web-ui/content/modules/96f2ae970f8c576b/host.js +0 -1
  269. package/dist/web-ui/content/modules/ba89cd97627e6682/queue-view-adapter.js +0 -1
  270. package/dist/web-ui/content/modules/c351eb11e0bdec2c/sidebar-peek.js +0 -1
  271. package/dist/web-ui/content/modules/ca7ca5902a35d26e/chunk.js +0 -1
  272. package/dist/web-ui/content/modules/d5d28ec375826ef2/chunk.js +0 -3
  273. package/dist/web-ui/content/modules/d6b16dedb9011c33/chunk.js +0 -1
  274. package/dist/web-ui/content/modules/d8a7011760f61719/group-editor.js +0 -1
  275. package/dist/web-ui/content/modules/d9e2b65e9679ad39/host.js +0 -75
  276. package/dist/web-ui/content/modules/eac72f69716dba05/chunk.js +0 -1
  277. package/dist/web-ui/content/modules/ebc53dedc3945514/host.js +0 -1
  278. package/dist/web-ui/content/modules/f43da7050badf54c/host.js +0 -1
  279. package/dist/web-ui/content/modules/fb2356eced24c4cc/host-bridge.js +0 -1
@@ -1,2 +1,2 @@
1
1
  /** Executed inside tmux's pipe-pane process. No launch environment or prompt is persisted. */
2
- export declare const ptyTmuxLogWriter = "\nconst fs=require('node:fs'),path=require('node:path');\nconst directory=process.argv[1];\nconst limit=1024*1024,keep=8;\nlet segment=0,size=0,carry=Buffer.alloc(0);\nlet fd=fs.openSync(path.join(directory,'0.chunk'),'a',0o600);\nprocess.stdin.on('data',data=>{\n const scan=Buffer.concat([carry,data]);\n const start=scan.lastIndexOf(Buffer.from('\\x1eWAND_CLI_EXIT:'));\n if(start>=0){const end=scan.indexOf(31,start);if(end>=0){\n const marker=scan.subarray(start,end+1);\n fs.writeFileSync(path.join(directory,'marker.tmp'),marker,{mode:0o600});\n fs.renameSync(path.join(directory,'marker.tmp'),path.join(directory,'marker'));\n }}\n carry=scan.subarray(Math.max(0,scan.length-512));\n for(let offset=0;offset<data.length;){\n if(size>=limit){fs.closeSync(fd);segment++;size=0;\n fd=fs.openSync(path.join(directory,segment+'.chunk'),'a',0o600);\n if(segment>=keep)fs.unlinkSync(path.join(directory,(segment-keep)+'.chunk'));\n }\n const length=Math.min(limit-size,data.length-offset);\n fs.writeSync(fd,data,offset,length);offset+=length;size+=length;\n }\n});\nprocess.stdin.on('end',()=>{fs.closeSync(fd);});\n";
2
+ export declare const ptyTmuxLogWriter = "\nconst fs=require('node:fs'),path=require('node:path'),net=require('node:net');\nconst directory=process.argv[1];\nconst sockPath=process.argv[2];\nconst limit=1024*1024,keep=8;\nlet segment=0,size=0,carry=Buffer.alloc(0);\nlet fd=fs.openSync(path.join(directory,'0.chunk'),'a',0o600);\nconst clients=new Set();\nlet server=null;\nif(sockPath){\n try{fs.unlinkSync(sockPath);}catch(e){}\n server=net.createServer(socket=>{\n try{socket.write(JSON.stringify({segment,size})+'\\n');}catch(e){}\n clients.add(socket);\n socket.on('close',()=>{clients.delete(socket);});\n socket.on('error',()=>{clients.delete(socket);try{socket.destroy();}catch(e){}});\n });\n server.listen(sockPath,()=>{try{fs.chmodSync(sockPath,0o600);}catch(e){}});\n process.on('exit',()=>{try{fs.unlinkSync(sockPath);}catch(e){}});\n}\nprocess.stdin.on('data',data=>{\n const scan=Buffer.concat([carry,data]);\n const start=scan.lastIndexOf(Buffer.from('\\x1eWAND_CLI_EXIT:'));\n if(start>=0){const end=scan.indexOf(31,start);if(end>=0){\n const marker=scan.subarray(start,end+1);\n fs.writeFileSync(path.join(directory,'marker.tmp'),marker,{mode:0o600});\n fs.renameSync(path.join(directory,'marker.tmp'),path.join(directory,'marker'));\n }}\n carry=scan.subarray(Math.max(0,scan.length-512));\n for(let offset=0;offset<data.length;){\n if(size>=limit){fs.closeSync(fd);segment++;size=0;\n fd=fs.openSync(path.join(directory,segment+'.chunk'),'a',0o600);\n if(segment>=keep)fs.unlinkSync(path.join(directory,(segment-keep)+'.chunk'));\n }\n const length=Math.min(limit-size,data.length-offset);\n fs.writeSync(fd,data,offset,length);offset+=length;size+=length;\n }\n for(const client of clients){\n try{client.write(data);}catch(e){}\n }\n});\nprocess.stdin.on('end',()=>{\n try{fs.closeSync(fd);}catch(e){}\n for(const client of clients){try{client.end();}catch(e){}}\n if(server){try{server.close(()=>{try{fs.unlinkSync(sockPath);}catch(e){}});}catch(e){}}\n});\n";
@@ -1,10 +1,24 @@
1
1
  /** Executed inside tmux's pipe-pane process. No launch environment or prompt is persisted. */
2
2
  export const ptyTmuxLogWriter = `
3
- const fs=require('node:fs'),path=require('node:path');
3
+ const fs=require('node:fs'),path=require('node:path'),net=require('node:net');
4
4
  const directory=process.argv[1];
5
+ const sockPath=process.argv[2];
5
6
  const limit=1024*1024,keep=8;
6
7
  let segment=0,size=0,carry=Buffer.alloc(0);
7
8
  let fd=fs.openSync(path.join(directory,'0.chunk'),'a',0o600);
9
+ const clients=new Set();
10
+ let server=null;
11
+ if(sockPath){
12
+ try{fs.unlinkSync(sockPath);}catch(e){}
13
+ server=net.createServer(socket=>{
14
+ try{socket.write(JSON.stringify({segment,size})+'\\n');}catch(e){}
15
+ clients.add(socket);
16
+ socket.on('close',()=>{clients.delete(socket);});
17
+ socket.on('error',()=>{clients.delete(socket);try{socket.destroy();}catch(e){}});
18
+ });
19
+ server.listen(sockPath,()=>{try{fs.chmodSync(sockPath,0o600);}catch(e){}});
20
+ process.on('exit',()=>{try{fs.unlinkSync(sockPath);}catch(e){}});
21
+ }
8
22
  process.stdin.on('data',data=>{
9
23
  const scan=Buffer.concat([carry,data]);
10
24
  const start=scan.lastIndexOf(Buffer.from('\\x1eWAND_CLI_EXIT:'));
@@ -22,6 +36,13 @@ process.stdin.on('data',data=>{
22
36
  const length=Math.min(limit-size,data.length-offset);
23
37
  fs.writeSync(fd,data,offset,length);offset+=length;size+=length;
24
38
  }
39
+ for(const client of clients){
40
+ try{client.write(data);}catch(e){}
41
+ }
42
+ });
43
+ process.stdin.on('end',()=>{
44
+ try{fs.closeSync(fd);}catch(e){}
45
+ for(const client of clients){try{client.end();}catch(e){}}
46
+ if(server){try{server.close(()=>{try{fs.unlinkSync(sockPath);}catch(e){}});}catch(e){}}
25
47
  });
26
- process.stdin.on('end',()=>{fs.closeSync(fd);});
27
48
  `;
@@ -1,5 +1,5 @@
1
1
  import type { WandStorage } from "./storage.js";
2
- import type { WandTask } from "./task-types.js";
2
+ import { type WandTask } from "./task-types.js";
3
3
  import type { SessionSnapshot } from "./types.js";
4
4
  /** Read all ownership/execution facts before the first destructive action. Never loads transcripts or settings. */
5
5
  export declare function createRetentionProtection(storage: WandStorage, sessions: readonly SessionSnapshot[], archivedMs: number | null, now: number): {
@@ -1,10 +1,11 @@
1
1
  import { AI_TEAM_ACTIVE_RUN_STATUSES } from "./ai-team-types.js";
2
2
  import { isArchiveBlocked } from "./archive-policy.js";
3
+ import { taskHumanAccepted } from "./task-types.js";
3
4
  import { TASK_RETENTION_ARCHIVE_VERSION } from "./task-retention.js";
4
5
  import { parseTimestampMs } from "./time-utils.js";
5
6
  function acceptedTask(task) {
6
7
  const acceptance = task.acceptance;
7
- return (task.status === "done" || task.status === "archived")
8
+ return taskHumanAccepted(task) && (task.status === "ready" || task.status === "archived")
8
9
  && typeof acceptance?.acceptedAt === "string" && parseTimestampMs(acceptance.acceptedAt) !== null
9
10
  && typeof acceptance.evidence === "string" && !!acceptance.evidence.trim()
10
11
  && typeof acceptance.blocker === "string" && !acceptance.blocker.trim();
@@ -123,7 +124,7 @@ export function createRetentionProtection(storage, sessions, archivedMs, now) {
123
124
  const unfinishedAncestors = spread(ancestors, unfinished);
124
125
  return {
125
126
  tasks, runs, teamSessionIds,
126
- canArchiveTask: (task) => task.status === "done" && acceptedTask(task)
127
+ canArchiveTask: (task) => task.status === "ready" && taskHumanAccepted(task)
127
128
  && !executionHeld.has(taskNode(task.id)) && !unfinishedAncestors.has(taskNode(task.id)),
128
129
  canPurgeTask: (id) => !purgeHeld.has(taskNode(id)),
129
130
  canArchiveSession: (id) => !archiveHeld.has(sessionNode(id)),
@@ -15,6 +15,11 @@ export interface RetentionDeps {
15
15
  sessions: SessionRegistry;
16
16
  /** 每次扫描读取。传入函数才能在设置保存后立刻用新窗口,而不是启动时的快照。 */
17
17
  taskRetention?: TaskRetentionSettings | (() => TaskRetentionSettings | undefined);
18
+ /**
19
+ * 归档或删除之后清掉不再属于任何未归档会话的 CLI 原生历史。
20
+ * 覆盖升级前就已归档、当时还没写清理的存量;本轮刚归档的 id 由调用方排除。
21
+ */
22
+ releaseArchivedProviderHistory?: (sessionIds: readonly string[]) => void;
18
23
  }
19
24
  export interface RetentionResult {
20
25
  archivedSessions: number;
package/dist/retention.js CHANGED
@@ -122,6 +122,17 @@ export function runRetentionSweep(deps, now = Date.now()) {
122
122
  result.archivedTasks += 1;
123
123
  }
124
124
  sweepTeamRuns(deps, protection, taskWindows, snapshots, purgedSessionIds, archivedSessionIds, now, result);
125
+ // 本轮刚归档的会话已经由 setArchived 清过 CLI 历史。这里只补更早的存量,
126
+ // 避免和本轮写入抢同一批文件。
127
+ const staleArchived = snapshots
128
+ .filter((session) => session.archived && !archivedSessionIds.has(session.id) && !purgedSessionIds.has(session.id))
129
+ .map((session) => session.id);
130
+ if (staleArchived.length > 0) {
131
+ try {
132
+ deps.releaseArchivedProviderHistory?.(staleArchived);
133
+ }
134
+ catch { /* 清理失败不回滚归档 */ }
135
+ }
125
136
  return result;
126
137
  }
127
138
  function archiveRetentionSession(deps, session, archivedSessionIds, result) {
@@ -6,8 +6,8 @@ function fields(value) {
6
6
  }
7
7
  function namespace(value) {
8
8
  const data = fields(value);
9
- if (data.scope !== "personal" && data.scope !== "project")
10
- throw new KnowledgeError("请选择个人或项目知识库。");
9
+ if (data.scope !== "global" && data.scope !== "personal" && data.scope !== "project")
10
+ throw new KnowledgeError("请选择全局、个人或项目知识库。");
11
11
  if (typeof data.ownerId !== "string" || !data.ownerId.trim() || data.ownerId.length > 200)
12
12
  throw new KnowledgeError("请选择员工或项目。");
13
13
  return { scope: data.scope, ownerId: data.ownerId };
@@ -36,6 +36,8 @@ export function registerSessionInputRoutes(app, ctx) {
36
36
  // 让退出 handler 不要把剩余 queuedMessages 清空(默认行为是清空)。
37
37
  const preserveQueue = !!req.body?.preserveQueue;
38
38
  const idempotencyKey = typeof req.body?.idempotencyKey === "string" ? req.body.idempotencyKey : undefined;
39
+ let inputReceipt;
40
+ let inputAccepted = false;
39
41
  try {
40
42
  const session = structured.get(req.params.id);
41
43
  if (!session) {
@@ -43,19 +45,22 @@ export function registerSessionInputRoutes(app, ctx) {
43
45
  return;
44
46
  }
45
47
  const snapshot = await structured.sendMessage(req.params.id, input, {
48
+ onAccepted: receipt => { inputAccepted = true; inputReceipt = receipt; },
46
49
  interrupt,
47
50
  preserveQueue,
48
51
  idempotencyKey,
49
52
  source: "user",
50
53
  });
51
- res.json(sessionResponseDTO(snapshot, req));
54
+ res.json({ ...sessionResponseDTO(snapshot, req), ...(inputReceipt ? { inputReceipt } : {}) });
52
55
  }
53
56
  catch (error) {
54
57
  const errorCode = error?.code;
55
- const status = errorCode === "duplicate_idempotency_key" || errorCode === "duplicate_queued_message" ? 409 : 400;
58
+ const status = inputAccepted ? 503
59
+ : errorCode === "idempotency_key_conflict" || errorCode === "duplicate_idempotency_key" || errorCode === "duplicate_queued_message" ? 409 : 400;
56
60
  res.status(status).json({
57
61
  error: getErrorMessage(error, "无法发送结构化消息。"),
58
- errorCode,
62
+ errorCode: inputAccepted ? "INPUT_DELIVERY_UNCONFIRMED" : errorCode,
63
+ ...(inputReceipt ? { inputReceipt } : {}),
59
64
  });
60
65
  }
61
66
  }));
@@ -73,7 +78,7 @@ export function registerSessionInputRoutes(app, ctx) {
73
78
  res.json(sessionResponseDTO(snapshot, req));
74
79
  }
75
80
  catch (error) {
76
- sendRouteError(res, surfaceDomainError(error, /^(排序长度与当前队列不一致,请刷新后重试。|排序参数无效。|排队消息已变化,请刷新后重试。)$/) ?? error, "无法调整排队顺序。");
81
+ sendRouteError(res, surfaceDomainError(error, /^(排序长度与当前队列不一致,请刷新后重试。|排序参数无效。|排队消息已变化,请刷新后重试。|排队消息正在交接,请按最新状态重试。)$/) ?? error, "无法调整排队顺序。");
77
82
  }
78
83
  });
79
84
  app.patch("/api/structured-sessions/:id/queued/:index", (req, res) => {
@@ -86,7 +91,7 @@ export function registerSessionInputRoutes(app, ctx) {
86
91
  res.json(sessionResponseDTO(structured.editQueuedMessage(req.params.id, Number(req.params.index), expectedText, text), req));
87
92
  }
88
93
  catch (error) {
89
- sendRouteError(res, surfaceDomainError(error, /^(排队消息已变化,请刷新后重试。|排队消息不能为空。)$/) ?? error, "无法编辑排队消息。");
94
+ sendRouteError(res, surfaceDomainError(error, /^(排队消息已变化,请刷新后重试。|排队消息不能为空。|排队消息正在交接,请按最新状态重试。)$/) ?? error, "无法编辑排队消息。");
90
95
  }
91
96
  });
92
97
  app.delete("/api/structured-sessions/:id/queued/:index", (req, res) => {
@@ -101,7 +106,7 @@ export function registerSessionInputRoutes(app, ctx) {
101
106
  res.json(sessionResponseDTO(snapshot, req));
102
107
  }
103
108
  catch (error) {
104
- sendRouteError(res, surfaceDomainError(error, /^(队列中没有该条消息(可能已被处理)。|排队消息已变化,请按最新顺序重试。)$/) ?? error, "无法删除排队消息。");
109
+ sendRouteError(res, surfaceDomainError(error, /^(队列中没有该条消息(可能已被处理)。|排队消息已变化,请按最新顺序重试。|排队消息正在交接,请按最新状态重试。)$/) ?? error, "无法删除排队消息。");
105
110
  }
106
111
  });
107
112
  app.post("/api/structured-sessions/:id/queued/:index/promote", asyncRoute(async (req, res) => {
@@ -112,16 +117,22 @@ export function registerSessionInputRoutes(app, ctx) {
112
117
  }
113
118
  const expectedText = typeof req.body?.expectedText === "string" ? req.body.expectedText : undefined;
114
119
  const idempotencyKey = typeof req.body?.idempotencyKey === "string" ? req.body.idempotencyKey : undefined;
120
+ let inputAccepted = false;
121
+ let inputReceipt;
115
122
  try {
116
- const snapshot = await structured.promoteQueuedMessage(req.params.id, index, expectedText, idempotencyKey);
117
- res.json(sessionResponseDTO(snapshot, req));
123
+ const snapshot = await structured.promoteQueuedMessage(req.params.id, index, expectedText, idempotencyKey, receipt => {
124
+ inputAccepted = true;
125
+ inputReceipt = receipt;
126
+ });
127
+ res.json({ ...sessionResponseDTO(snapshot, req), ...(inputReceipt ? { inputReceipt } : {}) });
118
128
  }
119
129
  catch (error) {
120
130
  const errorCode = error?.code;
121
- const status = errorCode === "duplicate_idempotency_key" ? 409 : 400;
131
+ const status = inputAccepted ? 503 : errorCode === "idempotency_key_conflict" || errorCode === "duplicate_idempotency_key" ? 409 : 400;
122
132
  res.status(status).json({
123
133
  error: getErrorMessage(error, "无法立即发送排队消息。"),
124
- errorCode,
134
+ errorCode: inputAccepted ? "INPUT_DELIVERY_UNCONFIRMED" : errorCode,
135
+ ...(inputReceipt ? { inputReceipt } : {}),
125
136
  });
126
137
  }
127
138
  }));
@@ -141,16 +152,21 @@ export function registerSessionInputRoutes(app, ctx) {
141
152
  const input = body.input ?? "";
142
153
  const view = body.view;
143
154
  const shortcutKey = body.shortcutKey;
155
+ let inputReceipt;
156
+ let inputAccepted = false;
144
157
  try {
145
158
  if (structured.get(sessionId)) {
146
- const completion = structured.sendMessage(sessionId, input, { source: "user" });
159
+ let confirmAcceptance;
160
+ const acceptance = new Promise(resolve => { confirmAcceptance = resolve; });
161
+ const completion = structured.sendMessage(sessionId, input, { source: "user",
162
+ idempotencyKey: body.idempotencyKey,
163
+ onAccepted: receipt => { inputAccepted = true; inputReceipt = receipt; confirmAcceptance(); },
164
+ });
147
165
  if (body.respondImmediately === true || (structured.get(sessionId)?.provider === "pi"
148
166
  && structured.get(sessionId)?.piSettings?.autoResources === true)) {
149
- // sendMessage updates the canonical snapshot synchronously before it
150
- // starts awaiting the runner. Native clients should not hold this
151
- // request open for an entire model turn (which can exceed their HTTP
152
- // timeout, especially while the first-turn title job is also active).
153
- // The normal structured events continue to carry progress/failure.
167
+ // Reject before returning 202 if validation/storage did not accept the
168
+ // input. After acceptance, execution failures arrive on the event stream.
169
+ await Promise.race([acceptance, completion]);
154
170
  completion.catch((error) => {
155
171
  console.error("[wand] Accepted structured input later failed", {
156
172
  sessionId,
@@ -161,11 +177,11 @@ export function registerSessionInputRoutes(app, ctx) {
161
177
  if (!accepted) {
162
178
  throw badRequest("未找到该结构化会话。");
163
179
  }
164
- res.status(202).json(sessionResponseDTO(accepted, req));
180
+ res.status(202).json({ ...sessionResponseDTO(accepted, req), ...(inputReceipt ? { inputReceipt } : {}) });
165
181
  return;
166
182
  }
167
183
  const snapshot = await completion;
168
- res.json(sessionResponseDTO(snapshot, req));
184
+ res.json({ ...sessionResponseDTO(snapshot, req), ...(inputReceipt ? { inputReceipt } : {}) });
169
185
  return;
170
186
  }
171
187
  const existingSession = processes.get(sessionId) || storage.getSession(sessionId);
@@ -190,7 +206,10 @@ export function registerSessionInputRoutes(app, ctx) {
190
206
  res.json(sessionResponseDTO(snapshot, req));
191
207
  }
192
208
  catch (error) {
193
- const response = getInputErrorResponse(error, sessionId);
209
+ const response = inputAccepted
210
+ ? { statusCode: 503, payload: { error: getErrorMessage(error), errorCode: "INPUT_DELIVERY_UNCONFIRMED",
211
+ ...(inputReceipt ? { inputReceipt } : {}), sessionId, sessionStatus: structured.get(sessionId)?.status ?? null } }
212
+ : getInputErrorResponse(error, sessionId);
194
213
  console.error("[wand] Input request failed", {
195
214
  sessionId, inputLength: input.length, view: view ?? "chat",
196
215
  responseStatus: response.statusCode, responsePayload: response.payload,
@@ -27,6 +27,19 @@ export function registerSessionLifecycleRoutes(app, ctx) {
27
27
  app.post("/api/sessions", asyncRoute(async (req, res) => {
28
28
  const body = req.body;
29
29
  try {
30
+ const isWorktreeMergeRequest = !body.employeeId && ((typeof body.systemPrompt === "string" && body.systemPrompt.includes("Worktree 合并 Agent"))
31
+ || (typeof body.initialInput === "string" && body.initialInput.includes("Worktree 合并 Agent"))
32
+ || (typeof body.prompt === "string" && body.prompt.includes("Worktree 合并 Agent")));
33
+ if (isWorktreeMergeRequest) {
34
+ body.kind = "structured";
35
+ body.employeeId = "e_wand_ops";
36
+ if (body.initialInput && !body.prompt) {
37
+ body.prompt = body.initialInput;
38
+ delete body.initialInput;
39
+ }
40
+ delete body.command;
41
+ delete body.provider;
42
+ }
30
43
  if (body.kind !== "structured" && body.kind !== "pty")
31
44
  throw badRequest("kind 必须是 structured 或 pty。");
32
45
  if (body.prompt !== undefined && typeof body.prompt !== "string")
@@ -205,7 +218,7 @@ export function registerSessionLifecycleRoutes(app, ctx) {
205
218
  res.status(201).json(sessionResponseDTO(structured.get(snapshot.id) ?? snapshot, req));
206
219
  }
207
220
  catch (error) {
208
- sendRouteError(res, surfaceDomainError(error, /Wand Agent|harness|core/i) ?? error, "无法启动结构化会话。");
221
+ sendRouteError(res, surfaceDomainError(error, /Wand Agent|harness|core/i) ?? error, body.kind === "pty" ? "无法启动终端会话。" : "无法启动结构化会话。");
209
222
  }
210
223
  }));
211
224
  app.put("/api/sessions/:id/quick-terminal", (req, res) => {
@@ -1,14 +1,17 @@
1
1
  import { generateCommitMessageOnly, getGitStatusAsync, QuickCommitError, runPush, runQuickCommitWithFallback, runTagHead, } from "./git-quick-commit.js";
2
2
  import { checkSessionWorktreeMergeabilityAsync, cleanupSessionWorktreeAsync, mergeSessionWorktreeAsync } from "./git-worktree.js";
3
3
  import { resolveSystemAiContext } from "./session-ai-context.js";
4
+ import { listGitWorktrees, mergeAllWorktreesAndPush, withGitRepositoryOperation } from "./git-worktree-batch.js";
4
5
  import { getErrorMessage } from "./error-utils.js";
5
6
  import { asyncRoute } from "./express-async.js";
6
7
  import { buildIterationCommitContext, COMMIT_CONTEXT_MODE_PREF_KEY, isCommitContextMode } from "./iteration-log.js";
7
8
  import { sendRouteError } from "./server-request.js";
8
- import { commitModeForRead, iterationCommitInput, commitMessageFromIteration, requireWorktreeSession, saveWorktreeMergeState, getWorktreeMergeResponseStatus, getWorktreeMergePayload, canMergeSession, isMergeActionAllowed } from "./session-service.js";
9
+ import { commitModeForRead, iterationCommitInput, commitMessageFromIteration, requireWorktreeSession, saveWorktreeMergeState, getWorktreeMergeResponseStatus, getWorktreeMergePayload, canMergeSession, isMergeActionAllowed, getInputDebugMeta } from "./session-service.js";
10
+ import { selectEmployeeCandidate } from "./silicon-employee-dispatch.js";
11
+ import { resolveExplicitOrFallbackModel } from "./session-model.js";
9
12
  /** Worktree merge/cleanup, git status/commit/push and iteration context routes. */
10
13
  export function registerSessionWorktreeRoutes(app, ctx) {
11
- const { storage, config, sessions, free, wantsCompactTools, sessionResponseDTO, } = ctx;
14
+ const { storage, config, sessions, free, structured, onSessionCreated, wantsCompactTools, sessionResponseDTO, } = ctx;
12
15
  app.post("/api/sessions/:id/worktree/merge/check", asyncRoute(async (req, res) => {
13
16
  try {
14
17
  const current = requireWorktreeSession(sessions.getLatest(req.params.id));
@@ -50,10 +53,10 @@ export function registerSessionWorktreeRoutes(app, ctx) {
50
53
  lastError: undefined,
51
54
  conflict: false,
52
55
  });
53
- const result = await mergeSessionWorktreeAsync({
56
+ const result = await withGitRepositoryOperation(merging.worktree.repoRoot ?? merging.cwd, () => mergeSessionWorktreeAsync({
54
57
  worktree: merging.worktree,
55
58
  targetBranch: current.worktreeMergeInfo?.targetBranch,
56
- });
59
+ }));
57
60
  const updated = saveWorktreeMergeState(sessions, merging, "merged", {
58
61
  targetBranch: result.targetBranch,
59
62
  mergedAt: result.mergedAt,
@@ -92,12 +95,37 @@ export function registerSessionWorktreeRoutes(app, ctx) {
92
95
  return;
93
96
  }
94
97
  try {
95
- res.json(await getGitStatusAsync(snapshot.cwd));
98
+ const status = await getGitStatusAsync(snapshot.cwd);
99
+ const worktreeCount = status.isGit ? Math.max(0, (await listGitWorktrees(snapshot.cwd)).length - 1) : 0;
100
+ res.json({ ...status, worktreeCount });
96
101
  }
97
102
  catch (error) {
98
103
  res.json({ isGit: false, error: getErrorMessage(error, "无法读取 git 状态。") });
99
104
  }
100
105
  }));
106
+ app.post("/api/sessions/:id/git/merge-worktrees", asyncRoute(async (req, res) => {
107
+ const snapshot = sessions.getLatest(req.params.id);
108
+ if (!snapshot) {
109
+ res.status(404).json({ error: "未找到该会话。" });
110
+ return;
111
+ }
112
+ if (!snapshot.cwd) {
113
+ res.status(400).json({ error: "会话没有工作目录。" });
114
+ return;
115
+ }
116
+ const { expectedBranch, expectedHead } = req.body ?? {};
117
+ if (typeof expectedBranch !== "string" || !expectedBranch.trim()
118
+ || typeof expectedHead !== "string" || !/^[a-f0-9]{40,64}$/.test(expectedHead)) {
119
+ res.status(400).json({ error: "请先刷新 Git 状态,确认当前分支与提交。" });
120
+ return;
121
+ }
122
+ try {
123
+ res.json(await mergeAllWorktreesAndPush({ cwd: snapshot.cwd, expectedBranch, expectedHead }));
124
+ }
125
+ catch (error) {
126
+ sendRouteError(res, error, "无法合并 Worktree。");
127
+ }
128
+ }));
101
129
  app.post("/api/sessions/:id/quick-commit", asyncRoute(async (req, res) => {
102
130
  const snapshot = sessions.getLatest(req.params.id);
103
131
  if (!snapshot) {
@@ -113,7 +141,7 @@ export function registerSessionWorktreeRoutes(app, ctx) {
113
141
  const ai = resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), free, { storage });
114
142
  // 自动 message + 迭代模式:拿本轮提示词当输入;提交成功后标记已用掉。
115
143
  const context = await iterationCommitInput(storage, snapshot, body);
116
- const result = await runQuickCommitWithFallback({
144
+ const result = await withGitRepositoryOperation(snapshot.cwd, () => runQuickCommitWithFallback({
117
145
  cwd: snapshot.cwd,
118
146
  language: config.language ?? "",
119
147
  ...ai,
@@ -125,7 +153,7 @@ export function registerSessionWorktreeRoutes(app, ctx) {
125
153
  submodule: !!body.submodule,
126
154
  iterationDigest: commitMessageFromIteration(body) ? context.digest : "",
127
155
  includeDiff: !!body.includeDiff,
128
- });
156
+ }));
129
157
  const consumed = result.ok && result.commit?.hash && context.entryIds.length > 0
130
158
  ? storage.markIterationPromptsConsumed(context.entryIds, result.commit.hash)
131
159
  : 0;
@@ -232,14 +260,14 @@ export function registerSessionWorktreeRoutes(app, ctx) {
232
260
  const body = (req.body ?? {});
233
261
  try {
234
262
  const ai = resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), free, { storage });
235
- const result = await runTagHead({
263
+ const result = await withGitRepositoryOperation(snapshot.cwd, () => runTagHead({
236
264
  cwd: snapshot.cwd,
237
265
  language: config.language ?? "",
238
266
  ...ai,
239
267
  tag: typeof body.tag === "string" ? body.tag : undefined,
240
268
  autoTag: !!body.autoTag,
241
269
  push: !!body.push,
242
- });
270
+ }));
243
271
  res.json(result);
244
272
  }
245
273
  catch (error) {
@@ -263,13 +291,13 @@ export function registerSessionWorktreeRoutes(app, ctx) {
263
291
  }
264
292
  const body = (req.body ?? {});
265
293
  try {
266
- const result = await runPush({
294
+ const result = await withGitRepositoryOperation(snapshot.cwd, () => runPush({
267
295
  cwd: snapshot.cwd,
268
296
  pushCommits: body.pushCommits !== false,
269
297
  pushTags: !!body.pushTags,
270
298
  submodule: !!body.submodule,
271
299
  tagName: typeof body.tag === "string" ? body.tag : undefined,
272
- });
300
+ }));
273
301
  res.json(result);
274
302
  }
275
303
  catch (error) {
@@ -287,7 +315,7 @@ export function registerSessionWorktreeRoutes(app, ctx) {
287
315
  res.status(400).json({ error: "当前 worktree 无需补偿清理。", errorCode: "WORKTREE_CLEANUP_NOT_NEEDED" });
288
316
  return;
289
317
  }
290
- await cleanupSessionWorktreeAsync({ worktree: current.worktree });
318
+ await withGitRepositoryOperation(current.worktree.repoRoot ?? current.cwd, () => cleanupSessionWorktreeAsync({ worktree: current.worktree }));
291
319
  const updated = saveWorktreeMergeState(sessions, current, "merged", {
292
320
  ...(current.worktreeMergeInfo ?? null),
293
321
  cleanupDone: true,
@@ -300,4 +328,69 @@ export function registerSessionWorktreeRoutes(app, ctx) {
300
328
  res.status(getWorktreeMergeResponseStatus(error)).json(getWorktreeMergePayload(error, "无法清理 worktree。"));
301
329
  }
302
330
  }));
331
+ app.post("/api/workspaces/:id/worktree-merge-agent", asyncRoute(async (req, res) => {
332
+ const workspace = storage.getWorkspace(req.params.id);
333
+ if (!workspace) {
334
+ res.status(404).json({ error: "未找到该项目。" });
335
+ return;
336
+ }
337
+ const prompt = typeof req.body?.prompt === "string" ? req.body.prompt.trim() : "";
338
+ if (!prompt) {
339
+ res.status(400).json({ error: "请输入合并任务书内容。" });
340
+ return;
341
+ }
342
+ const systemPromptOverride = typeof req.body?.systemPrompt === "string" ? req.body.systemPrompt.trim() : "";
343
+ // 所有系统自动发起的系统功能调用(包含 Worktree 合并)统一使用「勤劳的初二」
344
+ const employee = storage.getSystemSiliconEmployee() ?? storage.ensureSystemSiliconEmployee();
345
+ const candidate = selectEmployeeCandidate(employee);
346
+ const agent = candidate.agent;
347
+ const provider = agent.provider;
348
+ const mode = agent.mode ?? "managed";
349
+ const requestedEngine = agent.engine;
350
+ const model = agent.model && agent.model !== "default"
351
+ ? agent.model
352
+ : resolveExplicitOrFallbackModel(config, provider, agent.model);
353
+ const baseSystemPrompt = systemPromptOverride || [
354
+ `你是 Wand 为项目「${workspace.name}」启动的 Worktree 合并 Agent。`,
355
+ `项目主工作区:${workspace.cwd}`,
356
+ "",
357
+ employee.prompt,
358
+ ].join("\n");
359
+ const engineDecision = requestedEngine ? await structured.resolveNewSessionPiEngine(requestedEngine) : null;
360
+ const snapshot = structured.createSession({
361
+ cwd: workspace.cwd,
362
+ mode,
363
+ provider,
364
+ runner: undefined,
365
+ worktreeEnabled: false,
366
+ model,
367
+ thinkingEffort: agent.thinkingEffort ?? config.defaultThinkingEffort,
368
+ sessionSource: "interactive",
369
+ workspaceId: workspace.id,
370
+ systemPrompt: baseSystemPrompt,
371
+ employeeId: employee.id,
372
+ employeeName: employee.name,
373
+ employeeAvatar: employee.avatar,
374
+ employeeCandidates: employee.agents,
375
+ employeeCandidateIndex: candidate.index,
376
+ title: `合并 Worktree - ${workspace.name}`,
377
+ ...(engineDecision ? { engine: engineDecision.engine } : {}),
378
+ });
379
+ onSessionCreated?.(snapshot.cwd);
380
+ try {
381
+ Promise.resolve(structured.sendMessage(snapshot.id, prompt, { source: "user" })).catch((error) => {
382
+ console.error("[wand] Accepted merge agent prompt later failed", {
383
+ sessionId: snapshot.id,
384
+ error: getInputDebugMeta(error),
385
+ });
386
+ });
387
+ }
388
+ catch (error) {
389
+ console.error("[wand] Accepted merge agent prompt later failed", {
390
+ sessionId: snapshot.id,
391
+ error: getInputDebugMeta(error),
392
+ });
393
+ }
394
+ res.status(201).json(wantsCompactTools(req) ? sessionResponseDTO(snapshot, req) : snapshot);
395
+ }));
303
396
  }
@@ -20,7 +20,7 @@ import { TaskReadModel } from "./task-read-model.js";
20
20
  import { readTaskActions } from "./task-actions.js";
21
21
  import { refreshAutoBoardTaskTitles, taskTitleService } from "./task-title-service.js";
22
22
  import { provisionalTaskTitleFromDescription } from "./task-title.js";
23
- import { isWandTaskStatus, statusAfterWandTaskDispatch } from "./task-types.js";
23
+ import { isWandTaskStatus, taskWorkflowStage, statusAfterWandTaskDispatch } from "./task-types.js";
24
24
  import { DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_AGENT_MODE, DEFAULT_WAND_TASK_PRIORITY, WAND_MILESTONE_NAME_MAX_LENGTH } from "./task-types.js";
25
25
  import { badRequest, surfaceDomainError } from "./http-error.js";
26
26
  import { parseTaskAgent, parentTaskIdFrom } from "./task-agent-parsing.js";
@@ -157,6 +157,9 @@ function taskDto({ storage, sessions }, task, model) {
157
157
  : task.executionSubject?.type === "team" ? storage.getAiTeam(task.executionSubject.id) : null;
158
158
  return {
159
159
  ...task,
160
+ status: taskWorkflowStage(task.status),
161
+ workflowVersion: 1,
162
+ ...(task.status === "done" ? { legacyReview: true } : {}),
160
163
  commentCount: model ? model.commentCounts.get(task.id) ?? 0 : storage.countWandTaskComments(task.id),
161
164
  queuedCommentCount: model ? model.queuedCommentCounts.get(task.id) ?? 0 : storage.countQueuedTaskCommentDispatchesForTask(task.id),
162
165
  ...(model ? model.execution(task) : taskExecutionProjection(sessionIds.flatMap(id => {
@@ -333,6 +336,8 @@ export function registerTaskRoutes(app, deps) {
333
336
  const titleSource = providedTitle ? "user" : "auto";
334
337
  const title = providedTitle || provisionalTaskTitleFromDescription(description) || "新建任务";
335
338
  const status = isWandTaskStatus(body.status) ? body.status : "todo";
339
+ if (!["backlog", "todo", "doing"].includes(status))
340
+ throw badRequest("新建任务不能冒充已交付或已验收,请先创建再提交交付。");
336
341
  const priority = PRIORITIES.has(body.priority) ? body.priority : DEFAULT_WAND_TASK_PRIORITY;
337
342
  const agent = body.agent === undefined ? null : parseTaskAgent(body.agent);
338
343
  const executionSubject = body.executionSubject === undefined
@@ -353,8 +358,10 @@ export function registerTaskRoutes(app, deps) {
353
358
  const requestedMilestoneId = milestoneIdFrom(storage, body.milestoneId) ?? defaultMilestoneIdForWrite(storage);
354
359
  const milestoneId = scopedMilestoneId(storage, requestedMilestoneId, workspaceId)
355
360
  ?? defaultMilestoneIdForWrite(storage);
361
+ const cwd = typeof body.cwd === "string" && body.cwd.trim() ? text(body.cwd) : undefined;
356
362
  const task = storage.createWandTask({
357
363
  workspaceId,
364
+ cwd,
358
365
  parentTaskId,
359
366
  title,
360
367
  titleSource,
@@ -392,6 +399,15 @@ export function registerTaskRoutes(app, deps) {
392
399
  res.status(404).json({ error: "未找到该任务。" });
393
400
  return;
394
401
  }
402
+ if (body.workflow !== undefined) {
403
+ if (Object.keys(body).some(key => key !== "workflow"))
404
+ throw badRequest("工作流动作不能与其他编辑混合提交。");
405
+ const result = storage.applyTaskWorkflow(currentTask.id, body.workflow, { kind: "user" }, sessions);
406
+ res.json(dto(result));
407
+ return;
408
+ }
409
+ if (body.verification !== undefined || body.deliveryRevision !== undefined || body.legacyReview !== undefined)
410
+ throw badRequest("验收版本与 AI 结论只能通过工作流写入。");
395
411
  const patch = {};
396
412
  if (body.title !== undefined) {
397
413
  // 显式改标题(包括原生端的可编辑标题框)都算用户自己写的。
@@ -407,17 +423,22 @@ export function registerTaskRoutes(app, deps) {
407
423
  const acceptance = bodyObject(body.acceptance);
408
424
  const evidence = text(acceptance.evidence).slice(0, 16000);
409
425
  const blocker = text(acceptance.blocker).slice(0, 4000);
410
- const accepted = acceptance.acceptedAt != null;
411
- if (accepted && (!evidence || blocker))
412
- throw badRequest("验收通过需要交付证据,并先解除阻塞。");
413
- if (accepted && storage.listWandTasks().some(child => child.parentTaskId === currentTask.id && !child.acceptance?.acceptedAt))
414
- throw badRequest("仍有子任务未验收通过,不能通过主目标整体验收。");
415
- patch.acceptance = { evidence, blocker, acceptedAt: accepted ? new Date().toISOString() : null };
426
+ if (acceptance.acceptedAt != null || acceptance.confirmedRevision !== undefined)
427
+ throw badRequest("请使用‘确认并归档’工作流;保存证据不能代替人工确认。");
428
+ patch.acceptance = { evidence, blocker, acceptedAt: null };
416
429
  }
417
430
  if (body.status !== undefined) {
418
431
  if (!isWandTaskStatus(body.status))
419
432
  throw badRequest("任务状态无效。");
420
- patch.status = body.status;
433
+ if (body.status !== taskWorkflowStage(currentTask.status) && body.status !== currentTask.status) {
434
+ if (body.status === "archived") {
435
+ res.status(409).json({ error: "请先核对 AI 验收,再由人工确认并归档。" });
436
+ return;
437
+ }
438
+ if (!["backlog", "todo", "doing"].includes(body.status))
439
+ throw badRequest("验收、待归档和归档只能通过明确的验收动作流转。");
440
+ patch.status = body.status;
441
+ }
421
442
  }
422
443
  if (body.priority !== undefined) {
423
444
  if (!PRIORITIES.has(body.priority))
@@ -584,6 +605,8 @@ export function registerTaskRoutes(app, deps) {
584
605
  res.status(404).json({ error: "未找到该任务。" });
585
606
  return;
586
607
  }
608
+ if (task.status === "archived")
609
+ throw badRequest("归档任务需要先恢复,再新建执行。");
587
610
  if (!config)
588
611
  throw badRequest("当前服务未启用派发,无法派发 Agent。");
589
612
  const body = bodyObject(req.body);
@@ -594,6 +617,12 @@ export function registerTaskRoutes(app, deps) {
594
617
  && (body.kind === "pty" || (body.agent && bodyObject(body.agent).kind === "pty"))) {
595
618
  throw badRequest("PTY 只能选择 CLI 工具。");
596
619
  }
620
+ const reviewing = body.purpose === "review";
621
+ if (body.purpose !== undefined && body.purpose !== "review" && body.purpose !== "execution")
622
+ throw badRequest("派发目的无效。");
623
+ if (reviewing && (taskWorkflowStage(task.status) !== "review" || subject?.type !== "employee"))
624
+ throw badRequest("AI 验收请在待验收阶段明确选择员工,以获得当前任务的受限回填能力。");
625
+ const dispatchStatus = reviewing ? "review" : statusAfterWandTaskDispatch(task.status);
597
626
  const requestedPrompt = text(body.prompt);
598
627
  const existingSessions = storage.listWandTaskSessionIds(task.id).length;
599
628
  if (existingSessions > 0 && !requestedPrompt)
@@ -621,7 +650,7 @@ export function registerTaskRoutes(app, deps) {
621
650
  storage.updateWandTask(task.id, {
622
651
  executionSubject: subject,
623
652
  agent: preferredAgent,
624
- status: statusAfterWandTaskDispatch(task.status),
653
+ status: dispatchStatus,
625
654
  });
626
655
  recordIterationPromptForTask(storage, {
627
656
  sessionId: detail.run.chatSessionId ?? undefined,
@@ -685,12 +714,12 @@ export function registerTaskRoutes(app, deps) {
685
714
  // Compatibility creation/drag clients also enter the same durable comment path for employees.
686
715
  // No implicit description fallback: a first dispatch needs an explicit round instruction too.
687
716
  if (employee) {
688
- const result = await submitTaskComment(deps, task.id, { requestId: text(body.requestId) || randomUUID(), body: requestedPrompt, employeeId: employee.id }, override ?? undefined);
717
+ const result = await submitTaskComment(deps, task.id, { requestId: text(body.requestId) || randomUUID(), body: requestedPrompt, employeeId: employee.id, purpose: reviewing ? "review" : "execution" }, override ?? undefined);
689
718
  const session = result.comment.dispatch?.sessionId ? sessions?.getSlim(result.comment.dispatch.sessionId)
690
719
  ?? storage.getSessionSlim(result.comment.dispatch.sessionId) : null;
691
720
  // 排队中的派发同样要把任务挂到员工并保持活动;不能因为没有会话就跳过。
692
721
  if (result.comment.dispatch)
693
- storage.updateWandTask(task.id, { agent, executionSubject: { type: "employee", id: employee.id, ...(override ? { agentOverride: override } : {}) }, status: statusAfterWandTaskDispatch(task.status) });
722
+ storage.updateWandTask(task.id, { agent, executionSubject: { type: "employee", id: employee.id, ...(override ? { agentOverride: override } : {}) }, status: dispatchStatus });
694
723
  res.status(202).json({ ok: true, taskId: task.id, subject: { type: "employee", id: employee.id }, comment: result.comment,
695
724
  queued: result.comment.dispatch?.state === "queued",
696
725
  session: session ? { ...taskSessionSummary(session), employeeCandidateIndex: session.employeeCandidateIndex } : null });
@@ -702,7 +731,7 @@ export function registerTaskRoutes(app, deps) {
702
731
  storage.updateWandTask(task.id, {
703
732
  agent,
704
733
  executionSubject: usedSubject,
705
- status: statusAfterWandTaskDispatch(task.status),
734
+ status: dispatchStatus,
706
735
  });
707
736
  if (body.rememberAgentDefaults === true && !employee)
708
737
  writeTaskBoardLastAgent(storage, agent);