@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,235 @@
1
+ import {
2
+ createAgentSessionFromServices,
3
+ createAgentSessionServices,
4
+ getAgentDir,
5
+ initTheme,
6
+ SessionManager,
7
+ SettingsManager,
8
+ type SessionMessageEntry,
9
+ } from "@earendil-works/pi-coding-agent";
10
+ import { resolveSessionPath } from "./session-reader";
11
+
12
+ // ============================================================================
13
+ // 审核/阻塞判定(S3)
14
+ // 用 SessionManager.inMemory()(等价 --no-session)临时会话做一次结构化 AI 判定,
15
+ // 不落盘、不污染执行会话文件;执行会话上下文干净,重试不带审核噪音。
16
+ // ============================================================================
17
+
18
+ /** review 卡审核判定 */
19
+ export type AuditVerdict = "done" | "failed" | "waiting_reply" | "other";
20
+ /** running 卡阻塞判定 */
21
+ export type BlockKind =
22
+ | "sync_server"
23
+ | "infinite_loop"
24
+ | "rate_limit"
25
+ | "error"
26
+ | "asking"
27
+ | "normal";
28
+
29
+ export interface AuditInput {
30
+ cwd: string;
31
+ cardNumber: number;
32
+ cardName: string;
33
+ taskDescription: string;
34
+ /** 执行会话最后几条 AI 消息文本(供 AI 判定) */
35
+ recentMessages: string;
36
+ }
37
+
38
+ export interface AuditVerdictResult {
39
+ verdict: AuditVerdict;
40
+ reason: string;
41
+ }
42
+
43
+ export interface BlockCheckResult {
44
+ kind: BlockKind;
45
+ detail: string;
46
+ }
47
+
48
+ /** 从模型回复里抽取首个 JSON 对象块(容忍思考前缀/前后文)。 */
49
+ function extractJson(text: string): unknown | null {
50
+ const start = text.indexOf("{");
51
+ if (start === -1) return null;
52
+ let depth = 0;
53
+ for (let i = start; i < text.length; i++) {
54
+ if (text[i] === "{") depth += 1;
55
+ else if (text[i] === "}") {
56
+ depth -= 1;
57
+ if (depth === 0) {
58
+ try {
59
+ return JSON.parse(text.slice(start, i + 1));
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+
69
+ const AUDIT_TIMEOUT_MS = 90_000;
70
+
71
+ // ============================================================================
72
+ // 会话快照(程序检测 + 喂给 AI 的最近消息)
73
+ // ============================================================================
74
+
75
+ export interface SessionAuditSnapshot {
76
+ /** 程序判定:最后消息有失败迹象(模型报错/工具 isError/exit!=0) */
77
+ failure: boolean;
78
+ /** 最后几条 AI 消息文本(喂给 AI 判定) */
79
+ recentText: string;
80
+ /** 最后一条消息时间(ms epoch);0=未知(阻塞检测用) */
81
+ lastActivityMs: number;
82
+ }
83
+
84
+ function textOfContent(content: unknown): string {
85
+ if (typeof content === "string") return content;
86
+ if (Array.isArray(content)) {
87
+ return content
88
+ .filter((b): b is { type: "text"; text: string } => Boolean(b) && typeof b === "object" && "text" in b)
89
+ .map((b) => (b as { text: string }).text)
90
+ .join("\n");
91
+ }
92
+ return "";
93
+ }
94
+
95
+ /** 读执行会话文件:程序判定失败 + 最近消息文本 + 最后活动时间。文件缺失返回 null。 */
96
+ export async function readSessionAuditSnapshot(sessionId: string): Promise<SessionAuditSnapshot | null> {
97
+ try {
98
+ const filePath = await resolveSessionPath(sessionId);
99
+ if (!filePath) return null;
100
+ const manager = SessionManager.open(filePath);
101
+ const messages = manager.getEntries().filter((e): e is SessionMessageEntry => e.type === "message");
102
+ let failure = false;
103
+ let lastActivityMs = 0;
104
+ const recent: string[] = [];
105
+ for (const e of messages.slice(-4)) {
106
+ const m = e.message;
107
+ const ts = typeof m.timestamp === "number" ? m.timestamp : new Date(e.timestamp).getTime();
108
+ if (!Number.isNaN(ts)) lastActivityMs = Math.max(lastActivityMs, ts);
109
+ if (m.role === "assistant") {
110
+ if (m.errorMessage) failure = true;
111
+ const text = textOfContent(m.content);
112
+ if (text) recent.push(`AI: ${text.slice(0, 400)}`);
113
+ } else if (m.role === "toolResult") {
114
+ if (m.isError) failure = true;
115
+ const details = m.details as { exitCode?: number } | undefined;
116
+ if (details && typeof details.exitCode === "number" && details.exitCode !== 0) failure = true;
117
+ const text = textOfContent(m.content);
118
+ if (text) recent.push(`工具 ${m.toolName}: ${text.slice(0, 300)}`);
119
+ } else if (m.role === "user") {
120
+ const text = textOfContent(m.content);
121
+ if (text) recent.push(`用户: ${text.slice(0, 300)}`);
122
+ }
123
+ }
124
+ return { failure, recentText: recent.join("\n").slice(0, 4000), lastActivityMs };
125
+ } catch (error) {
126
+ console.error(
127
+ `[task-scheduler] 读会话快照失败 ${sessionId}:`,
128
+ error instanceof Error ? error.message : error,
129
+ );
130
+ return null;
131
+ }
132
+ }
133
+
134
+ /** 用 inMemory 临时会话跑一次结构化判定。超时/失败返回 null。 */
135
+ async function runStructuredVerdict<T>(
136
+ cwd: string,
137
+ instruction: string,
138
+ input: string,
139
+ ): Promise<T | null> {
140
+ let session: Awaited<ReturnType<typeof createAgentSessionFromServices>>["session"] | null = null;
141
+ const timer = setTimeout(() => {
142
+ // 超时兜底:dispose 会中断模型请求
143
+ if (session && typeof (session as { dispose?: () => void }).dispose === "function") {
144
+ try { (session as { dispose: () => void }).dispose(); } catch { /* 忽略 */ }
145
+ }
146
+ }, AUDIT_TIMEOUT_MS);
147
+
148
+ try {
149
+ initTheme();
150
+ const agentDir = getAgentDir();
151
+ const settingsManager = SettingsManager.create(cwd, agentDir);
152
+ const services = await createAgentSessionServices({
153
+ cwd,
154
+ agentDir,
155
+ settingsManager,
156
+ resourceLoaderOptions: { extensionFactories: [] },
157
+ });
158
+ const manager = SessionManager.inMemory(cwd);
159
+ const created = await createAgentSessionFromServices({
160
+ services,
161
+ sessionManager: manager,
162
+ noTools: "all",
163
+ });
164
+ session = created.session;
165
+ await session.prompt(`${instruction}\n\n${input}`, {
166
+ expandPromptTemplates: false,
167
+ source: "rpc",
168
+ });
169
+ await session.waitForIdle();
170
+ const text = session.getLastAssistantText() ?? "";
171
+ const parsed = extractJson(text);
172
+ return (parsed ?? null) as T | null;
173
+ } catch (error) {
174
+ console.error(
175
+ "[audit-session] 判定失败:",
176
+ error instanceof Error ? error.message : error,
177
+ );
178
+ return null;
179
+ } finally {
180
+ clearTimeout(timer);
181
+ if (session) {
182
+ try { session.dispose(); } catch { /* 忽略 */ }
183
+ }
184
+ }
185
+ }
186
+
187
+ const AUDIT_INSTRUCTION = `你是任务卡审核员。下面给出任务卡信息与执行会话最后几条消息,判断任务执行结果:
188
+ - done: 任务已正确完成
189
+ - failed: 执行失败 / 出错 / 未完成 / 模型报错
190
+ - waiting_reply: AI 正在向用户提问、等待用户输入
191
+ - other: 信息不足,无法判断
192
+
193
+ 只输出一行 JSON(不要输出其他文字):{"verdict":"done|failed|waiting_reply|other","reason":"简短原因"}`;
194
+
195
+ /** review 卡审核:返回 done/failed/waiting_reply/other;判定失败返回 null。 */
196
+ export async function runAuditVerdict(input: AuditInput): Promise<AuditVerdictResult | null> {
197
+ const taskText = [
198
+ `【任务卡 #${input.cardNumber}】${input.cardName}`,
199
+ `任务描述:${input.taskDescription || "(空)"}`,
200
+ ``,
201
+ `执行会话最后几条消息:`,
202
+ input.recentMessages || "(无消息)",
203
+ ].join("\n");
204
+ const result = await runStructuredVerdict<AuditVerdictResult>(input.cwd, AUDIT_INSTRUCTION, taskText);
205
+ if (result && ["done", "failed", "waiting_reply", "other"].includes(result.verdict)) {
206
+ return { verdict: result.verdict, reason: result.reason ?? "" };
207
+ }
208
+ return null;
209
+ }
210
+
211
+ const BLOCK_INSTRUCTION = `你是任务卡巡检员。任务卡执行会话已超过 5 分钟没有进展。根据任务与最后几条消息,判断阻塞类型:
212
+ - sync_server: 正在同步开启服务 / 输出流式日志(长任务,正常)
213
+ - infinite_loop: 死循环 / 重复执行同一操作
214
+ - rate_limit: 模型限流(429) / 节流退避
215
+ - error: 执行报错
216
+ - asking: AI 在提问等待用户输入
217
+ - normal: 正常运行中(长任务)
218
+
219
+ 只输出一行 JSON(不要输出其他文字):{"kind":"sync_server|infinite_loop|rate_limit|error|asking|normal","detail":"简短说明"}`;
220
+
221
+ /** running 卡阻塞判定:返回阻塞类型;判定失败返回 null。 */
222
+ export async function runBlockCheck(input: AuditInput): Promise<BlockCheckResult | null> {
223
+ const taskText = [
224
+ `【任务卡 #${input.cardNumber}】${input.cardName}`,
225
+ `任务描述:${input.taskDescription || "(空)"}`,
226
+ ``,
227
+ `执行会话最后几条消息:`,
228
+ input.recentMessages || "(无消息)",
229
+ ].join("\n");
230
+ const result = await runStructuredVerdict<BlockCheckResult>(input.cwd, BLOCK_INSTRUCTION, taskText);
231
+ if (result && ["sync_server", "infinite_loop", "rate_limit", "error", "asking", "normal"].includes(result.kind)) {
232
+ return { kind: result.kind, detail: result.detail ?? "" };
233
+ }
234
+ return null;
235
+ }
@@ -0,0 +1,63 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ import test from "node:test";
6
+
7
+ async function loadSubject() {
8
+ return import("./bash-output.ts");
9
+ }
10
+
11
+ test("accepts only pi bash logs directly inside the configured temp directory", async () => {
12
+ const { resolveBashOutputPath } = await loadSubject();
13
+ const tempRoot = join(tmpdir(), "pi-web-output-tests");
14
+ const expected = resolve(tempRoot, "pi-bash-ab12.log");
15
+
16
+ assert.equal(resolveBashOutputPath(join(tempRoot, "pi-bash-ab12.log"), tempRoot), expected);
17
+ assert.equal(resolveBashOutputPath(join(tempRoot, "..", "pi-bash-ab12.log"), tempRoot), null);
18
+ assert.equal(resolveBashOutputPath(join(tempRoot, "pi-bash-ab12.log.bak"), tempRoot), null);
19
+ assert.equal(resolveBashOutputPath(join(`${tempRoot}-other`, "pi-bash-ab12.log"), tempRoot), null);
20
+ });
21
+
22
+ test("reads small output and rejects oversized inline output before buffering it", async () => {
23
+ const { readUtf8FileWithinLimit } = await loadSubject();
24
+ const dir = await mkdtemp(join(tmpdir(), "pi-web-bash-output-"));
25
+ const filePath = join(dir, "pi-bash-ab12.log");
26
+ try {
27
+ await writeFile(filePath, "shell output", "utf8");
28
+
29
+ assert.deepEqual(await readUtf8FileWithinLimit(filePath, 32), {
30
+ tooLarge: false,
31
+ content: "shell output",
32
+ size: 12,
33
+ });
34
+ assert.deepEqual(await readUtf8FileWithinLimit(filePath, 4), {
35
+ tooLarge: true,
36
+ size: 12,
37
+ });
38
+ } finally {
39
+ await rm(dir, { recursive: true, force: true });
40
+ }
41
+ });
42
+
43
+ test("rejects symbolic links when opening bash output", async (t) => {
44
+ const { readUtf8FileWithinLimit } = await loadSubject();
45
+ const dir = await mkdtemp(join(tmpdir(), "pi-web-bash-output-link-"));
46
+ const targetPath = join(dir, "target.log");
47
+ const linkPath = join(dir, "pi-bash-link.log");
48
+ try {
49
+ await writeFile(targetPath, "not authorized through a link", "utf8");
50
+ try {
51
+ await symlink(targetPath, linkPath);
52
+ } catch (error) {
53
+ if (error?.code === "EPERM") {
54
+ t.skip("Creating symbolic links requires additional privileges on this platform");
55
+ return;
56
+ }
57
+ throw error;
58
+ }
59
+ await assert.rejects(() => readUtf8FileWithinLimit(linkPath));
60
+ } finally {
61
+ await rm(dir, { recursive: true, force: true });
62
+ }
63
+ });
@@ -0,0 +1,53 @@
1
+ import { constants } from "node:fs";
2
+ import { lstat, open } from "node:fs/promises";
3
+ import { basename, dirname, resolve } from "node:path";
4
+
5
+ export const MAX_INLINE_BASH_OUTPUT_BYTES = 5 * 1024 * 1024;
6
+
7
+ export function resolveBashOutputPath(filePath: string, tempRoot: string): string | null {
8
+ const resolvedPath = resolve(filePath);
9
+ if (dirname(resolvedPath) !== resolve(tempRoot)) return null;
10
+ if (!/^pi-bash-[A-Za-z0-9_-]+\.log$/.test(basename(resolvedPath))) return null;
11
+ return resolvedPath;
12
+ }
13
+
14
+ export async function openRegularFileNoFollow(filePath: string) {
15
+ const pathInfo = await lstat(filePath);
16
+ if (!pathInfo.isFile()) throw new Error("Bash output path is not a regular file");
17
+
18
+ const noFollow = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
19
+ const handle = await open(filePath, constants.O_RDONLY | noFollow);
20
+ try {
21
+ const fileInfo = await handle.stat();
22
+ if (!fileInfo.isFile()) throw new Error("Bash output path is not a regular file");
23
+ return { handle, fileInfo };
24
+ } catch (error) {
25
+ await handle.close();
26
+ throw error;
27
+ }
28
+ }
29
+
30
+ export async function readUtf8FileWithinLimit(
31
+ filePath: string,
32
+ maxBytes = MAX_INLINE_BASH_OUTPUT_BYTES,
33
+ ): Promise<{ tooLarge: true; size: number } | { tooLarge: false; content: string; size: number }> {
34
+ const { handle, fileInfo } = await openRegularFileNoFollow(filePath);
35
+ try {
36
+ if (fileInfo.size > maxBytes) return { tooLarge: true, size: fileInfo.size };
37
+
38
+ const buffer = Buffer.alloc(fileInfo.size);
39
+ let bytesRead = 0;
40
+ while (bytesRead < buffer.length) {
41
+ const result = await handle.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead);
42
+ if (result.bytesRead === 0) break;
43
+ bytesRead += result.bytesRead;
44
+ }
45
+ return {
46
+ tooLarge: false,
47
+ content: buffer.subarray(0, bytesRead).toString("utf8"),
48
+ size: bytesRead,
49
+ };
50
+ } finally {
51
+ await handle.close();
52
+ }
53
+ }
@@ -0,0 +1,129 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useState } from "react";
4
+
5
+ /**
6
+ * App wallpaper: original file Blob stored raw in IndexedDB (no re-encode,
7
+ * no compression), exposed to CSS as `--app-bg-image` on <html>.
8
+ * Images go through the CSS background layer on `body`; videos are rendered
9
+ * as an autoplaying muted <video> element by AppShell instead.
10
+ * Display settings live in lib/wallpaper-settings.ts.
11
+ */
12
+
13
+ const DB_NAME = "pi-web";
14
+ const STORE = "bg-image";
15
+ const KEY = "wallpaper";
16
+
17
+ export type BgKind = "image" | "video" | null;
18
+
19
+ function openDb(): Promise<IDBDatabase> {
20
+ return new Promise((resolve, reject) => {
21
+ const req = indexedDB.open(DB_NAME, 1);
22
+ req.onupgradeneeded = () => {
23
+ if (!req.result.objectStoreNames.contains(STORE)) {
24
+ req.result.createObjectStore(STORE);
25
+ }
26
+ };
27
+ req.onsuccess = () => resolve(req.result);
28
+ req.onerror = () => reject(req.error);
29
+ });
30
+ }
31
+
32
+ let currentUrl: string | null = null;
33
+ let bgKind: BgKind = null;
34
+
35
+ function kindOf(blob: Blob | null): BgKind {
36
+ if (!blob) return null;
37
+ if (blob.type.startsWith("video/")) return "video";
38
+ if (blob.type.startsWith("image/")) return "image";
39
+ return null;
40
+ }
41
+
42
+ /**
43
+ * Swap the stored media: revokes the previous object URL, points
44
+ * `--app-bg-image` at the new one (images only — video uses its own element)
45
+ * and returns the live object URL.
46
+ */
47
+ function setBackground(blob: Blob | null): { url: string | null; kind: BgKind } {
48
+ if (currentUrl) URL.revokeObjectURL(currentUrl);
49
+ bgKind = kindOf(blob);
50
+ currentUrl = blob ? URL.createObjectURL(blob) : null;
51
+ const el = document.documentElement.style;
52
+ if (bgKind === "image" && currentUrl) {
53
+ el.setProperty("--app-bg-image", `url("${currentUrl}")`);
54
+ } else {
55
+ el.setProperty("--app-bg-image", "none");
56
+ }
57
+ return { url: currentUrl, kind: bgKind };
58
+ }
59
+
60
+ async function loadBackground(): Promise<{
61
+ loaded: boolean;
62
+ url: string | null;
63
+ kind: BgKind;
64
+ }> {
65
+ try {
66
+ const db = await openDb();
67
+ const blob: Blob | null = await new Promise((resolve, reject) => {
68
+ const req = db.transaction(STORE, "readonly").objectStore(STORE).get(KEY);
69
+ req.onsuccess = () => resolve((req.result as Blob | undefined) ?? null);
70
+ req.onerror = () => reject(req.error);
71
+ });
72
+ if (!blob) return { loaded: false, ...setBackground(null) };
73
+ return { loaded: true, ...setBackground(blob) };
74
+ } catch {
75
+ // IndexedDB unavailable — background stays off.
76
+ return { loaded: false, ...setBackground(null) };
77
+ }
78
+ }
79
+
80
+ async function writeBackground(blob: Blob | null): Promise<void> {
81
+ const db = await openDb();
82
+ await new Promise<void>((resolve, reject) => {
83
+ const tx = db.transaction(STORE, "readwrite");
84
+ const store = tx.objectStore(STORE);
85
+ if (blob) store.put(blob, KEY);
86
+ else store.delete(KEY);
87
+ tx.oncomplete = () => resolve();
88
+ tx.onerror = () => reject(tx.error);
89
+ });
90
+ }
91
+
92
+ export function useAppBackground() {
93
+ const [hasBg, setHasBg] = useState(false);
94
+ const [ready, setReady] = useState(false);
95
+ const [kind, setKind] = useState<BgKind>(null);
96
+ const [url, setUrl] = useState<string | null>(null);
97
+
98
+ useEffect(() => {
99
+ let alive = true;
100
+ loadBackground().then((res) => {
101
+ if (!alive) return;
102
+ setHasBg(res.loaded);
103
+ setUrl(res.url);
104
+ setKind(res.kind);
105
+ setReady(true);
106
+ });
107
+ return () => {
108
+ alive = false;
109
+ };
110
+ }, []);
111
+
112
+ const pick = useCallback(async (file: Blob) => {
113
+ await writeBackground(file);
114
+ const res = setBackground(file);
115
+ setUrl(res.url);
116
+ setKind(res.kind);
117
+ setHasBg(true);
118
+ }, []);
119
+
120
+ const remove = useCallback(async () => {
121
+ await writeBackground(null);
122
+ setBackground(null);
123
+ setUrl(null);
124
+ setKind(null);
125
+ setHasBg(false);
126
+ }, []);
127
+
128
+ return { hasBg, ready, pick, remove, kind, url };
129
+ }
@@ -0,0 +1,83 @@
1
+ /** 看板工作台 ↔ AppShell 全局事件桥。
2
+ *
3
+ * 工作台(SessionWorkbench)内嵌完整 ChatWindow,但卡片在 tldraw 画布内,
4
+ * 无法直接拿到 AppShell 的 handler/props。两类通道:
5
+ * 1. 会话内部状态(统计/分支/系统提示)→ ChatWindow 回调直接捕获,卡片内渲染;
6
+ * 2. 需要 AppShell 参与的动作(打开文件面板/刷新侧栏/浏览器通知)→ 事件桥,
7
+ * 事件一律携带 sessionId,AppShell 处理时只做对应会话的动作,不退出看板。 */
8
+ /** 工作台内点击消息里的 file: 链接 → AppShell 打开右侧文件面板 */
9
+ export const BOARD_OPEN_FILE_EVENT = "pi-web:board-open-file";
10
+
11
+ /** 工作台内 fork 出新会话 → AppShell 刷新侧栏(不切换当前会话/退出看板) */
12
+ export const BOARD_SESSION_FORKED_EVENT = "pi-web:board-session-forked";
13
+
14
+ /** 看板内新建会话(新会话卡转正)→ AppShell 刷新侧栏,新会话出现在左侧树 */
15
+ export const BOARD_SESSION_CREATED_EVENT = "pi-web:board-session-created";
16
+
17
+ /** 看板内删除会话(画布删卡)→ AppShell 刷新侧栏(左侧树移除该会话) */
18
+ export const BOARD_SESSION_DELETED_EVENT = "pi-web:board-session-deleted";
19
+
20
+ /** 看板内会话改名 → AppShell 刷新侧栏(左侧树名称同步) */
21
+ export const BOARD_SESSION_RENAMED_EVENT = "pi-web:board-session-renamed";
22
+
23
+ /** 工作台内会话运行结束 → AppShell 刷新侧栏 + 浏览器通知 */
24
+ export const BOARD_AGENT_END_EVENT = "pi-web:board-agent-end";
25
+
26
+ /** 工作台内扩展发起阻塞请求 → AppShell 浏览器通知 */
27
+ export const BOARD_ATTENTION_NEEDED_EVENT = "pi-web:board-attention-needed";
28
+
29
+ /** 看板画布结构变化(清空/清理失效)→ BoardSection 刷新节点计数 */
30
+ export const BOARD_CANVAS_CHANGED_EVENT = "pi-web:board-canvas-changed";
31
+
32
+ /** 任务卡 API 操作 bump 了 boards.updated → useBoardCanvas 刷新乐观锁基线 */
33
+ export const BOARD_BASE_UPDATED_EVENT = "pi-web:board-base-updated";
34
+
35
+ declare global {
36
+ interface WindowEventMap {
37
+ "pi-web:board-open-file": CustomEvent<{ sessionId: string; filePath: string }>;
38
+ "pi-web:board-session-forked": CustomEvent<{ sessionId: string; newSessionId: string }>;
39
+ "pi-web:board-session-created": CustomEvent<{ sessionId: string; nodeId?: string }>;
40
+ "pi-web:board-session-deleted": CustomEvent<{ sessionId: string }>;
41
+ "pi-web:board-session-renamed": CustomEvent<{ sessionId: string; name?: string }>;
42
+ "pi-web:board-agent-end": CustomEvent<{ sessionId: string; sessionName?: string }>;
43
+ "pi-web:board-attention-needed": CustomEvent<{ sessionId: string; title?: string; method: string }>;
44
+ "pi-web:board-canvas-changed": CustomEvent<{ boardId: string }>;
45
+ "pi-web:board-base-updated": CustomEvent<{ boardId: string; updated: number }>;
46
+ }
47
+ }
48
+
49
+ export function dispatchBoardOpenFile(sessionId: string, filePath: string): void {
50
+ window.dispatchEvent(new CustomEvent(BOARD_OPEN_FILE_EVENT, { detail: { sessionId, filePath } }));
51
+ }
52
+
53
+ export function dispatchBoardSessionForked(sessionId: string, newSessionId: string): void {
54
+ window.dispatchEvent(new CustomEvent(BOARD_SESSION_FORKED_EVENT, { detail: { sessionId, newSessionId } }));
55
+ }
56
+
57
+ export function dispatchBoardSessionCreated(sessionId: string, nodeId?: string): void {
58
+ window.dispatchEvent(new CustomEvent(BOARD_SESSION_CREATED_EVENT, { detail: { sessionId, nodeId } }));
59
+ }
60
+
61
+ export function dispatchBoardSessionDeleted(sessionId: string): void {
62
+ window.dispatchEvent(new CustomEvent(BOARD_SESSION_DELETED_EVENT, { detail: { sessionId } }));
63
+ }
64
+
65
+ export function dispatchBoardSessionRenamed(sessionId: string, name?: string): void {
66
+ window.dispatchEvent(new CustomEvent(BOARD_SESSION_RENAMED_EVENT, { detail: { sessionId, name } }));
67
+ }
68
+
69
+ export function dispatchBoardAgentEnd(sessionId: string, sessionName?: string): void {
70
+ window.dispatchEvent(new CustomEvent(BOARD_AGENT_END_EVENT, { detail: { sessionId, sessionName } }));
71
+ }
72
+
73
+ export function dispatchBoardAttentionNeeded(sessionId: string, request: { title?: string; method: string }): void {
74
+ window.dispatchEvent(new CustomEvent(BOARD_ATTENTION_NEEDED_EVENT, { detail: { sessionId, ...request } }));
75
+ }
76
+
77
+ export function dispatchBoardCanvasChanged(boardId: string): void {
78
+ window.dispatchEvent(new CustomEvent(BOARD_CANVAS_CHANGED_EVENT, { detail: { boardId } }));
79
+ }
80
+
81
+ export function dispatchBoardBaseUpdated(boardId: string, updated: number): void {
82
+ window.dispatchEvent(new CustomEvent(BOARD_BASE_UPDATED_EVENT, { detail: { boardId, updated } }));
83
+ }
@@ -0,0 +1,66 @@
1
+ import { test, beforeEach, afterEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { randomUUID } from "node:crypto";
5
+ import { createJiti } from "jiti";
6
+
7
+ const jiti = createJiti(import.meta.url, { interopDefault: true, moduleCache: false });
8
+ const { setDbForTesting } = await jiti.import("./sqlite-db.ts");
9
+ const { createBoard, addNode, getBoardCanvas } = await jiti.import("./board-store.ts");
10
+ const { purgeOrphanBoardCards } = await jiti.import("./board-purge.ts");
11
+
12
+ const PROJECT = "test-project";
13
+ let db;
14
+
15
+ beforeEach(() => {
16
+ db = new DatabaseSync(":memory:");
17
+ setDbForTesting(db);
18
+ });
19
+
20
+ afterEach(() => {
21
+ db.close();
22
+ });
23
+
24
+ test("purgeOrphanBoardCards: 删孤儿卡片(会话不存在)+ 保留 draft 卡 + 幂等", async () => {
25
+ const b = createBoard(PROJECT, "b");
26
+ // 孤儿卡:refId 指向不存在的会话(随机 UUID,文件系统必无)
27
+ const orphanId = randomUUID();
28
+ const orphan = addNode(b.id, { x: 0, y: 0, refId: orphanId });
29
+ assert.ok(orphan);
30
+ // draft 卡:refId 为空,不删
31
+ const draft = addNode(b.id, { x: 100, y: 0, refId: null });
32
+ assert.ok(draft);
33
+
34
+ const r = await purgeOrphanBoardCards();
35
+ assert.equal(r.deletedNodes, 1);
36
+ assert.deepEqual(r.boards, [b.id]);
37
+
38
+ const canvas = getBoardCanvas(b.id);
39
+ assert.equal(canvas?.nodes.length, 1);
40
+ assert.equal(canvas?.nodes[0].id, draft.id); // draft 保留
41
+
42
+ // 幂等:再次清理全 0
43
+ const r2 = await purgeOrphanBoardCards();
44
+ assert.equal(r2.deletedNodes, 0);
45
+ assert.equal(r2.deletedEdges, 0);
46
+ assert.deepEqual(r2.boards, []);
47
+ });
48
+
49
+ test("purgeOrphanBoardCards: 级联删连线", async () => {
50
+ const b = createBoard(PROJECT, "b");
51
+ const orphanId = randomUUID();
52
+ const orphan = addNode(b.id, { x: 0, y: 0, refId: orphanId });
53
+ const keep = addNode(b.id, { x: 100, y: 0, refId: null }); // draft 卡(不删)
54
+ const { addEdge } = await jiti.import("./board-store.ts");
55
+ assert.ok(orphan && keep);
56
+ // 孤儿卡 ←→ draft 卡的连线
57
+ assert.ok(addEdge(b.id, { fromId: orphan.id, toId: keep.id, label: "x" }));
58
+
59
+ const r = await purgeOrphanBoardCards();
60
+ assert.equal(r.deletedNodes, 1);
61
+ assert.equal(r.deletedEdges, 1); // 指向孤儿卡的边被级联删
62
+ const canvas = getBoardCanvas(b.id);
63
+ assert.equal(canvas?.nodes.length, 1);
64
+ assert.equal(canvas?.nodes[0].id, keep.id); // draft 保留
65
+ assert.equal(canvas?.edges.length, 0); // 连线已清
66
+ });