@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,216 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
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 { getDb, setDbForTesting } = await jiti.import("./sqlite-db.ts");
8
+ const { createTask, deleteTask, listTasks, updateTask, taskForSession, assignSessionToTask, reorderTasks } = await jiti.import("./task-store.ts");
9
+ const { getBoard, getBoardCanvas, getOrCreateTaskBoard, addNode, createBoard } = await jiti.import("./board-store.ts");
10
+ const { createCard, addLink, createQuestion } = await jiti.import("./task-card-store.ts");
11
+
12
+ function freshDb() {
13
+ setDbForTesting(new DatabaseSync(":memory:"));
14
+ }
15
+
16
+ // Seed a session_meta row directly — in real flow membership writes these
17
+ // rows, but a NULL-task row is exactly a temp session's state.
18
+ function seedSession(sessionId, taskId) {
19
+ getDb()
20
+ .prepare("INSERT INTO session_meta (session_id, task_id, updated) VALUES (?, ?, ?)")
21
+ .run(sessionId, taskId, Date.now());
22
+ }
23
+
24
+ test("createTask and listTasks per project", () => {
25
+ freshDb();
26
+ const a = createTask("proj-a", " 重构登录 ");
27
+ assert.equal(a.name, "重构登录");
28
+ assert.deepEqual(a.sessionIds, []);
29
+ createTask("proj-a", "另一任务");
30
+ createTask("proj-b", "别项目");
31
+ const names = listTasks("proj-a").map((t) => t.name).sort();
32
+ assert.deepEqual(names, ["另一任务", "重构登录"]);
33
+ assert.equal(listTasks("proj-b").length, 1);
34
+ assert.equal(listTasks("proj-none").length, 0);
35
+ });
36
+
37
+ test("updateTask tracks membership diff without touching other tasks", () => {
38
+ freshDb();
39
+ const a = createTask("p", "A");
40
+ const b = createTask("p", "B");
41
+ seedSession("s1", null); // temp
42
+ seedSession("s2", null); // temp
43
+ seedSession("s3", b.id); // belongs to task B
44
+
45
+ let updated = updateTask(a.id, { sessionIds: ["s1", "s3"] });
46
+ // listTaskSessionIds 按 pinned DESC, updated DESC, rowid DESC 排序:
47
+ // 同批分配时后插入的行在前,这里做顺序无关的集合断言。
48
+ assert.deepEqual([...updated.sessionIds].sort(), ["s1", "s3"]);
49
+ // s1 newly assigned, s3 moved away from B, B untouched otherwise.
50
+ assert.equal(taskForSession("s1"), a.id);
51
+ assert.equal(taskForSession("s3"), a.id);
52
+ assert.equal(taskForSession("s2"), null);
53
+ assert.deepEqual(listTasks("p").find((t) => t.id === b.id).sessionIds, []);
54
+
55
+ // Replace: s1 falls back to temp, s3 kept.
56
+ updated = updateTask(a.id, { sessionIds: ["s3"] });
57
+ assert.deepEqual(updated.sessionIds, ["s3"]);
58
+ assert.equal(taskForSession("s1"), null);
59
+ assert.equal(taskForSession("s3"), a.id);
60
+
61
+ // Rename only — membership untouched.
62
+ updated = updateTask(a.id, { name: "改名任务" });
63
+ assert.equal(updated.name, "改名任务");
64
+ assert.deepEqual(updated.sessionIds, ["s3"]);
65
+
66
+ // Pin sorting: pinned sessions float before unpinned regardless of recency.
67
+ seedSession("sp", a.id);
68
+ getDb().prepare("UPDATE session_meta SET pinned = 1 WHERE session_id = ?").run("sp");
69
+ updated = updateTask(a.id, { sessionIds: ["sp", "s3", "s1"] });
70
+ assert.deepEqual(updated.pinnedSessionIds, ["sp"]);
71
+ assert.equal(updated.sessionIds[0], "sp");
72
+ });
73
+
74
+ test("deleteTask unassigns members and removes the task", () => {
75
+ freshDb();
76
+ const a = createTask("p", "A");
77
+ seedSession("s1", null);
78
+ updateTask(a.id, { sessionIds: ["s1"] });
79
+ deleteTask(a.id);
80
+ assert.equal(taskForSession("s1"), null);
81
+ assert.equal(listTasks("p").length, 0);
82
+ assert.equal(updateTask(a.id, { name: "x" }), null);
83
+ });
84
+
85
+ test("deleteTask cascades task_cards / links / questions (no orphan cards)", () => {
86
+ freshDb();
87
+ const t = createTask("p", "A");
88
+ const board = getOrCreateTaskBoard(t.id, "p", "A");
89
+ const c1 = createCard({ boardId: board.id, projectKey: "p", name: "卡1" });
90
+ const c2 = createCard({ boardId: board.id, projectKey: "p", name: "卡2" });
91
+ addLink(c1.id, c2.id, "prerequisite");
92
+ createQuestion(c1.id, "sess", "问题");
93
+
94
+ deleteTask(t.id);
95
+ assert.equal(getDb().prepare("SELECT COUNT(*) AS n FROM task_cards WHERE board_id = ?").get(t.id).n, 0);
96
+ assert.equal(getDb().prepare("SELECT COUNT(*) AS n FROM task_card_links").get().n, 0);
97
+ assert.equal(getDb().prepare("SELECT COUNT(*) AS n FROM task_card_questions").get().n, 0);
98
+ // 任务看板也随之删(deleteBoardCascade)
99
+ assert.equal(getBoard(t.id), undefined);
100
+ });
101
+
102
+ test("createTask validates input", () => {
103
+ freshDb();
104
+ assert.throws(() => createTask("", "x"), /projectKey is required/);
105
+ assert.throws(() => createTask("p", " "), /name must not be empty/);
106
+ });
107
+
108
+ test("updateTask returns null for unknown id and rejects empty rename", () => {
109
+ freshDb();
110
+ assert.equal(updateTask("nope", { name: "x" }), null);
111
+ const a = createTask("p", "A");
112
+ assert.throws(() => updateTask(a.id, { name: " " }), /name must not be empty/);
113
+ });
114
+
115
+ test("assignSessionToTask attaches a session atomically and bumps the task's updated", () => {
116
+ freshDb();
117
+ const a = createTask("p", "A");
118
+ seedSession("s1", null); // temp
119
+ seedSession("s2", a.id); // already a member
120
+
121
+ assert.equal(assignSessionToTask("s1", a.id), true);
122
+ assert.equal(taskForSession("s1"), a.id);
123
+ // 重复归属幂等:已是成员再归一次仍成功且不重复。
124
+ assert.equal(assignSessionToTask("s2", a.id), true);
125
+
126
+ const task = listTasks("p")[0];
127
+ assert.deepEqual([...task.sessionIds].sort(), ["s1", "s2"]);
128
+ assert.ok(task.updated >= task.created, "归属会刷新任务的 updated(排序用)");
129
+ });
130
+
131
+ test("assignSessionToTask returns false for a missing task without writing anything", () => {
132
+ freshDb();
133
+ seedSession("s1", null);
134
+ assert.equal(assignSessionToTask("s1", "no-such-task"), false);
135
+ assert.equal(taskForSession("s1"), null);
136
+ assert.equal(listTasks("p").length, 0);
137
+ });
138
+
139
+ test("listTasks honors sort_order within each pinned region", () => {
140
+ freshDb();
141
+ const a = createTask("p", "A");
142
+ const b = createTask("p", "B");
143
+ const c = createTask("p", "C");
144
+ // 新任务置顶:sort_order 取最小值 - 1,按创建序倒排(最新在最上)。
145
+ assert.deepEqual(listTasks("p").map((t) => t.name), ["C", "B", "A"]);
146
+
147
+ // 手动重排:C 置顶,A/B 非置顶区交换。
148
+ updateTask(c.id, { pinned: true });
149
+ reorderTasks("p", [b.id, a.id]); // 非置顶区顺序
150
+ assert.deepEqual(listTasks("p").map((t) => t.name), ["C", "B", "A"]);
151
+ });
152
+
153
+ test("reorderTasks validates ownership and preserves region isolation", () => {
154
+ freshDb();
155
+ const a = createTask("p", "A");
156
+ createTask("p", "B");
157
+ createTask("other", "X"); // 别项目
158
+ // 传入不属于该项目的 id → 抛错,事务回滚。
159
+ const other = listTasks("other")[0];
160
+ assert.throws(() => reorderTasks("p", [a.id, other.id]), /does not belong/);
161
+ // 回滚后顺序未变。
162
+ assert.deepEqual(listTasks("p").map((t) => t.name), ["B", "A"]);
163
+ });
164
+
165
+ test("updateTask supports sortOrder patch", () => {
166
+ freshDb();
167
+ const a = createTask("p", "A");
168
+ const b = createTask("p", "B");
169
+ // 新任务置顶(sort_order 递减),默认顺序:B(-1) 在 A(0) 前。
170
+ assert.deepEqual(listTasks("p").map((t) => t.name), ["B", "A"]);
171
+ // sortOrder 越大越靠后:A 设 99 后仍在 B 后面(B 为负数,排前面)。
172
+ updateTask(a.id, { sortOrder: 99 });
173
+ assert.deepEqual(listTasks("p").map((t) => t.name), ["B", "A"]);
174
+ // sortOrder 字段随任务返回。
175
+ const task = listTasks("p").find((t) => t.id === a.id);
176
+ assert.equal(task.sortOrder, 99);
177
+ assert.equal(b.sortOrder < 0, true);
178
+ });
179
+
180
+ test("assignSessionToTask moves a session out of its previous task", () => {
181
+ freshDb();
182
+ const a = createTask("p", "A");
183
+ const b = createTask("p", "B");
184
+ seedSession("s1", null);
185
+ assignSessionToTask("s1", a.id);
186
+ assignSessionToTask("s1", b.id); // 移动到 B
187
+ assert.equal(taskForSession("s1"), b.id);
188
+ const taskA = listTasks("p").find((t) => t.id === a.id);
189
+ assert.deepEqual(taskA.sessionIds, []);
190
+ });
191
+
192
+ test("deleteTask cascades to its task board (nodes/edges/view gone)", () => {
193
+ freshDb();
194
+ const a = createTask("p", "A");
195
+ const board = getOrCreateTaskBoard(a.id, "p", "A");
196
+ const n1 = addNode(board.id, { x: 0, y: 0, refId: "s1" });
197
+ assert.ok(n1);
198
+ deleteTask(a.id);
199
+ assert.equal(getBoard(board.id), undefined);
200
+ assert.equal(getBoardCanvas(board.id), null);
201
+ // 手动看板不受影响
202
+ const manual = createBoard("p", "manual");
203
+ assert.ok(getBoard(manual.id));
204
+ });
205
+
206
+ test("updateTask rename syncs task board name", () => {
207
+ freshDb();
208
+ const a = createTask("p", "旧名");
209
+ const board = getOrCreateTaskBoard(a.id, "p", "旧名");
210
+ assert.equal(board.name, "旧名");
211
+ updateTask(a.id, { name: "新名" });
212
+ assert.equal(getBoard(board.id)?.name, "新名");
213
+ // 看板未创建时改名无副作用
214
+ const b = createTask("p", "B");
215
+ updateTask(b.id, { name: "B2" });
216
+ });
@@ -0,0 +1,271 @@
1
+ import { randomUUID } from "crypto";
2
+ import { getDb } from "./sqlite-db";
3
+ import { deleteBoardCascade, renameTaskBoard } from "./board-store";
4
+
5
+ export interface Task {
6
+ id: string;
7
+ projectKey: string;
8
+ name: string;
9
+ created: number;
10
+ updated: number;
11
+ pinned: boolean;
12
+ sortOrder: number;
13
+ sessionIds: string[];
14
+ pinnedSessionIds: string[];
15
+ }
16
+
17
+ interface TaskRow {
18
+ id: string;
19
+ projectKey: string;
20
+ name: string;
21
+ created: number;
22
+ updated: number;
23
+ pinned: number;
24
+ sortOrder: number;
25
+ }
26
+
27
+ const now = () => Date.now();
28
+
29
+ /** 任务下根会话 id(不含 fork 子树,子树由树结构隐含归属)。 */
30
+ export function listTaskSessionIds(taskId: string): string[] {
31
+ // Order here is a fallback only: the sidebar re-sorts group contents by
32
+ // session modified-time (desc) + pinned segment, so this must stay in sync
33
+ // directionally (pinned first, then descending recency).
34
+ return getDb()
35
+ .prepare("SELECT session_id FROM session_meta WHERE task_id = ? ORDER BY pinned DESC, updated DESC, rowid DESC")
36
+ .all(taskId)
37
+ .map((r) => (r as { session_id: string }).session_id);
38
+ }
39
+
40
+ function getTaskRow(id: string): TaskRow | undefined {
41
+ return getDb()
42
+ .prepare(
43
+ "SELECT id, project_key AS projectKey, name, created, updated, pinned, sort_order AS sortOrder FROM tasks WHERE id = ?",
44
+ )
45
+ .get(id) as TaskRow | undefined;
46
+ }
47
+
48
+ function listPinnedTaskSessionIds(taskId: string): string[] {
49
+ return getDb()
50
+ .prepare("SELECT session_id FROM session_meta WHERE task_id = ? AND pinned = 1")
51
+ .all(taskId)
52
+ .map((r) => (r as { session_id: string }).session_id);
53
+ }
54
+
55
+ function rowToTask(row: TaskRow): Task {
56
+ return {
57
+ ...row,
58
+ pinned: row.pinned === 1,
59
+ sessionIds: listTaskSessionIds(row.id),
60
+ pinnedSessionIds: listPinnedTaskSessionIds(row.id),
61
+ };
62
+ }
63
+
64
+ /** A single task by id, or undefined. */
65
+ export function getTask(id: string): Task | undefined {
66
+ const row = getTaskRow(id);
67
+ return row ? rowToTask(row) : undefined;
68
+ }
69
+
70
+ /** All tasks of one project: pinned segment first, then manual order
71
+ * (sort_order), then creation time as a stable fallback. */
72
+ export function listTasks(projectKey: string): Task[] {
73
+ const rows = getDb()
74
+ .prepare(
75
+ "SELECT id, project_key AS projectKey, name, created, updated, pinned, sort_order AS sortOrder FROM tasks WHERE project_key = ? ORDER BY pinned DESC, sort_order, created, rowid",
76
+ )
77
+ .all(projectKey) as unknown as TaskRow[];
78
+ return rows.map(rowToTask);
79
+ }
80
+
81
+ export function createTask(projectKey: string, name: string): Task {
82
+ const trimmed = name.trim();
83
+ if (!projectKey) throw new Error("projectKey is required");
84
+ if (!trimmed) throw new Error("name must not be empty");
85
+ const id = randomUUID();
86
+ const ts = now();
87
+ // 新任务置顶:sort_order 取当前项目最小值 - 1(与看板 createBoard 一致),
88
+ // 保证新建任务出现在任务区最上方。
89
+ const minRow = getDb()
90
+ .prepare("SELECT MIN(sort_order) AS minOrder FROM tasks WHERE project_key = ?")
91
+ .get(projectKey) as { minOrder: number | null };
92
+ const sortOrder = minRow.minOrder === null ? 0 : minRow.minOrder - 1;
93
+ getDb()
94
+ .prepare("INSERT INTO tasks (id, project_key, name, created, updated, sort_order) VALUES (?, ?, ?, ?, ?, ?)")
95
+ .run(id, projectKey, trimmed, ts, ts, sortOrder);
96
+ return { id, projectKey: projectKey, name: trimmed, created: ts, updated: ts, pinned: false, sortOrder, sessionIds: [], pinnedSessionIds: [] };
97
+ }
98
+
99
+ /**
100
+ * Rename and/or replace task membership. `sessionIds` is a full-replace list:
101
+ * the diff only touches this task's members — added ids get task_id set (or
102
+ * moved from another task), removed ids fall back to temp (NULL), members of
103
+ * other tasks are never modified. Returns null when the task does not exist.
104
+ */
105
+ export function updateTask(
106
+ id: string,
107
+ patch: { name?: string; sessionIds?: string[]; pinned?: boolean; sortOrder?: number },
108
+ ): Task | null {
109
+ const db = getDb();
110
+ const existing = getTaskRow(id);
111
+ if (!existing) return null;
112
+
113
+ db.exec("BEGIN");
114
+ try {
115
+ if (patch.name !== undefined) {
116
+ const trimmed = patch.name.trim();
117
+ if (!trimmed) throw new Error("name must not be empty");
118
+ db.prepare("UPDATE tasks SET name = ?, updated = ? WHERE id = ?").run(trimmed, now(), id);
119
+ // 任务型看板名随任务同步(看板未创建则 0 行无害)
120
+ renameTaskBoard(id, trimmed);
121
+ }
122
+ if (patch.pinned !== undefined) {
123
+ db.prepare("UPDATE tasks SET pinned = ?, updated = ? WHERE id = ?").run(
124
+ patch.pinned ? 1 : 0,
125
+ now(),
126
+ id,
127
+ );
128
+ }
129
+ if (patch.sortOrder !== undefined) {
130
+ db.prepare("UPDATE tasks SET sort_order = ?, updated = ? WHERE id = ?").run(
131
+ patch.sortOrder,
132
+ now(),
133
+ id,
134
+ );
135
+ }
136
+ if (patch.sessionIds !== undefined) {
137
+ const current = new Set(listTaskSessionIds(id));
138
+ const next = new Set(patch.sessionIds);
139
+ const ts = now();
140
+ const assign = db.prepare(
141
+ "INSERT INTO session_meta (session_id, task_id, updated) VALUES (?, ?, ?) " +
142
+ "ON CONFLICT(session_id) DO UPDATE SET task_id = excluded.task_id, updated = excluded.updated",
143
+ );
144
+ const unassign = db.prepare(
145
+ "UPDATE session_meta SET task_id = NULL, updated = ? WHERE session_id = ?",
146
+ );
147
+ for (const sessionId of patch.sessionIds) {
148
+ if (!current.has(sessionId)) assign.run(sessionId, id, ts);
149
+ }
150
+ for (const sessionId of current) {
151
+ if (!next.has(sessionId)) unassign.run(ts, sessionId);
152
+ }
153
+ db.prepare("UPDATE tasks SET updated = ? WHERE id = ?").run(ts, id);
154
+ }
155
+ db.exec("COMMIT");
156
+ } catch (error) {
157
+ db.exec("ROLLBACK");
158
+ throw error;
159
+ }
160
+ return rowToTask(getTaskRow(id)!);
161
+ }
162
+
163
+ /**
164
+ * Bulk-reorder tasks within one project. `orderedIds` is the full ordered
165
+ * list of task ids for one pinned segment (caller splits pinned/unpinned);
166
+ * each id must belong to the project. sort_order values are written as the
167
+ * array position (0-based), preserving relative order.
168
+ */
169
+ export function reorderTasks(projectKey: string, orderedIds: string[]): Task[] {
170
+ const db = getDb();
171
+ if (orderedIds.length === 0) return listTasks(projectKey);
172
+ db.exec("BEGIN");
173
+ try {
174
+ const stmt = db.prepare("UPDATE tasks SET sort_order = ?, updated = ? WHERE id = ? AND project_key = ?");
175
+ orderedIds.forEach((id, index) => {
176
+ const res = stmt.run(index, now(), id, projectKey);
177
+ if (res.changes === 0) {
178
+ throw new Error(`task ${id} does not belong to project ${projectKey}`);
179
+ }
180
+ });
181
+ db.exec("COMMIT");
182
+ } catch (error) {
183
+ db.exec("ROLLBACK");
184
+ throw error;
185
+ }
186
+ return listTasks(projectKey);
187
+ }
188
+
189
+ /** Delete the task; all its sessions fall back to temp (task_id = NULL).
190
+ * 任务型看板(boards.task_id = 任务 id)连带级联删除。 */
191
+ export function deleteTask(id: string): void {
192
+ const db = getDb();
193
+ db.exec("BEGIN");
194
+ try {
195
+ db.prepare("UPDATE session_meta SET task_id = NULL, updated = ? WHERE task_id = ?").run(
196
+ now(),
197
+ id,
198
+ );
199
+ db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
200
+ // 任务卡级联:该任务看板(board_id = 任务 id)下的任务卡 + 依赖线 + 待答问题清掉,
201
+ // 防孤儿任务卡(任务卡节点/边已由 deleteBoardCascade 删,这里清业务表)。
202
+ db.prepare(
203
+ "DELETE FROM task_card_links WHERE card_id IN (SELECT id FROM task_cards WHERE board_id = ?) OR target_card_id IN (SELECT id FROM task_cards WHERE board_id = ?)",
204
+ ).run(id, id);
205
+ db.prepare("DELETE FROM task_card_questions WHERE card_id IN (SELECT id FROM task_cards WHERE board_id = ?)").run(id);
206
+ db.prepare("DELETE FROM task_cards WHERE board_id = ?").run(id);
207
+ // 任务即看板:删任务连带删其看板(含 nodes/edges/view)
208
+ deleteBoardCascade(id);
209
+ db.exec("COMMIT");
210
+ } catch (error) {
211
+ db.exec("ROLLBACK");
212
+ throw error;
213
+ }
214
+ }
215
+
216
+ /** Session's current task id, or null when it is a temp session. */
217
+ export function taskForSession(sessionId: string): string | null {
218
+ const row = getDb()
219
+ .prepare("SELECT task_id FROM session_meta WHERE session_id = ?")
220
+ .get(sessionId) as { task_id: string | null } | undefined;
221
+ return row?.task_id ?? null;
222
+ }
223
+
224
+ /** Session's current task name, or null when it is a temp session. */
225
+ export function taskNameForSession(sessionId: string): string | null {
226
+ const row = getDb()
227
+ .prepare(
228
+ "SELECT t.name FROM session_meta m JOIN tasks t ON t.id = m.task_id WHERE m.session_id = ?",
229
+ )
230
+ .get(sessionId) as { name: string } | undefined;
231
+ return row?.name ?? null;
232
+ }
233
+
234
+ /**
235
+ * 把一个会话原子归属到任务(任务不存在返回 false)。会刷新任务的 updated
236
+ * 使置顶/最近排序生效;会话原本在其他任务下则移动(ON CONFLICT 更新)。
237
+ * 会话创建完成时由服务端调用,避免前端两跳 PATCH 造成的“先临时区后任务”窗口。
238
+ */
239
+ export function assignSessionToTask(sessionId: string, taskId: string): boolean {
240
+ const db = getDb();
241
+ const task = getTaskRow(taskId);
242
+ if (!task) return false;
243
+ const ts = now();
244
+ db.exec("BEGIN");
245
+ try {
246
+ db.prepare(
247
+ "INSERT INTO session_meta (session_id, task_id, updated) VALUES (?, ?, ?) " +
248
+ "ON CONFLICT(session_id) DO UPDATE SET task_id = excluded.task_id, updated = excluded.updated",
249
+ ).run(sessionId, taskId, ts);
250
+ db.prepare("UPDATE tasks SET updated = ? WHERE id = ?").run(ts, taskId);
251
+ db.exec("COMMIT");
252
+ } catch (error) {
253
+ db.exec("ROLLBACK");
254
+ throw error;
255
+ }
256
+ return true;
257
+ }
258
+
259
+ /** Pin / unpin a session (region-relative: inside its task group, or in chat). */
260
+ export function setSessionPinned(sessionId: string, pinned: boolean): void {
261
+ const db = getDb();
262
+ db.prepare(
263
+ "INSERT INTO session_meta (session_id, task_id, updated, pinned) VALUES (?, NULL, ?, ?) " +
264
+ "ON CONFLICT(session_id) DO UPDATE SET pinned = excluded.pinned",
265
+ ).run(sessionId, now(), pinned ? 1 : 0);
266
+ }
267
+
268
+ /** Drop all task/meta bookkeeping for a session (used on session delete). */
269
+ export function unassignSession(sessionId: string): void {
270
+ getDb().prepare("DELETE FROM session_meta WHERE session_id = ?").run(sessionId);
271
+ }
@@ -0,0 +1,46 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ async function loadSubject() {
5
+ return import("./terminal-input.ts");
6
+ }
7
+
8
+ function key(keyValue, modifiers = {}) {
9
+ return {
10
+ key: keyValue,
11
+ altKey: false,
12
+ ctrlKey: false,
13
+ metaKey: false,
14
+ shiftKey: false,
15
+ ...modifiers,
16
+ };
17
+ }
18
+
19
+ test("maps navigation and editing keys to terminal sequences", async () => {
20
+ const { toTerminalKeyData } = await loadSubject();
21
+
22
+ assert.equal(toTerminalKeyData(key("ArrowDown")), "\x1b[B");
23
+ assert.equal(toTerminalKeyData(key("Home")), "\x1b[H");
24
+ assert.equal(toTerminalKeyData(key("Delete")), "\x1b[3~");
25
+ assert.equal(toTerminalKeyData(key("Tab", { shiftKey: true })), "\x1b[Z");
26
+ assert.equal(toTerminalKeyData(key("Enter", { shiftKey: true })), "\n");
27
+ });
28
+
29
+ test("maps legacy control and alt keys used by pi-tui", async () => {
30
+ const { toTerminalKeyData } = await loadSubject();
31
+
32
+ assert.equal(toTerminalKeyData(key("c", { ctrlKey: true })), "\x03");
33
+ assert.equal(toTerminalKeyData(key("]", { ctrlKey: true })), "\x1d");
34
+ assert.equal(toTerminalKeyData(key("ArrowLeft", { altKey: true })), "\x1bb");
35
+ assert.equal(toTerminalKeyData(key("x", { altKey: true })), "\x1bx");
36
+ assert.equal(toTerminalKeyData(key("v", { ctrlKey: true })), null);
37
+ assert.equal(toTerminalKeyData(key("x", { metaKey: true })), null);
38
+ });
39
+
40
+ test("leaves printable text to the text input and wraps pasted text", async () => {
41
+ const { asBracketedPaste, toTerminalKeyData } = await loadSubject();
42
+
43
+ assert.equal(toTerminalKeyData(key("a")), null);
44
+ assert.equal(toTerminalKeyData(key(" ")), null);
45
+ assert.equal(asBracketedPaste("first\nsecond"), "\x1b[200~first\nsecond\x1b[201~");
46
+ });
@@ -0,0 +1,63 @@
1
+ export interface TerminalKeyEventLike {
2
+ key: string;
3
+ altKey: boolean;
4
+ ctrlKey: boolean;
5
+ metaKey: boolean;
6
+ shiftKey: boolean;
7
+ }
8
+
9
+ const SPECIAL_KEY_SEQUENCES: Record<string, string> = {
10
+ ArrowUp: "\x1b[A",
11
+ ArrowDown: "\x1b[B",
12
+ ArrowRight: "\x1b[C",
13
+ ArrowLeft: "\x1b[D",
14
+ Home: "\x1b[H",
15
+ End: "\x1b[F",
16
+ Insert: "\x1b[2~",
17
+ Delete: "\x1b[3~",
18
+ PageUp: "\x1b[5~",
19
+ PageDown: "\x1b[6~",
20
+ Escape: "\x1b",
21
+ Backspace: "\x7f",
22
+ };
23
+
24
+ const ALT_ARROW_SEQUENCES: Record<string, string> = {
25
+ ArrowLeft: "\x1bb",
26
+ ArrowRight: "\x1bf",
27
+ ArrowUp: "\x1bp",
28
+ ArrowDown: "\x1bn",
29
+ };
30
+
31
+ function legacyCtrlSequence(key: string): string | null {
32
+ if (key.length !== 1) return null;
33
+ const code = key.toUpperCase().charCodeAt(0);
34
+ if (code >= 64 && code <= 95) return String.fromCharCode(code & 0x1f);
35
+ if (key === "?") return "\x7f";
36
+ return null;
37
+ }
38
+
39
+ export function toTerminalKeyData(event: TerminalKeyEventLike): string | null {
40
+ if (event.metaKey) return null;
41
+ if (event.ctrlKey && !event.altKey && event.key.toLowerCase() === "v") return null;
42
+
43
+ if (event.ctrlKey && !event.altKey) {
44
+ const control = legacyCtrlSequence(event.key);
45
+ if (control) return control;
46
+ }
47
+
48
+ if (event.altKey && !event.ctrlKey) {
49
+ if (event.key === "Backspace") return "\x1b\x7f";
50
+ const arrow = ALT_ARROW_SEQUENCES[event.key];
51
+ if (arrow) return arrow;
52
+ if (event.key.length === 1) return `\x1b${event.key}`;
53
+ }
54
+
55
+ if (event.key === "Enter") return event.shiftKey ? "\n" : "\r";
56
+ if (event.key === "Tab") return event.shiftKey ? "\x1b[Z" : "\t";
57
+
58
+ return SPECIAL_KEY_SEQUENCES[event.key] ?? null;
59
+ }
60
+
61
+ export function asBracketedPaste(text: string): string {
62
+ return `\x1b[200~${text}\x1b[201~`;
63
+ }
@@ -0,0 +1,71 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ createTerminal, writeTerminal, outputSince, fullBuffer,
5
+ currentOffset, removeTerminal, listTerminals, trimRingBuffer,
6
+ } from "./terminal-manager.ts";
7
+
8
+ const wait = (ms) => new Promise((r) => setTimeout(r, ms));
9
+
10
+ test("session echoes input; offsets let SSE clients resume", async () => {
11
+ const t = await createTerminal({ cwd: "/tmp", projectRoot: null, projectLabel: "t", cols: 80, rows: 24 });
12
+ try {
13
+ assert.ok(t.id);
14
+ assert.equal(t.running, true);
15
+ await wait(400); // let the shell print its prompt
16
+
17
+ const before = currentOffset(t.id);
18
+ writeTerminal(t.id, "echo hi-marker-$$\r");
19
+ await wait(900);
20
+
21
+ const replay = outputSince(t.id, before);
22
+ assert.ok(replay !== null && replay.includes("hi-marker-"), "resume slice contains new output");
23
+ assert.ok(fullBuffer(t.id)?.data.includes("hi-marker-"), "full buffer contains output");
24
+
25
+ // Offsets are cumulative: replaying from an older point still works while
26
+ // everything fits in the ring buffer.
27
+ assert.ok(outputSince(t.id, 0)?.includes("hi-marker-"));
28
+ } finally {
29
+ removeTerminal(t.id);
30
+ }
31
+ assert.equal(listTerminals().some((s) => s.id === t.id), false);
32
+ });
33
+
34
+ test("trimRingBuffer cuts at a line boundary", () => {
35
+ // Overflow lands inside a line → keep from the next line start.
36
+ const [kept, dropped] = trimRingBuffer("aaaa\nbbbb\ncccc\ndddd", 12);
37
+ assert.equal(dropped, 5, "drops up to (and including) the line break");
38
+ assert.equal(kept, "bbbb\ncccc\ndddd");
39
+
40
+ // Fits → untouched.
41
+ const [kept2, dropped2] = trimRingBuffer("short", 10);
42
+ assert.equal(kept2, "short");
43
+ assert.equal(dropped2, 0);
44
+
45
+ // Newline-free overflow → plain cut fallback (cannot align).
46
+ const [kept3, dropped3] = trimRingBuffer("xxxxx", 3);
47
+ assert.equal(kept3, "xxx");
48
+ assert.equal(dropped3, 2);
49
+ });
50
+
51
+ test("ring buffer rotation invalidates stale offsets", async () => {
52
+ const t = await createTerminal({
53
+ cwd: "/tmp", projectRoot: null, projectLabel: "t",
54
+ cols: 200, rows: 50,
55
+ });
56
+ try {
57
+ await wait(400);
58
+ // Push > 256KB through the shell so the buffer rotates.
59
+ writeTerminal(t.id, "head -c 400000 /dev/zero | tr '\\0' 'x'\r");
60
+ await wait(2500);
61
+ const fb = fullBuffer(t.id);
62
+ assert.ok(fb && fb.data.length <= 256 * 1024 + 4096, "buffer stays bounded");
63
+ assert.ok(fb.startOffset > 0 || fb.data.length < 400000, "rotation tracked");
64
+ // Offset 0 fell out of the window → null tells the client to full-replay.
65
+ if ((fb?.startOffset ?? 0) > 0) {
66
+ assert.equal(outputSince(t.id, 0), null);
67
+ }
68
+ } finally {
69
+ removeTerminal(t.id);
70
+ }
71
+ });