@baique/pi-web-sky 0.1.35 → 0.1.37

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 (483) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +11 -11
  3. package/.next/build-manifest.json +4 -4
  4. package/.next/next-server.js.nft.json +1 -1
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/react-loadable-manifest.json +2 -2
  7. package/.next/required-server-files.js +6 -6
  8. package/.next/required-server-files.json +6 -6
  9. package/.next/server/app/_global-error/page.js +2 -2
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +3 -3
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/server/app/api/agent/[id]/bash-output/route.js +2 -2
  24. package/.next/server/app/api/agent/[id]/bash-output/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/agent/[id]/events/route.js +3 -3
  26. package/.next/server/app/api/agent/[id]/events/route_client-reference-manifest.js +1 -1
  27. package/.next/server/app/api/agent/[id]/route.js +2 -2
  28. package/.next/server/app/api/agent/[id]/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/agent/new/route.js +1 -1
  30. package/.next/server/app/api/agent/new/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/agent/running/route.js +1 -1
  32. package/.next/server/app/api/agent/running/route_client-reference-manifest.js +1 -1
  33. package/.next/server/app/api/app-update/route.js +1 -1
  34. package/.next/server/app/api/app-update/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  36. package/.next/server/app/api/auth/all-providers/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  38. package/.next/server/app/api/auth/api-key/[provider]/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/auth/login/[provider]/route.js +3 -3
  40. package/.next/server/app/api/auth/login/[provider]/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  42. package/.next/server/app/api/auth/logout/[provider]/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/auth/providers/route.js +1 -1
  44. package/.next/server/app/api/auth/providers/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/boards/[id]/canvas/route.js +1 -1
  46. package/.next/server/app/api/boards/[id]/canvas/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/boards/[id]/edges/[eid]/route.js +1 -1
  48. package/.next/server/app/api/boards/[id]/edges/[eid]/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/boards/[id]/edges/route.js +1 -1
  50. package/.next/server/app/api/boards/[id]/edges/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/boards/[id]/nodes/[nid]/route.js +1 -1
  52. package/.next/server/app/api/boards/[id]/nodes/[nid]/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/boards/[id]/nodes/route.js +1 -1
  54. package/.next/server/app/api/boards/[id]/nodes/route_client-reference-manifest.js +1 -1
  55. package/.next/server/app/api/boards/[id]/reconcile-task-edges/route.js +1 -1
  56. package/.next/server/app/api/boards/[id]/reconcile-task-edges/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/boards/[id]/route.js +1 -1
  58. package/.next/server/app/api/boards/[id]/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/boards/purge-orphans/route.js +1 -1
  60. package/.next/server/app/api/boards/purge-orphans/route_client-reference-manifest.js +1 -1
  61. package/.next/server/app/api/boards/reorder/route.js +1 -1
  62. package/.next/server/app/api/boards/reorder/route_client-reference-manifest.js +1 -1
  63. package/.next/server/app/api/boards/route.js +1 -1
  64. package/.next/server/app/api/boards/route_client-reference-manifest.js +1 -1
  65. package/.next/server/app/api/cwd/browse/route.js +1 -1
  66. package/.next/server/app/api/cwd/browse/route_client-reference-manifest.js +1 -1
  67. package/.next/server/app/api/cwd/validate/route.js +2 -2
  68. package/.next/server/app/api/cwd/validate/route_client-reference-manifest.js +1 -1
  69. package/.next/server/app/api/default-cwd/route.js +2 -2
  70. package/.next/server/app/api/default-cwd/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/file-index/route.js +2 -2
  72. package/.next/server/app/api/file-index/route_client-reference-manifest.js +1 -1
  73. package/.next/server/app/api/files/[...path]/route.js +2 -2
  74. package/.next/server/app/api/files/[...path]/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/api/git/diff/route.js +1 -1
  76. package/.next/server/app/api/git/diff/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/git/status/route.js +1 -1
  78. package/.next/server/app/api/git/status/route_client-reference-manifest.js +1 -1
  79. package/.next/server/app/api/home/route.js +1 -1
  80. package/.next/server/app/api/home/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/mcp/route.js +2 -2
  82. package/.next/server/app/api/mcp/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/models/route.js +2 -2
  84. package/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/models-config/catalog/route.js +1 -1
  86. package/.next/server/app/api/models-config/catalog/route_client-reference-manifest.js +1 -1
  87. package/.next/server/app/api/models-config/discover/route.js +1 -1
  88. package/.next/server/app/api/models-config/discover/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/models-config/route.js +1 -1
  90. package/.next/server/app/api/models-config/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/models-config/test/route.js +1 -1
  92. package/.next/server/app/api/models-config/test/route_client-reference-manifest.js +1 -1
  93. package/.next/server/app/api/plugins/route.js +2 -2
  94. package/.next/server/app/api/plugins/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/project-trust/route.js +2 -2
  96. package/.next/server/app/api/project-trust/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/quota/route.js +1 -1
  98. package/.next/server/app/api/quota/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/search/route.js +2 -2
  100. package/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/sessions/[id]/auto-name/route.js +3 -3
  102. package/.next/server/app/api/sessions/[id]/auto-name/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/sessions/[id]/context/route.js +2 -2
  104. package/.next/server/app/api/sessions/[id]/context/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +2 -2
  106. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/sessions/[id]/export/route.js +2 -2
  108. package/.next/server/app/api/sessions/[id]/export/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  110. package/.next/server/app/api/sessions/[id]/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/sessions/[id]/state/route.js +2 -2
  112. package/.next/server/app/api/sessions/[id]/state/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/sessions/[id]/todos/route.js +2 -2
  114. package/.next/server/app/api/sessions/[id]/todos/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/sessions/route.js +2 -2
  116. package/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/skills/check/route.js +1 -1
  118. package/.next/server/app/api/skills/check/route.js.nft.json +1 -1
  119. package/.next/server/app/api/skills/check/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/skills/install/route.js +2 -2
  121. package/.next/server/app/api/skills/install/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/skills/route.js +2 -31
  123. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  124. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  125. package/.next/server/app/api/skills/search/route.js +1 -1
  126. package/.next/server/app/api/skills/search/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/skills/update/route.js +1 -1
  128. package/.next/server/app/api/skills/update/route.js.nft.json +1 -1
  129. package/.next/server/app/api/skills/update/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/task-card-questions/[id]/answer/route.js +1 -1
  131. package/.next/server/app/api/task-card-questions/[id]/answer/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/task-card-questions/route.js +1 -1
  133. package/.next/server/app/api/task-card-questions/route_client-reference-manifest.js +1 -1
  134. package/.next/server/app/api/task-cards/[id]/route.js +1 -1
  135. package/.next/server/app/api/task-cards/[id]/route_client-reference-manifest.js +1 -1
  136. package/.next/server/app/api/task-cards/route.js +1 -1
  137. package/.next/server/app/api/task-cards/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/api/task-scheduler/status/route.js +1 -1
  139. package/.next/server/app/api/task-scheduler/status/route_client-reference-manifest.js +1 -1
  140. package/.next/server/app/api/tasks/[id]/board/route.js +1 -1
  141. package/.next/server/app/api/tasks/[id]/board/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/tasks/[id]/route.js +1 -1
  143. package/.next/server/app/api/tasks/[id]/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/tasks/reorder/route.js +1 -1
  145. package/.next/server/app/api/tasks/reorder/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/tasks/route.js +1 -1
  147. package/.next/server/app/api/tasks/route_client-reference-manifest.js +1 -1
  148. package/.next/server/app/api/terminal/[id]/events/route.js +3 -3
  149. package/.next/server/app/api/terminal/[id]/events/route_client-reference-manifest.js +1 -1
  150. package/.next/server/app/api/terminal/[id]/route.js +2 -2
  151. package/.next/server/app/api/terminal/[id]/route_client-reference-manifest.js +1 -1
  152. package/.next/server/app/api/terminal/route.js +2 -2
  153. package/.next/server/app/api/terminal/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/api/worktrees/route.js +2 -2
  155. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  156. package/.next/server/app/favicon.ico/route.js +1 -1
  157. package/.next/server/app/index.html +1 -1
  158. package/.next/server/app/index.rsc +5 -5
  159. package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
  160. package/.next/server/app/index.segments/_full.segment.rsc +5 -5
  161. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  162. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  163. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  164. package/.next/server/app/page.js +3 -3
  165. package/.next/server/app/page_client-reference-manifest.js +1 -1
  166. package/.next/server/app-paths-manifest.json +11 -11
  167. package/.next/server/chunks/2404.js +2 -2
  168. package/.next/server/chunks/2430.js +4 -4
  169. package/.next/server/chunks/3500.js +30 -0
  170. package/.next/server/chunks/4226.js +2 -2
  171. package/.next/server/chunks/542.js +11 -6
  172. package/.next/server/chunks/5794.js +2 -2
  173. package/.next/server/chunks/6429.js +1 -1
  174. package/.next/server/chunks/898.js +2 -2
  175. package/.next/server/instrumentation.js +1 -1
  176. package/.next/server/middleware-build-manifest.js +1 -1
  177. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  178. package/.next/server/middleware.js +3 -3
  179. package/.next/server/next-font-manifest.js +1 -1
  180. package/.next/server/next-font-manifest.json +1 -1
  181. package/.next/server/pages/404.html +1 -1
  182. package/.next/server/pages/500.html +1 -1
  183. package/.next/server/server-reference-manifest.json +1 -1
  184. package/.next/static/chunks/5315.dba7515ae6e139ae.js +4 -0
  185. package/.next/static/chunks/{7999.f4ab221861378788.js → 7999.879d5a20e8641752.js} +1 -1
  186. package/.next/static/chunks/app/_not-found/{page-af4fe23994cb3d66.js → page-8cea8d8de35f4da4.js} +1 -1
  187. package/.next/static/chunks/app/{layout-1d9a7a44fbc92d2e.js → layout-c4ca28ccc08bc975.js} +1 -1
  188. package/.next/static/chunks/app/{page-6d6779a81a0a7639.js → page-c9ead38525a85686.js} +3 -3
  189. package/.next/static/chunks/{main-app-b3e8a8d15d83e1f0.js → main-app-dd2385a1a5a588df.js} +1 -1
  190. package/.next/static/chunks/next/dist/client/components/builtin/global-error-be943dcb89867ab9.js +1 -0
  191. package/.next/static/chunks/{webpack-fa4f40085b1ec1ac.js → webpack-4bbf04891e910713.js} +1 -1
  192. package/.next/static/css/422c7dc0faa04d89.css +1 -0
  193. package/.next/trace +8 -9
  194. package/.next/trace-build +1 -1
  195. package/.next/types/app/api/agent/[id]/bash-output/route.ts +1 -1
  196. package/.next/types/app/api/agent/[id]/events/route.ts +1 -1
  197. package/.next/types/app/api/agent/[id]/route.ts +1 -1
  198. package/.next/types/app/api/agent/new/route.ts +1 -1
  199. package/.next/types/app/api/agent/running/route.ts +1 -1
  200. package/.next/types/app/api/app-update/route.ts +1 -1
  201. package/.next/types/app/api/auth/all-providers/route.ts +1 -1
  202. package/.next/types/app/api/auth/api-key/[provider]/route.ts +1 -1
  203. package/.next/types/app/api/auth/login/[provider]/route.ts +1 -1
  204. package/.next/types/app/api/auth/logout/[provider]/route.ts +1 -1
  205. package/.next/types/app/api/auth/providers/route.ts +1 -1
  206. package/.next/types/app/api/boards/[id]/canvas/route.ts +1 -1
  207. package/.next/types/app/api/boards/[id]/edges/[eid]/route.ts +1 -1
  208. package/.next/types/app/api/boards/[id]/edges/route.ts +1 -1
  209. package/.next/types/app/api/boards/[id]/nodes/[nid]/route.ts +1 -1
  210. package/.next/types/app/api/boards/[id]/nodes/route.ts +1 -1
  211. package/.next/types/app/api/boards/[id]/reconcile-task-edges/route.ts +1 -1
  212. package/.next/types/app/api/boards/[id]/route.ts +1 -1
  213. package/.next/types/app/api/boards/purge-orphans/route.ts +1 -1
  214. package/.next/types/app/api/boards/reorder/route.ts +1 -1
  215. package/.next/types/app/api/boards/route.ts +1 -1
  216. package/.next/types/app/api/cwd/browse/route.ts +1 -1
  217. package/.next/types/app/api/cwd/validate/route.ts +1 -1
  218. package/.next/types/app/api/default-cwd/route.ts +1 -1
  219. package/.next/types/app/api/file-index/route.ts +1 -1
  220. package/.next/types/app/api/files/[...path]/route.ts +1 -1
  221. package/.next/types/app/api/git/diff/route.ts +1 -1
  222. package/.next/types/app/api/git/status/route.ts +1 -1
  223. package/.next/types/app/api/home/route.ts +1 -1
  224. package/.next/types/app/api/mcp/route.ts +1 -1
  225. package/.next/types/app/api/models/route.ts +1 -1
  226. package/.next/types/app/api/models-config/catalog/route.ts +1 -1
  227. package/.next/types/app/api/models-config/discover/route.ts +1 -1
  228. package/.next/types/app/api/models-config/route.ts +1 -1
  229. package/.next/types/app/api/models-config/test/route.ts +1 -1
  230. package/.next/types/app/api/plugins/route.ts +1 -1
  231. package/.next/types/app/api/project-trust/route.ts +1 -1
  232. package/.next/types/app/api/quota/route.ts +1 -1
  233. package/.next/types/app/api/search/route.ts +1 -1
  234. package/.next/types/app/api/sessions/[id]/auto-name/route.ts +1 -1
  235. package/.next/types/app/api/sessions/[id]/context/route.ts +1 -1
  236. package/.next/types/app/api/sessions/[id]/entries/[entryId]/thinking/route.ts +1 -1
  237. package/.next/types/app/api/sessions/[id]/export/route.ts +1 -1
  238. package/.next/types/app/api/sessions/[id]/route.ts +1 -1
  239. package/.next/types/app/api/sessions/[id]/state/route.ts +1 -1
  240. package/.next/types/app/api/sessions/[id]/todos/route.ts +1 -1
  241. package/.next/types/app/api/sessions/route.ts +1 -1
  242. package/.next/types/app/api/skills/check/route.ts +1 -1
  243. package/.next/types/app/api/skills/install/route.ts +1 -1
  244. package/.next/types/app/api/skills/route.ts +1 -1
  245. package/.next/types/app/api/skills/search/route.ts +1 -1
  246. package/.next/types/app/api/skills/update/route.ts +1 -1
  247. package/.next/types/app/api/task-card-questions/[id]/answer/route.ts +1 -1
  248. package/.next/types/app/api/task-card-questions/route.ts +1 -1
  249. package/.next/types/app/api/task-cards/[id]/route.ts +1 -1
  250. package/.next/types/app/api/task-cards/route.ts +1 -1
  251. package/.next/types/app/api/task-scheduler/status/route.ts +1 -1
  252. package/.next/types/app/api/tasks/[id]/board/route.ts +1 -1
  253. package/.next/types/app/api/tasks/[id]/route.ts +1 -1
  254. package/.next/types/app/api/tasks/reorder/route.ts +1 -1
  255. package/.next/types/app/api/tasks/route.ts +1 -1
  256. package/.next/types/app/api/terminal/[id]/events/route.ts +1 -1
  257. package/.next/types/app/api/terminal/[id]/route.ts +1 -1
  258. package/.next/types/app/api/terminal/route.ts +1 -1
  259. package/.next/types/app/api/worktrees/route.ts +1 -1
  260. package/.next/types/app/layout.ts +1 -1
  261. package/.next/types/app/page.ts +1 -1
  262. package/bin/pi-web-sky.js +15 -20
  263. package/lib/agent-client.test.mjs +64 -0
  264. package/lib/agent-client.ts +53 -0
  265. package/lib/agent-event-connection.test.mjs +193 -0
  266. package/lib/agent-event-connection.ts +202 -0
  267. package/lib/agent-event-stream.test.mjs +155 -0
  268. package/lib/agent-event-stream.ts +132 -0
  269. package/lib/agent-event-wire.test.mjs +278 -0
  270. package/lib/agent-event-wire.ts +107 -0
  271. package/lib/agent-phase.ts +24 -0
  272. package/lib/allowed-roots.ts +31 -0
  273. package/lib/ansi.test.mjs +52 -0
  274. package/lib/ansi.ts +211 -0
  275. package/lib/api-types.ts +139 -0
  276. package/lib/app-update.test.mjs +27 -0
  277. package/lib/app-update.ts +28 -0
  278. package/lib/atomic-file.test.mjs +37 -0
  279. package/lib/atomic-file.ts +34 -0
  280. package/lib/audit-session.ts +235 -0
  281. package/lib/bash-output.test.mjs +63 -0
  282. package/lib/bash-output.ts +53 -0
  283. package/lib/bg-image.ts +129 -0
  284. package/lib/board-events.ts +83 -0
  285. package/lib/board-purge.test.mjs +66 -0
  286. package/lib/board-purge.ts +65 -0
  287. package/lib/board-search.test.mjs +110 -0
  288. package/lib/board-search.ts +63 -0
  289. package/lib/board-store.test.mjs +542 -0
  290. package/lib/board-store.ts +794 -0
  291. package/lib/board-types.ts +96 -0
  292. package/lib/board-utils.ts +17 -0
  293. package/lib/bounded-form-data.test.mjs +54 -0
  294. package/lib/bounded-form-data.ts +49 -0
  295. package/lib/browser-notifications.test.mjs +167 -0
  296. package/lib/browser-notifications.ts +104 -0
  297. package/lib/chat-lazy-load.test.mjs +140 -0
  298. package/lib/chat-lazy-load.ts +64 -0
  299. package/lib/clipboard-paths.test.mjs +140 -0
  300. package/lib/clipboard-paths.ts +192 -0
  301. package/lib/clipboard.ts +18 -0
  302. package/lib/compaction-summary.test.mjs +44 -0
  303. package/lib/compaction-summary.ts +34 -0
  304. package/lib/custom-ui-terminal.test.mjs +32 -0
  305. package/lib/custom-ui-terminal.ts +27 -0
  306. package/lib/deepseek-pricing.test.mjs +120 -0
  307. package/lib/deepseek-pricing.ts +71 -0
  308. package/lib/directory-browser.test.mjs +60 -0
  309. package/lib/directory-browser.ts +83 -0
  310. package/lib/draft-stash.test.mjs +91 -0
  311. package/lib/draft-stash.ts +102 -0
  312. package/lib/draft-store.ts +105 -0
  313. package/lib/dropdown-direction.ts +27 -0
  314. package/lib/extension-command.test.mjs +63 -0
  315. package/lib/extension-command.ts +81 -0
  316. package/lib/file-access.test.mjs +31 -0
  317. package/lib/file-access.ts +59 -0
  318. package/lib/file-dirent.test.mjs +56 -0
  319. package/lib/file-dirent.ts +17 -0
  320. package/lib/file-explorer-state.test.mjs +63 -0
  321. package/lib/file-explorer-state.ts +36 -0
  322. package/lib/file-fuzzy.test.mjs +29 -0
  323. package/lib/file-fuzzy.ts +189 -0
  324. package/lib/file-links.test.mjs +92 -0
  325. package/lib/file-links.ts +148 -0
  326. package/lib/file-paths.ts +43 -0
  327. package/lib/file-types.test.mjs +34 -0
  328. package/lib/file-types.ts +73 -0
  329. package/lib/file-upload.test.mjs +70 -0
  330. package/lib/file-upload.ts +59 -0
  331. package/lib/file-viewer-state.test.mjs +20 -0
  332. package/lib/file-viewer-state.ts +15 -0
  333. package/lib/frontmatter.test.mjs +55 -0
  334. package/lib/frontmatter.ts +87 -0
  335. package/lib/git-changes.test.mjs +52 -0
  336. package/lib/git-changes.ts +232 -0
  337. package/lib/git-status.ts +47 -0
  338. package/lib/git-types.ts +29 -0
  339. package/lib/http-dispatcher.test.mjs +83 -0
  340. package/lib/http-dispatcher.ts +86 -0
  341. package/lib/i18n/format.test.mjs +19 -0
  342. package/lib/i18n/format.ts +60 -0
  343. package/lib/i18n/messages/en.ts +662 -0
  344. package/lib/i18n/messages/zh-CN.ts +661 -0
  345. package/lib/i18n/registry.test.mjs +32 -0
  346. package/lib/i18n/registry.ts +43 -0
  347. package/lib/i18n/types.ts +15 -0
  348. package/lib/image-attachments.test.mjs +27 -0
  349. package/lib/image-attachments.ts +56 -0
  350. package/lib/initial-navigation.test.mjs +65 -0
  351. package/lib/initial-navigation.ts +15 -0
  352. package/lib/markdown.test.mjs +104 -0
  353. package/lib/markdown.ts +347 -0
  354. package/lib/message-display.test.mjs +130 -0
  355. package/lib/message-display.ts +47 -0
  356. package/lib/model-catalog.test.mjs +327 -0
  357. package/lib/model-catalog.ts +414 -0
  358. package/lib/model-discovery-auth.ts +58 -0
  359. package/lib/model-discovery.test.mjs +50 -0
  360. package/lib/model-discovery.ts +75 -0
  361. package/lib/model-id-preference.ts +46 -0
  362. package/lib/model-scope.test.mjs +193 -0
  363. package/lib/model-scope.ts +198 -0
  364. package/lib/models-cache.test.mjs +122 -0
  365. package/lib/models-cache.ts +90 -0
  366. package/lib/models-config-store.test.mjs +200 -0
  367. package/lib/models-config-store.ts +84 -0
  368. package/lib/next-config-esm.test.mjs +14 -0
  369. package/lib/next-config.test.mjs +13 -0
  370. package/lib/node-version.test.mjs +33 -0
  371. package/lib/normalize.test.mjs +54 -0
  372. package/lib/normalize.ts +63 -0
  373. package/lib/npx.ts +62 -0
  374. package/lib/panel-layout.test.mjs +56 -0
  375. package/lib/panel-layout.ts +52 -0
  376. package/lib/patch.test.mjs +110 -0
  377. package/lib/patch.ts +134 -0
  378. package/lib/path-security.ts +42 -0
  379. package/lib/paths.test.mjs +58 -0
  380. package/lib/paths.ts +71 -0
  381. package/lib/pi-types.ts +183 -0
  382. package/lib/pi-web-options.test.mjs +63 -0
  383. package/lib/process-lifecycle.test.mjs +83 -0
  384. package/lib/project-command-env.test.mjs +298 -0
  385. package/lib/project-command-env.ts +129 -0
  386. package/lib/project-groups.test.mjs +60 -0
  387. package/lib/project-groups.ts +53 -0
  388. package/lib/project-identity.test.mjs +37 -0
  389. package/lib/project-identity.ts +26 -0
  390. package/lib/project-tree.test.mjs +115 -0
  391. package/lib/project-tree.ts +176 -0
  392. package/lib/project-trust.test.mjs +117 -0
  393. package/lib/project-trust.ts +48 -0
  394. package/lib/prompt-recovery.test.mjs +33 -0
  395. package/lib/prompt-recovery.ts +47 -0
  396. package/lib/provider-api-key-route.test.mjs +12 -0
  397. package/lib/provider-credential-store.test.mjs +126 -0
  398. package/lib/provider-credential-store.ts +114 -0
  399. package/lib/provider-listing-runtime.ts +37 -0
  400. package/lib/provider-listing.test.mjs +114 -0
  401. package/lib/provider-listing.ts +118 -0
  402. package/lib/provider-services.ts +64 -0
  403. package/lib/request-security.test.mjs +256 -0
  404. package/lib/request-security.ts +136 -0
  405. package/lib/rpc-manager-shutdown.test.mjs +372 -0
  406. package/lib/rpc-manager-widgets.test.mjs +342 -0
  407. package/lib/rpc-manager.test.mjs +139 -0
  408. package/lib/rpc-manager.ts +1703 -0
  409. package/lib/rpc-session-info.test.mjs +93 -0
  410. package/lib/session-delete.test.mjs +53 -0
  411. package/lib/session-delete.ts +86 -0
  412. package/lib/session-file-references-core.ts +82 -0
  413. package/lib/session-file-references.test.mjs +91 -0
  414. package/lib/session-file-references.ts +29 -0
  415. package/lib/session-path.test.mjs +24 -0
  416. package/lib/session-path.ts +11 -0
  417. package/lib/session-reader.pagination.test.mjs +115 -0
  418. package/lib/session-reader.test.mjs +446 -0
  419. package/lib/session-reader.ts +548 -0
  420. package/lib/session-resolve.test.mjs +65 -0
  421. package/lib/session-row-context-menu.test.mjs +46 -0
  422. package/lib/session-row-context-menu.ts +29 -0
  423. package/lib/session-scanner.test.mjs +151 -0
  424. package/lib/session-scanner.ts +238 -0
  425. package/lib/session-search.test.mjs +173 -0
  426. package/lib/session-search.ts +284 -0
  427. package/lib/session-stats.ts +26 -0
  428. package/lib/session-timing.test.mjs +107 -0
  429. package/lib/session-timing.ts +45 -0
  430. package/lib/session-title.test.mjs +284 -0
  431. package/lib/session-title.ts +255 -0
  432. package/lib/skill-frontmatter.test.mjs +75 -0
  433. package/lib/skill-frontmatter.ts +40 -0
  434. package/lib/skill-lock.test.mjs +182 -0
  435. package/lib/skill-lock.ts +146 -0
  436. package/lib/skill-updates.test.mjs +182 -0
  437. package/lib/skill-updates.ts +263 -0
  438. package/lib/skills-service.ts +16 -0
  439. package/lib/slash-display.test.mjs +122 -0
  440. package/lib/slash-display.ts +30 -0
  441. package/lib/sqlite-db.test.mjs +140 -0
  442. package/lib/sqlite-db.ts +205 -0
  443. package/lib/startup-preferences.test.mjs +130 -0
  444. package/lib/startup-preferences.ts +65 -0
  445. package/lib/streaming-message.test.mjs +212 -0
  446. package/lib/streaming-message.ts +151 -0
  447. package/lib/subagent-widget.test.mjs +87 -0
  448. package/lib/subagent-widget.ts +109 -0
  449. package/lib/sync-room-server.mjs +137 -0
  450. package/lib/task-card-store.test.mjs +309 -0
  451. package/lib/task-card-store.ts +559 -0
  452. package/lib/task-scheduler.ts +498 -0
  453. package/lib/task-store.test.mjs +216 -0
  454. package/lib/task-store.ts +271 -0
  455. package/lib/terminal-input.test.mjs +46 -0
  456. package/lib/terminal-input.ts +63 -0
  457. package/lib/terminal-manager.test.mjs +71 -0
  458. package/lib/terminal-manager.ts +245 -0
  459. package/lib/tool-execution-progress.test.mjs +32 -0
  460. package/lib/tool-execution-progress.ts +29 -0
  461. package/lib/tool-names.ts +25 -0
  462. package/lib/tool-preset-preference.test.mjs +72 -0
  463. package/lib/tool-preset-preference.ts +41 -0
  464. package/lib/tool-presets.test.mjs +56 -0
  465. package/lib/tool-presets.ts +42 -0
  466. package/lib/turn-written-files.test.mjs +137 -0
  467. package/lib/turn-written-files.ts +60 -0
  468. package/lib/types.ts +349 -0
  469. package/lib/wallpaper-settings.ts +191 -0
  470. package/lib/web-auth.test.mjs +50 -0
  471. package/lib/web-auth.ts +45 -0
  472. package/lib/workspace-memory.test.mjs +129 -0
  473. package/lib/workspace-memory.ts +98 -0
  474. package/lib/worktree.test.mjs +50 -0
  475. package/lib/worktree.ts +250 -0
  476. package/package.json +3 -1
  477. package/server.mjs +34 -0
  478. package/.next/server/chunks/7513.js +0 -30
  479. package/.next/static/chunks/5315.03de3c6abd8b283e.js +0 -4
  480. package/.next/static/chunks/next/dist/client/components/builtin/global-error-00a4badaccb388e6.js +0 -1
  481. package/.next/static/css/cf25164cb7c04d95.css +0 -1
  482. /package/.next/static/{Ud6Y2ONkGuPVFbo1xZQvk → 7sDSz-wW-RWYnxh1l6-fD}/_buildManifest.js +0 -0
  483. /package/.next/static/{Ud6Y2ONkGuPVFbo1xZQvk → 7sDSz-wW-RWYnxh1l6-fD}/_ssgManifest.js +0 -0
@@ -0,0 +1,542 @@
1
+ import { test, beforeEach, afterEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { createJiti } from "jiti";
5
+
6
+ const jiti = createJiti(import.meta.url, { interopDefault: true, moduleCache: false });
7
+ const { setDbForTesting, SCHEMA_VERSION } = await jiti.import("./sqlite-db.ts");
8
+ const {
9
+ getBoard,
10
+ listBoards,
11
+ createBoard,
12
+ renameBoard,
13
+ deleteBoard,
14
+ deleteBoardCascade,
15
+ getOrCreateTaskBoard,
16
+ getBoardCanvas,
17
+ putBoardCanvas,
18
+ addNode,
19
+ patchNode,
20
+ deleteNode,
21
+ deleteNodesByIds,
22
+ bindNodeToSession,
23
+ getNodeByGlobalId,
24
+ addEdge,
25
+ deleteEdge,
26
+ getSystemRunningBoard,
27
+ reorderBoards,
28
+ syncCardEdges,
29
+ syncExecEdge,
30
+ removeSessionFromBoards,
31
+ } = await jiti.import("./board-store.ts");
32
+ const { createCard, addLink, replaceLinks, updateCard, getCard } = await jiti.import("./task-card-store.ts");
33
+ const { SYSTEM_RUNNING_BOARD_ID } = await jiti.import("./board-types.ts");
34
+
35
+ const PROJECT = "test-project";
36
+
37
+ let db;
38
+
39
+
40
+ beforeEach(() => {
41
+ db = new DatabaseSync(":memory:");
42
+ setDbForTesting(db);
43
+ });
44
+
45
+ afterEach(() => {
46
+ db.close();
47
+ });
48
+
49
+ test("schema version reaches latest with board tables", () => {
50
+ const tables = db
51
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE 'board_%' ORDER BY name")
52
+ .all();
53
+ const names = tables.map((t) => t.name).sort();
54
+ assert.deepEqual(names, ["board_edges", "board_nodes", "board_view", "boards"].sort());
55
+ const boards = db
56
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'boards'")
57
+ .all();
58
+ assert.equal(boards.length, 1);
59
+ assert.equal(SCHEMA_VERSION, 8);
60
+ const row = db.prepare("PRAGMA user_version").get();
61
+ assert.equal(row.user_version, 8);
62
+ // v5 迁移:boards.task_id 列存在
63
+ const cols = db.prepare("PRAGMA table_info(boards)").all().map((c) => c.name);
64
+ assert.ok(cols.includes("task_id"));
65
+ // v6 迁移:task_id 唯一索引(并发防重复创建任务看板)
66
+ const uniq = db.prepare("SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'boards' AND name = 'idx_boards_task_unique'").all();
67
+ assert.equal(uniq.length, 1);
68
+ });
69
+
70
+ test("board CRUD: create / rename / list / delete", () => {
71
+ const b = createBoard(PROJECT, "默认看板");
72
+ assert.equal(b.isSystem, false);
73
+ assert.equal(b.name, "默认看板");
74
+ assert.equal(b.nodeCount, 0);
75
+
76
+ const renamed = renameBoard(b.id, "重构看板");
77
+ assert.equal(renamed?.name, "重构看板");
78
+ assert.equal(getBoard(b.id)?.name, "重构看板");
79
+
80
+ const other = createBoard("other-project", "别的项目");
81
+ assert.equal(listBoards(PROJECT).length, 1);
82
+ assert.equal(listBoards("other-project").length, 1);
83
+ assert.equal(getBoard(other.id)?.projectKey, "other-project");
84
+
85
+ assert.equal(deleteBoard(b.id), true);
86
+ assert.equal(getBoard(b.id), undefined);
87
+ assert.equal(listBoards(PROJECT).length, 0);
88
+ });
89
+
90
+ test("empty projectKey / name rejected", () => {
91
+ assert.throws(() => createBoard("", "x"), /projectKey is required/);
92
+ assert.throws(() => createBoard(PROJECT, " "), /name must not be empty/);
93
+ });
94
+
95
+ test("system running board: always present, not deletable/renamable", () => {
96
+ const sys = getSystemRunningBoard();
97
+ assert.equal(sys.id, SYSTEM_RUNNING_BOARD_ID);
98
+ assert.equal(sys.isSystem, true);
99
+ assert.equal(getBoard(SYSTEM_RUNNING_BOARD_ID)?.isSystem, true);
100
+ assert.equal(renameBoard(SYSTEM_RUNNING_BOARD_ID, "x"), null);
101
+ assert.equal(deleteBoard(SYSTEM_RUNNING_BOARD_ID), false);
102
+ assert.equal(addNode(SYSTEM_RUNNING_BOARD_ID, { x: 0, y: 0 }), null);
103
+ });
104
+
105
+ test("nodes: add / patch / delete cascade edges", () => {
106
+ const b = createBoard(PROJECT, "b");
107
+ const n1 = addNode(b.id, { x: 10, y: 20, w: 280, h: 120, refId: "s1" });
108
+ const n2 = addNode(b.id, { x: 400, y: 50, refId: "s2" });
109
+ assert.ok(n1 && n2);
110
+ assert.equal(n1.kind, "session");
111
+ assert.equal(n1.x, 10);
112
+
113
+ const patched = patchNode(b.id, n1.id, { x: 99, expanded: true });
114
+ assert.equal(patched?.x, 99);
115
+ assert.equal(patched?.expanded, true);
116
+
117
+ const e = addEdge(b.id, { fromId: n1.id, toId: n2.id, label: "depends", dashed: true });
118
+ assert.ok(e);
119
+ assert.equal(e.dashed, true);
120
+
121
+ // deleting n1 removes the edge
122
+ assert.equal(deleteNode(b.id, n1.id), true);
123
+ const canvas = getBoardCanvas(b.id);
124
+ assert.equal(canvas?.nodes.length, 1);
125
+ assert.equal(canvas?.edges.length, 0);
126
+ assert.equal(deleteNode(b.id, n1.id), false);
127
+ });
128
+
129
+ test("getOrCreateTaskBoard: 多次调用同一行 + task_id UNIQUE 拒绝重复(并发防重建)", () => {
130
+ const b1 = getOrCreateTaskBoard("task-conc-1", PROJECT, "并发任务");
131
+ // 连续多次调用(模拟并发请求)→ 恒返回同一看板
132
+ for (let i = 0; i < 5; i += 1) {
133
+ const again = getOrCreateTaskBoard("task-conc-1", PROJECT, "并发任务");
134
+ assert.equal(again.id, b1.id);
135
+ }
136
+ // 库里 task_id 唯一(UNIQUE 索引生效)
137
+ const rows = db.prepare("SELECT COUNT(*) c FROM boards WHERE task_id = ?").get("task-conc-1");
138
+ assert.equal(rows.c, 1);
139
+ // 手动重复插入被 UNIQUE 拒绝(并发时只有第一个 INSERT 成功)
140
+ assert.throws(() => {
141
+ db.prepare("INSERT INTO boards (id, project_key, name, is_system, task_id, sort_order, created, updated) VALUES (?, ?, ?, 0, ?, 0, 1, 1)")
142
+ .run("dup-id", PROJECT, "dup", "task-conc-1");
143
+ }, /UNIQUE/i);
144
+ // 不同 task_id 互不影响
145
+ const b2 = getOrCreateTaskBoard("task-conc-2", PROJECT, "另一个");
146
+ assert.notEqual(b2.id, b1.id);
147
+ assert.equal(db.prepare("SELECT COUNT(*) c FROM boards WHERE task_id IN ('task-conc-1','task-conc-2')").get().c, 2);
148
+ });
149
+
150
+ test("patchNode: refId 可定向写入/缺省保留/null 解绑(draft 卡转正)", () => {
151
+ const b = createBoard(PROJECT, "b");
152
+ // draft 卡:refId 为空
153
+ const draft = addNode(b.id, { x: 0, y: 0, refId: null });
154
+ assert.ok(draft);
155
+ assert.equal(draft.refId, null);
156
+ const beforeUpdated = getBoard(b.id)?.updated ?? 0;
157
+
158
+ // 转正:写入真实会话 id
159
+ const bound = patchNode(b.id, draft.id, { refId: "session-real-1" });
160
+ assert.equal(bound?.refId, "session-real-1");
161
+ // 缺省不改
162
+ assert.equal(patchNode(b.id, draft.id, { x: 55 })?.refId, "session-real-1");
163
+ // 显式 null 解绑
164
+ assert.equal(patchNode(b.id, draft.id, { refId: null })?.refId, null);
165
+
166
+ // PATCH 会 bump boards.updated(乐观锁基线):非递减(同毫秒内可能相等,不依赖单调性)
167
+ const afterUpdated = getBoard(b.id)?.updated ?? 0;
168
+ assert.ok(afterUpdated >= beforeUpdated);
169
+ // 携带过期基线的全量保存被 409 拒绝:不回头把绑定覆盖回 draft(哨兵基线 0 必过期)
170
+ const stale = putBoardCanvas(b.id, {
171
+ nodes: [{ id: draft.id, boardId: b.id, kind: "session", refId: null, x: 0, y: 0, w: 340, h: 160, expanded: false, props: {}, created: 1, updated: 1 }],
172
+ }, { baseUpdated: 0 });
173
+ assert.equal(stale, "conflict");
174
+ // 用当前基线则放行
175
+ const fresh = putBoardCanvas(b.id, {
176
+ nodes: [{ id: draft.id, boardId: b.id, kind: "session", refId: "session-real-1", x: 0, y: 0, w: 340, h: 160, expanded: false, props: {}, created: 1, updated: 1 }],
177
+ }, { baseUpdated: afterUpdated });
178
+ assert.equal(fresh, true);
179
+ assert.equal(getBoardCanvas(b.id)?.nodes[0].refId, "session-real-1");
180
+ });
181
+
182
+ test("deleteNodesByIds: 跨看板批量删节点 + 级联删边 + bump updated", () => {
183
+ const b1 = createBoard(PROJECT, "b1");
184
+ const b2 = createBoard(PROJECT, "b2");
185
+ const n1 = addNode(b1.id, { x: 0, y: 0, refId: "s1" });
186
+ const n2 = addNode(b1.id, { x: 100, y: 0, refId: "s2" });
187
+ const n3 = addNode(b2.id, { x: 0, y: 0, refId: "s3" });
188
+ assert.ok(n1 && n2 && n3);
189
+ // 连线:n1→n2(同板)、n1→n3(跨板不允许 addEdge,用同板验证)
190
+ assert.ok(addEdge(b1.id, { fromId: n1.id, toId: n2.id, label: "x" }));
191
+ const before1 = getBoard(b1.id)?.updated ?? 0;
192
+
193
+ // 删 n1 + n3(跨两个 board)→ n1 的边被级联删,n2 保留
194
+ const r = deleteNodesByIds([n1.id, n3.id, "no-such"]);
195
+ assert.equal(r.deletedNodes, 2);
196
+ assert.equal(r.deletedEdges, 1);
197
+ assert.deepEqual(r.boards.sort(), [b1.id, b2.id].sort());
198
+ const c1 = getBoardCanvas(b1.id);
199
+ assert.equal(c1?.nodes.length, 1);
200
+ assert.equal(c1?.nodes[0].id, n2.id);
201
+ assert.equal(c1?.edges.length, 0);
202
+ const c2 = getBoardCanvas(b2.id);
203
+ assert.equal(c2?.nodes.length, 0);
204
+ // bump 看板 updated(乐观锁基线;同毫秒内可能相等,不依赖单调性)
205
+ assert.ok((getBoard(b1.id)?.updated ?? 0) >= before1);
206
+ // 空列表 / 系统看板节点不删
207
+ assert.deepEqual(deleteNodesByIds([]), { deletedNodes: 0, deletedEdges: 0, boards: [] });
208
+ });
209
+
210
+ test("bindNodeToSession: 全局 nodeId 写 ref_id(服务端后台转正)+ bump 看板 updated", () => {
211
+ const b = createBoard(PROJECT, "b");
212
+ const draft = addNode(b.id, { x: 0, y: 0, refId: null });
213
+ assert.ok(draft);
214
+ assert.equal(draft.refId, null);
215
+ const beforeUpdated = getBoard(b.id)?.updated ?? 0;
216
+
217
+ // 服务端拿到 boardNodeId 后直接绑定(不依赖 boardId,nodeId 全局唯一)
218
+ const bound = bindNodeToSession(draft.id, "session-real-2");
219
+ assert.ok(bound);
220
+ assert.equal(bound.refId, "session-real-2");
221
+ assert.equal(bound.boardId, b.id);
222
+ // bump 看板 updated(乐观锁基线:迟到的旧全量保存会被 409 拒绝)
223
+ const afterUpdated = getBoard(b.id)?.updated ?? 0;
224
+ assert.ok(afterUpdated >= beforeUpdated);
225
+ // 全量保存读到的 refId 已是绑定值
226
+ assert.equal(getBoardCanvas(b.id)?.nodes[0].refId, "session-real-2");
227
+
228
+ // 不存在的 nodeId → null;系统看板节点 → null
229
+ assert.equal(bindNodeToSession("no-such-node", "x"), null);
230
+ });
231
+
232
+ test("getNodeByGlobalId: 按全局 nodeId 读节点(未转正卡轮询用)", () => {
233
+ const b = createBoard(PROJECT, "b");
234
+ const n1 = addNode(b.id, { x: 0, y: 0, refId: null });
235
+ const n2 = addNode(b.id, { x: 100, y: 0, refId: "s1" });
236
+ assert.ok(n1 && n2);
237
+
238
+ // draft 卡:refId null
239
+ assert.equal(getNodeByGlobalId(n1.id)?.refId, null);
240
+ // 已绑定:refId 有值
241
+ assert.equal(getNodeByGlobalId(n2.id)?.refId, "s1");
242
+ // 轮询发现服务端已写入 ref_id → 前端据此转正
243
+ bindNodeToSession(n1.id, "session-real-3");
244
+ assert.equal(getNodeByGlobalId(n1.id)?.refId, "session-real-3");
245
+ assert.equal(getNodeByGlobalId("no-such"), undefined);
246
+ });
247
+
248
+ test("addEdge rejects self-loop and cross-board refs", () => {
249
+ const b = createBoard(PROJECT, "b");
250
+ const b2 = createBoard(PROJECT, "b2");
251
+ const n1 = addNode(b.id, { x: 0, y: 0 });
252
+ const n2 = addNode(b.id, { x: 100, y: 0 });
253
+ const n3 = addNode(b2.id, { x: 0, y: 0 });
254
+ assert.ok(n1 && n2 && n3);
255
+ assert.equal(addEdge(b.id, { fromId: n1.id, toId: n1.id }), null);
256
+ assert.equal(addEdge(b.id, { fromId: n1.id, toId: n3.id }), null);
257
+ assert.ok(addEdge(b.id, { fromId: n1.id, toId: n2.id }));
258
+ assert.equal(deleteEdge(b.id, "nope"), false);
259
+ });
260
+
261
+ test("putBoardCanvas full replace inside transaction, rollback on error", () => {
262
+ const b = createBoard(PROJECT, "b");
263
+ const n1 = addNode(b.id, { x: 0, y: 0, refId: "s1" });
264
+ const n2 = addNode(b.id, { x: 100, y: 0, refId: "s2" });
265
+ assert.ok(n1 && n2);
266
+ addEdge(b.id, { fromId: n1.id, toId: n2.id });
267
+
268
+ const nodes = [
269
+ { ...n1, x: 5, y: 5, w: 300, h: 150, props: { color: "#f00" }, created: n1.created, updated: n1.updated },
270
+ ];
271
+ const edges = [];
272
+ const ok = putBoardCanvas(b.id, {
273
+ nodes,
274
+ edges,
275
+ view: { boardId: b.id, cameraX: -50, cameraY: -30, cameraZ: 0.8, updated: 1 },
276
+ });
277
+ assert.equal(ok, true);
278
+
279
+ const canvas = getBoardCanvas(b.id);
280
+ assert.equal(canvas?.nodes.length, 1);
281
+ assert.equal(canvas?.nodes[0].x, 5);
282
+ assert.deepEqual(canvas?.nodes[0].props, { color: "#f00" });
283
+ assert.equal(canvas?.edges.length, 0);
284
+ assert.equal(canvas?.view?.cameraZ, 0.8);
285
+
286
+ // system board / missing board not writable
287
+ assert.equal(putBoardCanvas(SYSTEM_RUNNING_BOARD_ID, { nodes: [] }), false);
288
+ assert.equal(putBoardCanvas("missing", { nodes: [] }), false);
289
+ });
290
+
291
+ test("putBoardCanvas empty overwrite guard + allowEmpty escape hatch", () => {
292
+ const b = createBoard(PROJECT, "b");
293
+ addNode(b.id, { x: 0, y: 0, refId: "s1" });
294
+ // 非空看板被空节点集覆盖 → 拒绝(防客户端未加载完成清空看板)
295
+ assert.equal(putBoardCanvas(b.id, { nodes: [] }), "empty-overwrite");
296
+ assert.equal(getBoardCanvas(b.id)?.nodes.length, 1);
297
+ // 显式 allowEmpty → 放行
298
+ assert.equal(putBoardCanvas(b.id, { nodes: [], edges: [] }, { allowEmpty: true }), true);
299
+ assert.equal(getBoardCanvas(b.id)?.nodes.length, 0);
300
+ // 原本就空的看板,空覆盖本就允许
301
+ assert.equal(putBoardCanvas(b.id, { nodes: [] }), true);
302
+ });
303
+
304
+ test("rollback keeps db consistent when transaction throws", () => {
305
+ const b = createBoard(PROJECT, "b");
306
+ const n1 = addNode(b.id, { x: 0, y: 0 });
307
+ assert.ok(n1);
308
+ // addEdge with a bad ref throws inside a transaction? no — returns null.
309
+ // Force a real throw via a duplicate primary key through putBoardCanvas.
310
+ const nodes = [
311
+ { ...n1, id: "dup", created: n1.created, updated: n1.updated },
312
+ { ...n1, id: "dup", created: n1.created, updated: n1.updated },
313
+ ];
314
+ assert.throws(() => putBoardCanvas(b.id, { nodes }));
315
+ // rollback: original node still there
316
+ const canvas = getBoardCanvas(b.id);
317
+ assert.equal(canvas?.nodes.length, 1);
318
+ assert.equal(canvas?.nodes[0].id, n1.id);
319
+ });
320
+
321
+ test("board reorder within project", () => {
322
+ const a = createBoard(PROJECT, "A");
323
+ const b = createBoard(PROJECT, "B");
324
+ const c = createBoard(PROJECT, "C");
325
+ // 新板置顶:创建顺序 C, B, A
326
+ assert.deepEqual(listBoards(PROJECT).map((x) => x.id), [c.id, b.id, a.id]);
327
+
328
+ const reordered = reorderBoards(PROJECT, [a.id, c.id, b.id]);
329
+ assert.deepEqual(reordered.map((x) => x.id), [a.id, c.id, b.id]);
330
+
331
+ // foreign id → rollback keeps order
332
+ assert.throws(() => reorderBoards(PROJECT, [a.id, "foreign"]));
333
+ assert.deepEqual(listBoards(PROJECT).map((x) => x.id), [a.id, c.id, b.id]);
334
+ });
335
+
336
+ test("canvas of missing board is null", () => {
337
+ assert.equal(getBoardCanvas("missing"), null);
338
+ assert.equal(addNode("missing", { x: 0, y: 0 }), null);
339
+ });
340
+
341
+ test("task board: lazy create (upsert), id = taskId, not in manual reorder scope", () => {
342
+ const b1 = getOrCreateTaskBoard("task-1", PROJECT, "任务一");
343
+ assert.equal(b1.id, "task-1");
344
+ assert.equal(b1.taskId, "task-1");
345
+ assert.equal(b1.isSystem, false);
346
+ assert.equal(b1.name, "任务一");
347
+
348
+ // 幂等:再次调用返回同一 board(不重复创建)
349
+ const b2 = getOrCreateTaskBoard("task-1", PROJECT, "任务一");
350
+ assert.equal(b2.id, b1.id);
351
+ assert.equal(getBoard("task-1")?.taskId, "task-1");
352
+
353
+ // 出现在 listBoards 中(展示层自行过滤 taskId == null)
354
+ assert.ok(listBoards(PROJECT).some((b) => b.id === "task-1"));
355
+ });
356
+
357
+ test("task board: deleteBoardCascade removes nodes/edges/view within caller transaction", () => {
358
+ const b = getOrCreateTaskBoard("task-2", PROJECT, "任务二");
359
+ const n1 = addNode(b.id, { x: 0, y: 0, refId: "s1" });
360
+ const n2 = addNode(b.id, { x: 100, y: 0, refId: "s2" });
361
+ assert.ok(n1 && n2);
362
+ addEdge(b.id, { fromId: n1.id, toId: n2.id });
363
+ putBoardCanvas(b.id, {
364
+ nodes: undefined,
365
+ edges: undefined,
366
+ view: { boardId: b.id, cameraX: 1, cameraY: 2, cameraZ: 3, updated: 1 },
367
+ });
368
+
369
+ // 事务内调用:异常回滚不残留
370
+ const dbLocal = db;
371
+ dbLocal.exec("BEGIN");
372
+ try {
373
+ deleteBoardCascade(b.id);
374
+ throw new Error("boom");
375
+ } catch {
376
+ dbLocal.exec("ROLLBACK");
377
+ }
378
+ assert.ok(getBoard(b.id)); // 回滚后看板仍在
379
+
380
+ dbLocal.exec("BEGIN");
381
+ try {
382
+ deleteBoardCascade(b.id);
383
+ dbLocal.exec("COMMIT");
384
+ } catch {
385
+ dbLocal.exec("ROLLBACK");
386
+ throw new Error("unexpected");
387
+ }
388
+ assert.equal(getBoard(b.id), undefined);
389
+ assert.equal(getBoardCanvas(b.id), null);
390
+ });
391
+
392
+ test("syncCardEdges: 依赖边自动生成/更新/补回(label=kind,禁删语义)", () => {
393
+ const b = createBoard(PROJECT, "看板A");
394
+ const c1 = createCard({ boardId: b.id, projectKey: PROJECT, name: "卡1" });
395
+ const c2 = createCard({ boardId: b.id, projectKey: PROJECT, name: "卡2" });
396
+ const c3 = createCard({ boardId: b.id, projectKey: PROJECT, name: "卡3" });
397
+ const n1 = addNode(b.id, { kind: "taskcard", refId: c1.id, x: 10, y: 10 });
398
+ const n2 = addNode(b.id, { kind: "taskcard", refId: c2.id, x: 300, y: 10 });
399
+ const n3 = addNode(b.id, { kind: "taskcard", refId: c3.id, x: 600, y: 10 });
400
+
401
+ addLink(c1.id, c2.id, "prerequisite");
402
+ syncCardEdges(c1.id);
403
+
404
+ const canvas = getBoardCanvas(b.id);
405
+ const auto = canvas.edges.filter((e) => e.fromId === n1.id);
406
+ assert.equal(auto.length, 1);
407
+ assert.equal(auto[0].toId, n2.id);
408
+ assert.equal(auto[0].label, "prerequisite");
409
+
410
+ // 改 target:删旧边加新边
411
+ replaceLinks(c1.id, [c3.id], [c2.id]);
412
+ syncCardEdges(c1.id);
413
+ let edges = getBoardCanvas(b.id).edges.filter((e) => e.fromId === n1.id);
414
+ assert.deepEqual(
415
+ edges.map((e) => [e.toId, e.label]).sort(),
416
+ [[n2.id, "related"], [n3.id, "prerequisite"]].sort(),
417
+ );
418
+
419
+ // 手动删自动边 → syncCardEdges 补回
420
+ const autoEdge = getBoardCanvas(b.id).edges.find((e) => e.fromId === n1.id && e.label === "prerequisite");
421
+ assert.ok(autoEdge);
422
+ deleteEdge(b.id, autoEdge.id);
423
+ assert.equal(getBoardCanvas(b.id).edges.filter((e) => e.id === autoEdge.id).length, 0);
424
+ syncCardEdges(c1.id);
425
+ assert.ok(getBoardCanvas(b.id).edges.find((e) => e.fromId === n1.id && e.label === "prerequisite"));
426
+
427
+ // 被依赖方删除 link 后,再次 sync 清掉出边
428
+ replaceLinks(c1.id, [], []);
429
+ syncCardEdges(c1.id);
430
+ assert.equal(getBoardCanvas(b.id).edges.filter((e) => e.fromId === n1.id).length, 0);
431
+ });
432
+
433
+ test("syncCardEdges: target 无画布节点时跳过不崩;无 node 的卡直接返回", () => {
434
+ const b = createBoard(PROJECT, "看板A");
435
+ const c1 = createCard({ boardId: b.id, projectKey: PROJECT, name: "卡1" });
436
+ const c2 = createCard({ boardId: b.id, projectKey: PROJECT, name: "卡2" });
437
+ addNode(b.id, { kind: "taskcard", refId: c1.id, x: 10, y: 10 });
438
+ // c2 无 node
439
+ addLink(c1.id, c2.id, "prerequisite");
440
+ syncCardEdges(c1.id); // 不抛错,无边
441
+ assert.equal(getBoardCanvas(b.id).edges.length, 0);
442
+
443
+ // 无 node 的卡直接返回
444
+ const c3 = createCard({ boardId: b.id, projectKey: PROJECT, name: "卡3" });
445
+ addLink(c3.id, c1.id, "prerequisite");
446
+ syncCardEdges(c3.id);
447
+ assert.equal(getBoardCanvas(b.id).edges.length, 0);
448
+ });
449
+
450
+ test("syncExecEdge: 绑 sessionId → 建 label=exec 边;手动删补回;解绑删边", () => {
451
+ const b = createBoard(PROJECT, "看板A");
452
+ const c = createCard({ boardId: b.id, projectKey: PROJECT, name: "任务" });
453
+ const cardNode = addNode(b.id, { kind: "taskcard", refId: c.id, x: 10, y: 10 });
454
+ const sessionNode = addNode(b.id, { kind: "session", refId: "sess-1", x: 300, y: 10 });
455
+ const boardId = b.id;
456
+
457
+ // 未绑定 → 无边
458
+ syncExecEdge(c.id);
459
+ assert.equal(getBoardCanvas(boardId).edges.filter((e) => e.label === "exec").length, 0);
460
+
461
+ // 绑定 → 建边(from=taskcard node, to=session node)
462
+ updateCard(c.id, { sessionId: "sess-1" });
463
+ syncExecEdge(c.id);
464
+ const execEdge = getBoardCanvas(boardId).edges.find((e) => e.label === "exec");
465
+ assert.ok(execEdge);
466
+ assert.equal(execEdge.fromId, cardNode.id);
467
+ assert.equal(execEdge.toId, sessionNode.id);
468
+
469
+ // 手动删边 → 补回(禁删语义)
470
+ deleteEdge(boardId, execEdge.id);
471
+ assert.equal(getBoardCanvas(boardId).edges.filter((e) => e.label === "exec").length, 0);
472
+ syncExecEdge(c.id);
473
+ assert.ok(
474
+ getBoardCanvas(boardId).edges.find(
475
+ (e) => e.label === "exec" && e.fromId === cardNode.id && e.toId === sessionNode.id,
476
+ ),
477
+ );
478
+
479
+ // 解绑 → 删边
480
+ updateCard(c.id, { sessionId: null });
481
+ syncExecEdge(c.id);
482
+ assert.equal(getBoardCanvas(boardId).edges.filter((e) => e.label === "exec").length, 0);
483
+ });
484
+
485
+ test("syncExecEdge: session 节点缺失不建线;节点后补/换绑重建", () => {
486
+ const b = createBoard(PROJECT, "看板A");
487
+ const c = createCard({ boardId: b.id, projectKey: PROJECT, name: "任务" });
488
+ addNode(b.id, { kind: "taskcard", refId: c.id, x: 10, y: 10 });
489
+ const boardId = b.id;
490
+
491
+ // 绑到不存在的会话 → 不建线(无残留边)
492
+ updateCard(c.id, { sessionId: "ghost-session" });
493
+ syncExecEdge(c.id);
494
+ assert.equal(getBoardCanvas(boardId).edges.filter((e) => e.label === "exec").length, 0);
495
+
496
+ // 换绑到 sess-2(节点尚不存在)→ 仍不建线
497
+ updateCard(c.id, { sessionId: "sess-2" });
498
+ syncExecEdge(c.id);
499
+ assert.equal(getBoardCanvas(boardId).edges.filter((e) => e.label === "exec").length, 0);
500
+
501
+ // 会话节点后补 → 建线
502
+ const s2 = addNode(boardId, { kind: "session", refId: "sess-2", x: 400, y: 10 });
503
+ syncExecEdge(c.id);
504
+ const e2 = getBoardCanvas(boardId).edges.find((x) => x.label === "exec");
505
+ assert.ok(e2);
506
+ assert.equal(e2.toId, s2.id);
507
+
508
+ // 换绑到另一会话 → 边改向,只保留一条
509
+ const s3 = addNode(boardId, { kind: "session", refId: "sess-3", x: 500, y: 10 });
510
+ updateCard(c.id, { sessionId: "sess-3" });
511
+ syncExecEdge(c.id);
512
+ const e3 = getBoardCanvas(boardId).edges.find((x) => x.label === "exec");
513
+ assert.ok(e3);
514
+ assert.equal(e3.toId, s3.id);
515
+ assert.equal(getBoardCanvas(boardId).edges.filter((x) => x.label === "exec").length, 1);
516
+ });
517
+
518
+ test("removeSessionFromBoards: 删会话清任务卡引用 + exec 边 + 画布节点,幂等", () => {
519
+ const b = createBoard(PROJECT, "看板A");
520
+ const b2 = createBoard(PROJECT, "看板B");
521
+ const c = createCard({ boardId: b.id, projectKey: PROJECT, name: "任务" });
522
+ addNode(b.id, { kind: "taskcard", refId: c.id, x: 10, y: 10 });
523
+ addNode(b.id, { kind: "session", refId: "sess-x", x: 300, y: 10 });
524
+ // 同一会话也在手动看板上有卡
525
+ addNode(b2.id, { kind: "session", refId: "sess-x", x: 0, y: 0 });
526
+ updateCard(c.id, { sessionId: "sess-x" });
527
+ syncExecEdge(c.id);
528
+ assert.ok(getBoardCanvas(b.id).edges.find((e) => e.label === "exec"));
529
+
530
+ const removed = removeSessionFromBoards("sess-x");
531
+ assert.equal(removed.removedNodes, 2);
532
+ // 受影响看板 updated 返回(乐观锁基线刷新用)
533
+ assert.equal(removed.boards.length, 2);
534
+ // exec 边消失 + 会话节点从两个看板消失
535
+ assert.equal(getBoardCanvas(b.id).edges.filter((e) => e.label === "exec").length, 0);
536
+ assert.equal(getBoardCanvas(b.id).nodes.filter((n) => n.refId === "sess-x").length, 0);
537
+ assert.equal(getBoardCanvas(b2.id).nodes.filter((n) => n.refId === "sess-x").length, 0);
538
+ // 任务卡 session_id 清空
539
+ assert.equal(getCard(c.id)?.sessionId, null);
540
+ // 幂等:无引用返回 0
541
+ assert.equal(removeSessionFromBoards("sess-x").removedNodes, 0);
542
+ });