@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,12 +1,14 @@
1
1
  import crypto from "node:crypto";
2
+ import { formatGlobalKnowledge, isGlobalKnowledgeInstallation, readGlobalKnowledgeSync, syncGlobalKnowledgeFiles } from "../global-knowledge.js";
2
3
  import { isGlobalWorkspace } from "../workspace-binding.js";
3
4
  import { normalizeEmployeeKnowledge } from "../employee-knowledge-content.js";
4
5
  import { EMPLOYEE_KNOWLEDGE_MAX_ENTRIES } from "../employee-knowledge-types.js";
5
- import { KnowledgeError } from "../knowledge-types.js";
6
+ import { GLOBAL_KNOWLEDGE_MAX_BYTES, GLOBAL_KNOWLEDGE_OWNER_ID, KnowledgeError } from "../knowledge-types.js";
6
7
  const hash = (value) => crypto.createHash("sha256").update(value).digest("hex");
7
- const spec = (scope) => scope === "personal"
8
- ? { table: "employee_knowledge", owner: "employee_id" }
9
- : { table: "project_knowledge", owner: "workspace_id" };
8
+ const spec = (scope) => scope === "global"
9
+ ? { table: "global_knowledge", owner: "owner_id" }
10
+ : scope === "personal" ? { table: "employee_knowledge", owner: "employee_id" }
11
+ : { table: "project_knowledge", owner: "workspace_id" };
10
12
  export class KnowledgeRepository {
11
13
  db;
12
14
  s;
@@ -15,6 +17,11 @@ export class KnowledgeRepository {
15
17
  this.s = s;
16
18
  }
17
19
  #owner(scope, ownerId) {
20
+ if (scope === "global") {
21
+ if (ownerId !== GLOBAL_KNOWLEDGE_OWNER_ID)
22
+ throw new KnowledgeError("全局知识库归属无效。", 400);
23
+ return { name: "全局知识库", readOnly: false };
24
+ }
18
25
  if (scope === "personal") {
19
26
  const employee = this.s.getSiliconEmployeeDefinition(ownerId);
20
27
  if (!employee)
@@ -38,7 +45,8 @@ export class KnowledgeRepository {
38
45
  WHERE NOT EXISTS (SELECT 1 FROM workspaces w WHERE w.id = workspace_id) GROUP BY workspace_id`).all();
39
46
  for (const row of orphans)
40
47
  projects.push({ id: String(row.workspace_id), name: `${String(row.project_name)}(已移除项目)`, readOnly: true });
41
- return { employees: this.s.listSiliconEmployees({ includeArchived: true }).map(employee => ({ id: employee.id, name: employee.name })), projects };
48
+ return { global: { id: GLOBAL_KNOWLEDGE_OWNER_ID, name: "全局知识库" },
49
+ employees: this.s.listSiliconEmployees({ includeArchived: true }).map(employee => ({ id: employee.id, name: employee.name })), projects };
42
50
  }
43
51
  #project(row, scope, ownerId) {
44
52
  return { id: row.id, scope, ownerId, title: row.title || row.content.split(/\r?\n/).find(line => line.trim())?.trim().slice(0, 100) || "无标题文档",
@@ -54,7 +62,10 @@ export class KnowledgeRepository {
54
62
  const normalizedQuery = query.trim().slice(0, 200).toLocaleLowerCase();
55
63
  const entries = rows.map(row => this.#project(row, scope, ownerId))
56
64
  .filter(entry => `${entry.title}\n${entry.content}\n${entry.folder}`.toLocaleLowerCase().includes(normalizedQuery));
57
- return { scope, ownerId, ownerName: owner.name, readOnly: owner.readOnly, entries, total: rows.length, limit: EMPLOYEE_KNOWLEDGE_MAX_ENTRIES };
65
+ return { scope, ownerId, ownerName: owner.name, readOnly: owner.readOnly, entries, total: rows.length, limit: EMPLOYEE_KNOWLEDGE_MAX_ENTRIES,
66
+ ...(scope === "global" ? { totalBytes: Buffer.byteLength(formatGlobalKnowledge(rows.map(row => this.#project(row, scope, ownerId)))),
67
+ maxTotalBytes: GLOBAL_KNOWLEDGE_MAX_BYTES,
68
+ ...(isGlobalKnowledgeInstallation(this.s.databasePath()) ? { cliSync: this.s.getPreference("pref:globalKnowledgeSync", readGlobalKnowledgeSync()) } : {}) } : {}) };
58
69
  }
59
70
  getKnowledgeDocument(scope, ownerId, id, accessToken) {
60
71
  if (accessToken)
@@ -90,9 +101,42 @@ export class KnowledgeRepository {
90
101
  }
91
102
  return { content, title, folder };
92
103
  }
104
+ #checkGlobalBudget(scope, input, replacingId = "") {
105
+ if (scope !== "global")
106
+ return;
107
+ const entries = this.readKnowledgeLibrary("global", GLOBAL_KNOWLEDGE_OWNER_ID).entries.filter(entry => entry.id !== replacingId);
108
+ const next = { ...input, title: input.title || input.content.split(/\r?\n/).find(line => line.trim())?.trim().slice(0, 100) || "无标题文档",
109
+ folder: input.folder || "未分类", id: replacingId || `gk_${"0".repeat(32)}`, revision: 9999999999 };
110
+ const total = Buffer.byteLength(formatGlobalKnowledge([...entries, next]));
111
+ if (total > GLOBAL_KNOWLEDGE_MAX_BYTES)
112
+ throw new KnowledgeError("全局约定每轮完整加载,合计最多 24 KB(含标题与目录)。请精简后保存;已有内容不会被截断或删除。");
113
+ }
114
+ syncGlobalKnowledge() {
115
+ if (!isGlobalKnowledgeInstallation(this.s.databasePath()))
116
+ return;
117
+ try {
118
+ // Serialize snapshots and file writes with other CLI processes using the same database.
119
+ this.s.transaction(() => {
120
+ const library = this.readKnowledgeLibrary("global", GLOBAL_KNOWLEDGE_OWNER_ID);
121
+ // Do not create rule files until a global library has been used at least once.
122
+ if (!library.total && !readGlobalKnowledgeSync().length)
123
+ return;
124
+ const results = syncGlobalKnowledgeFiles(formatGlobalKnowledge(library.entries));
125
+ this.s.setPreference("pref:globalKnowledgeSync", results);
126
+ for (const result of results)
127
+ if (result.error)
128
+ console.warn(`[knowledge] ${result.provider}: ${result.error}`);
129
+ });
130
+ }
131
+ catch (error) {
132
+ const message = error.message;
133
+ this.s.setPreference("pref:globalKnowledgeSync", [{ provider: "全部 CLI", path: "", error: message }]);
134
+ console.warn(`[knowledge] CLI 同步未完成:${message}`);
135
+ }
136
+ }
93
137
  createKnowledgeDocument(scope, ownerId, input, accessToken) {
94
138
  const values = this.#normalize(input);
95
- return this.s.transaction(() => {
139
+ const result = this.s.transaction(() => {
96
140
  if (accessToken)
97
141
  this.#requireAccess(accessToken, scope, ownerId);
98
142
  const owner = this.#owner(scope, ownerId);
@@ -106,9 +150,13 @@ export class KnowledgeRepository {
106
150
  const count = Number(this.db.prepare(`SELECT COUNT(*) AS count FROM ${table} WHERE ${column} = ?`).get(ownerId)?.count ?? 0);
107
151
  if (count >= EMPLOYEE_KNOWLEDGE_MAX_ENTRIES)
108
152
  throw new KnowledgeError("知识库已满,请先删除不需要的文档;不会自动丢弃旧知识。");
109
- const id = `${scope === "personal" ? "k" : "pk"}_${crypto.randomUUID().replace(/-/g, "")}`;
153
+ this.#checkGlobalBudget(scope, values);
154
+ const id = `${scope === "global" ? "gk" : scope === "personal" ? "k" : "pk"}_${crypto.randomUUID().replace(/-/g, "")}`;
110
155
  const now = new Date().toISOString();
111
- if (scope === "personal")
156
+ if (scope === "global")
157
+ this.db.prepare(`INSERT INTO global_knowledge (id, owner_id, title, folder, content, content_hash, created_at, updated_at)
158
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(id, ownerId, values.title, values.folder, values.content, contentHash, now, now);
159
+ else if (scope === "personal")
112
160
  this.db.prepare(`INSERT INTO employee_knowledge (id, employee_id, title, folder, content, content_hash, created_at, updated_at)
113
161
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(id, ownerId, values.title, values.folder, values.content, contentHash, now, now);
114
162
  else
@@ -116,10 +164,13 @@ export class KnowledgeRepository {
116
164
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, ownerId, owner.name, values.title, values.folder, values.content, contentHash, now, now);
117
165
  return { entry: this.getKnowledgeDocument(scope, ownerId, id), created: true };
118
166
  });
167
+ if (scope === "global")
168
+ this.syncGlobalKnowledge();
169
+ return result;
119
170
  }
120
171
  updateKnowledgeDocument(scope, ownerId, id, revision, input, accessToken) {
121
172
  const values = this.#normalize(input);
122
- return this.s.transaction(() => {
173
+ const result = this.s.transaction(() => {
123
174
  if (accessToken)
124
175
  this.#requireAccess(accessToken, scope, ownerId);
125
176
  const owner = this.#owner(scope, ownerId);
@@ -133,12 +184,16 @@ export class KnowledgeRepository {
133
184
  const duplicate = this.db.prepare(`SELECT id FROM ${table} WHERE ${column} = ? AND content_hash = ? AND id != ?`).get(ownerId, contentHash, id);
134
185
  if (duplicate)
135
186
  throw new KnowledgeError("当前知识库已有相同正文,请核对后保留一篇。");
187
+ this.#checkGlobalBudget(scope, values, id);
136
188
  this.db.prepare(`UPDATE ${table} SET title = ?, folder = ?, content = ?, content_hash = ?, updated_at = ?, revision = revision + 1
137
189
  WHERE ${column} = ? AND id = ? AND revision = ?`).run(values.title, values.folder, values.content, contentHash, new Date().toISOString(), ownerId, id, revision);
138
190
  // An older execution must not recreate the previous fact after the user corrected it.
139
191
  this.invalidateKnowledgeNamespace(scope, ownerId);
140
192
  return this.getKnowledgeDocument(scope, ownerId, id);
141
193
  });
194
+ if (scope === "global")
195
+ this.syncGlobalKnowledge();
196
+ return result;
142
197
  }
143
198
  deleteKnowledgeDocument(scope, ownerId, id, revision, accessToken) {
144
199
  this.s.transaction(() => {
@@ -151,6 +206,8 @@ export class KnowledgeRepository {
151
206
  this.db.prepare(`DELETE FROM ${table} WHERE ${owner} = ? AND id = ?`).run(ownerId, id);
152
207
  this.invalidateKnowledgeNamespace(scope, ownerId);
153
208
  });
209
+ if (scope === "global")
210
+ this.syncGlobalKnowledge();
154
211
  }
155
212
  invalidateKnowledgeNamespace(scope, ownerId) {
156
213
  this.db.prepare(`INSERT INTO knowledge_namespace_versions(scope, owner_id, revision) VALUES (?, ?, 1)
@@ -173,16 +230,16 @@ export class KnowledgeRepository {
173
230
  const task = binding?.workspaceTaskId ? this.s.getWorkspaceTask(binding.workspaceTaskId) : null;
174
231
  const workspace = binding?.workspaceId ? this.s.getWorkspace(binding.workspaceId) : null;
175
232
  const workspaceId = workspace && !isGlobalWorkspace(workspace) && (!binding?.workspaceTaskId || task?.workspaceId === workspace.id) ? workspace.id : null;
176
- return employeeId || workspaceId ? { sessionId, employeeId, workspaceId } : null;
233
+ return { sessionId, employeeId, workspaceId };
177
234
  }
178
235
  issueKnowledgeAccess(sessionId, now = Date.now()) {
179
236
  const scope = this.knowledgeSessionScope(sessionId);
180
237
  if (!scope)
181
- throw new KnowledgeError("当前会话未绑定可用员工或项目,知识库能力不可用。", 403);
238
+ throw new KnowledgeError("当前会话不存在或不允许访问知识库。", 403);
182
239
  this.db.prepare("DELETE FROM knowledge_access WHERE expires_at <= ?").run(now);
183
240
  const token = crypto.randomBytes(32).toString("base64url");
184
- this.db.prepare(`INSERT INTO knowledge_access (token_hash, session_id, employee_id, workspace_id, employee_revision, project_revision, expires_at)
185
- VALUES (?, ?, ?, ?, ?, ?, ?)`).run(hash(token), sessionId, scope.employeeId, scope.workspaceId, this.#namespaceRevision("personal", scope.employeeId), this.#namespaceRevision("project", scope.workspaceId), now + 6 * 3600_000);
241
+ this.db.prepare(`INSERT INTO knowledge_access (token_hash, session_id, employee_id, workspace_id, employee_revision, project_revision, global_revision, expires_at)
242
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(hash(token), sessionId, scope.employeeId, scope.workspaceId, this.#namespaceRevision("personal", scope.employeeId), this.#namespaceRevision("project", scope.workspaceId), this.#namespaceRevision("global", GLOBAL_KNOWLEDGE_OWNER_ID), now + 6 * 3600_000);
186
243
  return token;
187
244
  }
188
245
  resolveKnowledgeAccess(token, now = Date.now()) {
@@ -194,7 +251,8 @@ export class KnowledgeRepository {
194
251
  const current = this.knowledgeSessionScope(String(row.session_id));
195
252
  if (!current || current.employeeId !== row.employee_id || current.workspaceId !== row.workspace_id
196
253
  || this.#namespaceRevision("personal", current.employeeId) !== row.employee_revision
197
- || this.#namespaceRevision("project", current.workspaceId) !== row.project_revision)
254
+ || this.#namespaceRevision("project", current.workspaceId) !== row.project_revision
255
+ || this.#namespaceRevision("global", GLOBAL_KNOWLEDGE_OWNER_ID) !== row.global_revision)
198
256
  return null;
199
257
  return current;
200
258
  }
@@ -202,7 +260,8 @@ export class KnowledgeRepository {
202
260
  revokeSessionKnowledgeAccess(sessionId) { this.db.prepare("DELETE FROM knowledge_access WHERE session_id = ?").run(sessionId); }
203
261
  #requireAccess(token, scope, ownerId) {
204
262
  const access = this.resolveKnowledgeAccess(token);
205
- if ((scope === "personal" ? access?.employeeId : access?.workspaceId) !== ownerId)
263
+ if (!access || (scope === "global" ? GLOBAL_KNOWLEDGE_OWNER_ID : scope === "personal" ? access.employeeId : access.workspaceId) !== ownerId) {
206
264
  throw new KnowledgeError("知识库归属已变化或访问已失效,请等待新的会话执行。", 403);
265
+ }
207
266
  }
208
267
  }
@@ -128,6 +128,7 @@ export interface WorkspaceRow {
128
128
  export declare function mapWorkspaceKind(raw: string | null | undefined): WorkspaceKind;
129
129
  export declare function mapWorkspaceRow(row: WorkspaceRow): Workspace;
130
130
  export interface WorkspaceTaskRow {
131
+ container_kind?: string;
131
132
  id: string;
132
133
  workspace_id: string;
133
134
  name: string;
@@ -196,7 +196,11 @@ export function parseSessionOptions(raw) {
196
196
  options.queuedInputScheduling = parsed.queuedInputScheduling.map(value => isRecord(value) && (value.source === "user" || value.source === "task")
197
197
  && typeof value.enqueuedAt === "string" && Number.isFinite(Date.parse(value.enqueuedAt))
198
198
  ? { source: value.source, enqueuedAt: value.enqueuedAt,
199
- ...(Number.isSafeInteger(value.sequence) && Number(value.sequence) >= 0 ? { sequence: Number(value.sequence) } : {}) }
199
+ ...(Number.isSafeInteger(value.sequence) && Number(value.sequence) >= 0 ? { sequence: Number(value.sequence) } : {}),
200
+ ...(isRecord(value.handoff) && typeof value.handoff.id === "string"
201
+ && typeof value.handoff.boundaryId === "string" && typeof value.handoff.incarnationId === "string"
202
+ ? { handoff: { id: value.handoff.id, boundaryId: value.handoff.boundaryId, incarnationId: value.handoff.incarnationId } } : {}),
203
+ ...(typeof value.acceptanceId === "string" ? { acceptanceId: value.acceptanceId } : {}) }
200
204
  : { source: "task", enqueuedAt: new Date(0).toISOString() });
201
205
  }
202
206
  if (isAutonomyPolicy(parsed.autonomyPolicy))
@@ -383,6 +387,7 @@ export function mapWorkspaceTaskRow(row) {
383
387
  id: row.id,
384
388
  workspaceId: row.workspace_id,
385
389
  name: row.name,
390
+ kind: row.container_kind === "section" ? "section" : "task",
386
391
  worktree: mapWorkspaceTaskWorktree(row.worktree_json),
387
392
  ...(cwd ? { cwd } : {}),
388
393
  milestoneId: typeof row.milestone_id === "string" && row.milestone_id ? row.milestone_id : null,
@@ -403,4 +403,64 @@ export const STORAGE_MIGRATIONS = [
403
403
  db.exec("ALTER TABLE wand_tasks ADD COLUMN retention_archive_version INTEGER");
404
404
  }
405
405
  } },
406
+ { version: 24, name: "task-two-level-workflow", up: db => {
407
+ const columns = new Set(db.prepare("PRAGMA table_info(wand_tasks)").all().map(column => column.name));
408
+ if (!columns.has("verification_json"))
409
+ db.exec("ALTER TABLE wand_tasks ADD COLUMN verification_json TEXT");
410
+ if (!columns.has("delivery_revision"))
411
+ db.exec("ALTER TABLE wand_tasks ADD COLUMN delivery_revision INTEGER NOT NULL DEFAULT 0");
412
+ const dispatchColumns = new Set(db.prepare("PRAGMA table_info(wand_task_comment_dispatches)").all().map(column => column.name));
413
+ if (!dispatchColumns.has("purpose"))
414
+ db.exec("ALTER TABLE wand_task_comment_dispatches ADD COLUMN purpose TEXT NOT NULL DEFAULT 'execution'");
415
+ if (!dispatchColumns.has("review_revision"))
416
+ db.exec("ALTER TABLE wand_task_comment_dispatches ADD COLUMN review_revision INTEGER");
417
+ db.exec(`
418
+ CREATE TABLE IF NOT EXISTS task_workflow_events (
419
+ id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES wand_tasks(id) ON DELETE CASCADE,
420
+ request_id TEXT NOT NULL, signature TEXT NOT NULL, action TEXT NOT NULL,
421
+ actor_kind TEXT NOT NULL, actor_id TEXT, source_session_id TEXT,
422
+ evidence TEXT NOT NULL, blocker TEXT NOT NULL, created_at TEXT NOT NULL, result_json TEXT NOT NULL,
423
+ UNIQUE(task_id, request_id)
424
+ );
425
+ CREATE INDEX IF NOT EXISTS idx_task_workflow_history ON task_workflow_events(task_id, created_at);
426
+ CREATE TABLE IF NOT EXISTS task_workflow_access (
427
+ token_hash TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES wand_tasks(id) ON DELETE CASCADE,
428
+ session_id TEXT NOT NULL REFERENCES command_sessions(id) ON DELETE CASCADE,
429
+ employee_id TEXT, workspace_task_id TEXT NOT NULL, delivery_revision INTEGER NOT NULL, expires_at INTEGER NOT NULL
430
+ );
431
+ CREATE INDEX IF NOT EXISTS idx_task_workflow_access_session ON task_workflow_access(session_id);
432
+ CREATE TRIGGER IF NOT EXISTS revoke_task_workflow_access_on_binding
433
+ AFTER UPDATE OF workspace_id, workspace_task_id, session_options, automation_id ON command_sessions
434
+ WHEN OLD.workspace_id IS NOT NEW.workspace_id OR OLD.workspace_task_id IS NOT NEW.workspace_task_id
435
+ OR OLD.automation_id IS NOT NEW.automation_id
436
+ OR (CASE WHEN json_valid(OLD.session_options) THEN json_extract(OLD.session_options, '$.employeeId') END)
437
+ IS NOT (CASE WHEN json_valid(NEW.session_options) THEN json_extract(NEW.session_options, '$.employeeId') END)
438
+ BEGIN DELETE FROM task_workflow_access WHERE session_id = NEW.id; END;
439
+ `);
440
+ } },
441
+ { version: 25, name: "structured-input-acceptances", up: db => db.exec(`
442
+ CREATE TABLE IF NOT EXISTS structured_input_acceptances (
443
+ session_id TEXT NOT NULL REFERENCES command_sessions(id) ON DELETE CASCADE,
444
+ idempotency_key TEXT NOT NULL, signature TEXT NOT NULL, receipt_json TEXT NOT NULL,
445
+ PRIMARY KEY(session_id, idempotency_key)
446
+ );
447
+ CREATE TRIGGER IF NOT EXISTS delete_structured_input_acceptances
448
+ AFTER DELETE ON command_sessions BEGIN
449
+ DELETE FROM structured_input_acceptances WHERE session_id = OLD.id;
450
+ END;
451
+ `) },
452
+ { version: 26, name: "global-knowledge", up: db => {
453
+ db.exec(`CREATE TABLE IF NOT EXISTS global_knowledge (
454
+ id TEXT PRIMARY KEY, owner_id TEXT NOT NULL CHECK(owner_id = 'global'),
455
+ title TEXT NOT NULL DEFAULT '', folder TEXT NOT NULL DEFAULT '', content TEXT NOT NULL,
456
+ content_hash TEXT NOT NULL, revision INTEGER NOT NULL DEFAULT 1,
457
+ created_at TEXT NOT NULL, updated_at TEXT, UNIQUE(owner_id, content_hash)
458
+ )`);
459
+ const columns = db.prepare("PRAGMA table_info(knowledge_access)").all();
460
+ if (!columns.some(column => column.name === "global_revision")) {
461
+ // Existing grants predate global access and must not silently acquire it.
462
+ db.exec("ALTER TABLE knowledge_access ADD COLUMN global_revision INTEGER NOT NULL DEFAULT -1");
463
+ }
464
+ } },
465
+ { version: 27, name: "workspace-session-sections", up: db => db.exec("ALTER TABLE workspace_tasks ADD COLUMN container_kind TEXT NOT NULL DEFAULT 'task'") },
406
466
  ];
@@ -0,0 +1,19 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ import type { WandStorage } from "../storage.js";
3
+ import type { SessionSnapshot } from "../types.js";
4
+ /** Acceptance proves durable ownership of the input, not external tool execution. */
5
+ export interface StructuredInputReceipt {
6
+ accepted: true;
7
+ acceptanceId: string;
8
+ sessionId: string;
9
+ idempotencyKey: string;
10
+ acceptedAt: string;
11
+ }
12
+ export declare class SessionInputRepository {
13
+ private readonly db;
14
+ private readonly s;
15
+ constructor(db: DatabaseSync, s: WandStorage);
16
+ getStructuredInputReceipt(sessionId: string, key: string, signature: string): StructuredInputReceipt | null;
17
+ /** Queue/user turn and receipt share the write lock and commit/rollback boundary. */
18
+ acceptStructuredInput(snapshot: SessionSnapshot, signature: string, receipt: StructuredInputReceipt, userTurn?: boolean): boolean;
19
+ }
@@ -0,0 +1,35 @@
1
+ export class SessionInputRepository {
2
+ db;
3
+ s;
4
+ constructor(db, s) {
5
+ this.db = db;
6
+ this.s = s;
7
+ }
8
+ getStructuredInputReceipt(sessionId, key, signature) {
9
+ const row = this.db.prepare(`SELECT signature, receipt_json FROM structured_input_acceptances
10
+ WHERE session_id = ? AND idempotency_key = ?`).get(sessionId, key);
11
+ if (!row)
12
+ return null;
13
+ if (row.signature !== signature) {
14
+ throw Object.assign(new Error("该幂等键已用于不同的消息或操作,请核对原请求。"), { code: "idempotency_key_conflict" });
15
+ }
16
+ return JSON.parse(row.receipt_json);
17
+ }
18
+ /** Queue/user turn and receipt share the write lock and commit/rollback boundary. */
19
+ acceptStructuredInput(snapshot, signature, receipt, userTurn = false) {
20
+ return this.s.transaction(() => {
21
+ if (this.getStructuredInputReceipt(snapshot.id, receipt.idempotencyKey, signature))
22
+ return false;
23
+ const exists = this.db.prepare("SELECT 1 FROM command_sessions WHERE id = ? AND session_kind = 'structured'").get(snapshot.id);
24
+ if (!exists)
25
+ throw new Error("未找到该结构化会话。");
26
+ this.s.updateSessionRuntimeMetadata(snapshot);
27
+ if (userTurn)
28
+ this.s.checkpointSessionMessages(snapshot.id, snapshot.messages ?? [], snapshot.structuredState);
29
+ this.db.prepare(`INSERT INTO structured_input_acceptances
30
+ (session_id, idempotency_key, signature, receipt_json) VALUES (?, ?, ?, ?)`)
31
+ .run(snapshot.id, receipt.idempotencyKey, signature, JSON.stringify(receipt));
32
+ return true;
33
+ });
34
+ }
35
+ }
@@ -0,0 +1,29 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ import type { WandStorage } from "../storage.js";
3
+ import type { SessionRegistry } from "../session-registry.js";
4
+ import { type TaskWorkflowActor } from "../task-workflow.js";
5
+ import { type WandTask } from "../task-types.js";
6
+ type Grant = {
7
+ taskId: string;
8
+ sessionId: string;
9
+ employeeId: string | null;
10
+ revision: number;
11
+ };
12
+ export declare class TaskWorkflowRepository {
13
+ private readonly db;
14
+ private readonly s;
15
+ constructor(db: DatabaseSync, s: WandStorage);
16
+ /** Called only by an active runner; never grants the project or arbitrary task IDs. */
17
+ issueTaskWorkflowAccess(sessionId: string): {
18
+ token: string;
19
+ taskId: string;
20
+ revision: number;
21
+ } | null;
22
+ resolveTaskWorkflowAccess(token: string): Grant | null;
23
+ revokeTaskWorkflowAccess(token: string): void;
24
+ applyEmployeeTaskWorkflow(token: string, taskId: string, input: unknown): WandTask;
25
+ applyTaskWorkflow(taskId: string, value: unknown, actor: TaskWorkflowActor, registry?: SessionRegistry): WandTask;
26
+ hasTaskReviewReceipt(taskId: string, sessionId: string, revision: number): boolean;
27
+ listTaskWorkflowEvents(taskId: string): Array<Record<string, unknown>>;
28
+ }
29
+ export {};
@@ -0,0 +1,110 @@
1
+ import crypto from "node:crypto";
2
+ import { HttpError } from "../http-error.js";
3
+ import { isArchiveBlocked } from "../archive-policy.js";
4
+ import { AI_TEAM_ACTIVE_RUN_STATUSES } from "../ai-team-types.js";
5
+ import { parseTaskWorkflowInput, taskWorkflowPatch } from "../task-workflow.js";
6
+ const hash = (value) => crypto.createHash("sha256").update(value).digest("hex");
7
+ export class TaskWorkflowRepository {
8
+ db;
9
+ s;
10
+ constructor(db, s) {
11
+ this.db = db;
12
+ this.s = s;
13
+ }
14
+ /** Called only by an active runner; never grants the project or arbitrary task IDs. */
15
+ issueTaskWorkflowAccess(sessionId) {
16
+ const session = this.s.getSessionSlim(sessionId);
17
+ const scope = this.s.knowledgeSessionScope(sessionId);
18
+ if (!session || session.archived || ["failed", "stopped", "exited"].includes(session.status) || !session.workspaceTaskId || !scope?.employeeId || session.automationId?.startsWith("dingtalk:"))
19
+ return null;
20
+ const employee = this.s.getSiliconEmployeeDefinition(scope.employeeId);
21
+ if (!employee || employee.archivedAt)
22
+ return null;
23
+ const task = this.s.getWandTaskByWorkspaceTaskId(session.workspaceTaskId);
24
+ if (!task || task.status === "archived" || task.workspaceId !== scope.workspaceId)
25
+ return null;
26
+ const token = crypto.randomBytes(32).toString("base64url"), revision = task.deliveryRevision ?? 0;
27
+ this.db.prepare("DELETE FROM task_workflow_access WHERE expires_at <= ? OR session_id = ?").run(Date.now(), sessionId);
28
+ this.db.prepare(`INSERT INTO task_workflow_access(token_hash, task_id, session_id, employee_id, workspace_task_id, delivery_revision, expires_at)
29
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(hash(token), task.id, sessionId, scope.employeeId, session.workspaceTaskId, revision, Date.now() + 6 * 3600_000);
30
+ return { token, taskId: task.id, revision };
31
+ }
32
+ resolveTaskWorkflowAccess(token) {
33
+ if (!/^[A-Za-z0-9_-]{43}$/.test(token))
34
+ return null;
35
+ const row = this.db.prepare("SELECT * FROM task_workflow_access WHERE token_hash = ? AND expires_at > ?").get(hash(token), Date.now());
36
+ if (!row)
37
+ return null;
38
+ const session = this.s.getSessionSlim(String(row.session_id));
39
+ const scope = this.s.knowledgeSessionScope(String(row.session_id));
40
+ const task = this.s.getWandTask(String(row.task_id));
41
+ const employee = scope?.employeeId ? this.s.getSiliconEmployeeDefinition(scope.employeeId) : null;
42
+ if (!employee || employee.archivedAt)
43
+ return null;
44
+ if (!session || session.archived || ["failed", "stopped", "exited"].includes(session.status) || !scope || !task || task.status === "archived" || task.workspaceTaskId !== row.workspace_task_id
45
+ || session.workspaceTaskId !== row.workspace_task_id || scope.employeeId !== row.employee_id || scope.workspaceId !== task.workspaceId
46
+ || (task.deliveryRevision ?? 0) !== row.delivery_revision)
47
+ return null;
48
+ return { taskId: task.id, sessionId: session.id, employeeId: scope.employeeId, revision: Number(row.delivery_revision) };
49
+ }
50
+ revokeTaskWorkflowAccess(token) { this.db.prepare("DELETE FROM task_workflow_access WHERE token_hash = ?").run(hash(token)); }
51
+ applyEmployeeTaskWorkflow(token, taskId, input) {
52
+ return this.s.transaction(() => {
53
+ const access = this.resolveTaskWorkflowAccess(token);
54
+ if (!access || access.taskId !== taskId)
55
+ throw new HttpError(403, "forbidden", "任务回填权限已失效或不属于当前任务,请等待新的执行;不能改用所有者身份。");
56
+ return this.applyTaskWorkflow(taskId, input, { kind: "employee", sessionId: access.sessionId, employeeId: access.employeeId });
57
+ });
58
+ }
59
+ applyTaskWorkflow(taskId, value, actor, registry) {
60
+ const input = parseTaskWorkflowInput(value);
61
+ const signature = hash(JSON.stringify({ actor, input }));
62
+ return this.s.transaction(() => {
63
+ const previous = this.db.prepare("SELECT signature, result_json FROM task_workflow_events WHERE task_id = ? AND request_id = ?").get(taskId, input.requestId);
64
+ if (previous) {
65
+ if (previous.signature !== signature)
66
+ throw new HttpError(409, "conflict", "请求标识已用于另一份验收意图,请保留原输入并核对记录。");
67
+ return this.s.getWandTask(taskId) ?? JSON.parse(String(previous.result_json));
68
+ }
69
+ const task = this.s.getWandTask(taskId);
70
+ if (!task)
71
+ throw new HttpError(404, "not_found", "任务已删除。");
72
+ if (task.updatedAt !== input.expectedUpdatedAt)
73
+ throw new HttpError(409, "conflict", "任务已变化,请刷新并核对当前交付后再操作;草稿不会自动重发。");
74
+ const children = this.s.listWandTaskChildren(taskId);
75
+ const ownedSession = actor.kind === "employee" ? actor.sessionId : null;
76
+ const family = [task, ...children];
77
+ for (const member of family) {
78
+ const queued = this.s.countQueuedTaskCommentDispatchesForTask(member.id);
79
+ if (queued > 0)
80
+ throw new HttpError(409, "busy", "任务仍有排队执行,请先处理队列。");
81
+ for (const id of this.s.listWandTaskSessionIds(member.id)) {
82
+ if (id === ownedSession)
83
+ continue;
84
+ const session = registry?.getSlim(id) ?? this.s.getSessionSlim(id);
85
+ if (session && isArchiveBlocked(session))
86
+ throw new HttpError(409, "busy", "任务仍有执行或等待处理的请求,不能推进验收。");
87
+ }
88
+ if ([member.id, member.workspaceTaskId].some(id => id && this.s.listAiTeamRuns({ taskId: id, statuses: AI_TEAM_ACTIVE_RUN_STATUSES, limit: 1 }).length))
89
+ throw new HttpError(409, "busy", "任务仍有运行中的团队,不能推进验收。");
90
+ }
91
+ const now = new Date(Math.max(Date.now(), Date.parse(task.updatedAt) + 1)).toISOString();
92
+ const patch = taskWorkflowPatch(task, children, input, actor, now);
93
+ const result = this.s.tasks.writeWandTask(task, patch, true);
94
+ const id = crypto.randomUUID();
95
+ this.db.prepare(`INSERT INTO task_workflow_events(id, task_id, request_id, signature, action, actor_kind, actor_id, source_session_id, evidence, blocker, created_at, result_json)
96
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, taskId, input.requestId, signature, input.action, actor.kind, actor.kind === "employee" ? actor.employeeId : null, ownedSession, input.evidence, input.blocker, now, JSON.stringify(result));
97
+ const labels = { submit: "交付已提交,等待 AI 验收", pass: "AI 验收通过,等待人工确认归档", fail: "AI 验收未通过,退回执行中", return: "人工退回,继续修改", accept: "人工确认交付并归档" };
98
+ this.db.prepare("INSERT INTO wand_task_comments(id, task_id, author_name, body, created_at) VALUES (?, ?, ?, ?, ?)")
99
+ .run(id, taskId, "验收流程", [labels[input.action], input.evidence ? `证据:${input.evidence}` : "", input.blocker ? `缺口:${input.blocker}` : "", `交付版本:${result.deliveryRevision ?? 0}${ownedSession ? ` · 来源会话:${ownedSession}` : ""}`].filter(Boolean).join("\n\n"), now);
100
+ return result;
101
+ });
102
+ }
103
+ hasTaskReviewReceipt(taskId, sessionId, revision) {
104
+ return Boolean(this.db.prepare("SELECT 1 FROM task_workflow_events WHERE task_id = ? AND source_session_id = ? AND action IN ('pass', 'fail') AND json_extract(result_json, '$.verification.revision') = ? LIMIT 1").get(taskId, sessionId, revision));
105
+ }
106
+ listTaskWorkflowEvents(taskId) {
107
+ return this.db.prepare(`SELECT id, request_id AS requestId, action, actor_kind AS actorKind, actor_id AS actorId,
108
+ source_session_id AS sourceSessionId, evidence, blocker, created_at AS createdAt FROM task_workflow_events WHERE task_id = ? ORDER BY created_at DESC, rowid DESC LIMIT 50`).all(taskId);
109
+ }
110
+ }
@@ -39,6 +39,7 @@ export declare class TaskRepository {
39
39
  countWandTaskComments(taskId: string): number;
40
40
  /** One grouped query for task-list projections; tasks without comments are omitted (read as zero). */
41
41
  countWandTaskCommentsByTask(): Map<string, number>;
42
+ isTaskReviewComment(id: string): boolean;
42
43
  getWandTaskComment(id: string): WandTaskComment | null;
43
44
  /** One task-scoped query for a bounded set of known comment IDs (e.g. status refresh for older pages). */
44
45
  getWandTaskCommentsByIds(taskId: string, ids: readonly string[]): WandTaskComment[];
@@ -61,6 +62,8 @@ export declare class TaskRepository {
61
62
  referencedTaskIds?: readonly string[];
62
63
  agent?: WandTaskAgent | null;
63
64
  queued?: boolean;
65
+ purpose?: "execution" | "review";
66
+ reviewRevision?: number | null;
64
67
  }): WandTaskComment;
65
68
  updateTaskCommentDispatch(id: string, state: "queued" | "pending" | "accepted" | "failed" | "unknown", sessionId: string | null, error?: string | null): void;
66
69
  /** Hold only unclaimed intents atomically. Running/pending deliveries are never touched. */
@@ -117,8 +120,10 @@ export declare class TaskRepository {
117
120
  private insertWandTaskBody;
118
121
  createWandTask(input: CreateWandTaskInput): WandTask;
119
122
  /** Private canonical writer; callers own the transaction, never a reverse writer. */
120
- writeWandTask(current: WandTask, patch: WandTaskPatch): WandTask;
123
+ writeWandTask(current: WandTask, patch: WandTaskPatch, workflow?: boolean): WandTask;
121
124
  updateWandTask(id: string, patch: WandTaskPatch): WandTask | null;
125
+ /** Explicit destructive project removal collects history, never claims delivery acceptance. */
126
+ archiveTaskForWorkspaceRemoval(id: string): WandTask | null;
122
127
  /** Commit completion archives only the selected completed cards, atomically. */
123
128
  archiveCompletedWandTasks(ids: readonly string[]): string[];
124
129
  /**