@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,205 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { homedir } from "os";
3
+ import { join, normalize } from "path";
4
+
5
+ /** Same semantics as the SDK's getAgentDir — keeps this module SDK-free so
6
+ * unit tests can load it without pulling in the whole coding-agent dist. */
7
+ const ENV_AGENT_DIR = "PI_CODING_AGENT_DIR";
8
+
9
+ export function getAgentDir(): string {
10
+ const envDir = process.env[ENV_AGENT_DIR];
11
+ if (envDir) {
12
+ const expanded = envDir.startsWith("~") ? join(homedir(), envDir.slice(1)) : envDir;
13
+ return normalize(expanded);
14
+ }
15
+ return join(homedir(), ".pi", "agent");
16
+ }
17
+
18
+ declare global {
19
+ var __piWebDb: DatabaseSync | undefined;
20
+ }
21
+
22
+ export const DB_FILE_NAME = "pi-web.db";
23
+
24
+ /** Absolute path of the single pi-web database (lives next to agent dir). */
25
+ export function dbPath(): string {
26
+ return join(getAgentDir(), DB_FILE_NAME);
27
+ }
28
+
29
+ /** Inject a database for tests (e.g. `:memory:`). Overrides the singleton and
30
+ * ensures the schema is present so callers can use it immediately. */
31
+ export function setDbForTesting(db: DatabaseSync): void {
32
+ initSchema(db);
33
+ globalThis.__piWebDb = db;
34
+ }
35
+
36
+ /**
37
+ * Open (once) and return the pi-web database, creating tables on first use.
38
+ * Stored on globalThis so it survives Next.js hot-reload like the other
39
+ * singletons in this codebase.
40
+ */
41
+ export function getDb(): DatabaseSync {
42
+ if (globalThis.__piWebDb) return globalThis.__piWebDb;
43
+ const db = new DatabaseSync(dbPath());
44
+ db.exec("PRAGMA journal_mode = WAL;");
45
+ db.exec("PRAGMA foreign_keys = ON;");
46
+ initSchema(db);
47
+ globalThis.__piWebDb = db;
48
+ return db;
49
+ }
50
+
51
+ /** Idempotent DDL — run on every open; `IF NOT EXISTS` keeps it cheap. */
52
+ export function initSchema(db: DatabaseSync): void {
53
+ db.exec(`
54
+ CREATE TABLE IF NOT EXISTS tasks (
55
+ id TEXT PRIMARY KEY,
56
+ project_key TEXT NOT NULL,
57
+ name TEXT NOT NULL,
58
+ created INTEGER NOT NULL,
59
+ updated INTEGER NOT NULL
60
+ );
61
+ CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_key);
62
+
63
+ CREATE TABLE IF NOT EXISTS session_meta (
64
+ session_id TEXT PRIMARY KEY,
65
+ task_id TEXT,
66
+ updated INTEGER NOT NULL
67
+ );
68
+ CREATE INDEX IF NOT EXISTS idx_meta_task ON session_meta(task_id);
69
+
70
+ CREATE TABLE IF NOT EXISTS search_state (
71
+ session_id TEXT PRIMARY KEY,
72
+ mtime TEXT NOT NULL,
73
+ title TEXT NOT NULL
74
+ );
75
+
76
+ CREATE VIRTUAL TABLE IF NOT EXISTS session_search USING fts5(
77
+ session_id UNINDEXED,
78
+ title,
79
+ body,
80
+ tokenize = 'trigram'
81
+ );
82
+ `);
83
+ migrate(db);
84
+ }
85
+
86
+ /**
87
+ * 版本化迁移:用 PRAGMA user_version 记录当前 schema 版本。
88
+ * 老库打开时自动按序补齐缺失的迁移(每个迁移一个事务,成功后推进版本号),
89
+ * 新库建表后从 v0 一路迁到 SCHEMA_VERSION。重复打开不再执行已完成的迁移。
90
+ */
91
+ export const SCHEMA_VERSION = 8;
92
+
93
+ interface Migration {
94
+ version: number;
95
+ name: string;
96
+ /** 单条语句串;每条独立执行,包在同一个事务里。 */
97
+ statements: string[];
98
+ }
99
+
100
+ /** 迁移历史(按版本升序)。只允许追加,不允许修改/删除历史项。 */
101
+ const MIGRATIONS: Migration[] = [
102
+ {
103
+ version: 1,
104
+ name: "pinned columns",
105
+ statements: [
106
+ "ALTER TABLE tasks ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;",
107
+ "ALTER TABLE session_meta ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;",
108
+ ],
109
+ },
110
+ {
111
+ version: 2,
112
+ name: "tasks.sort_order",
113
+ statements: [
114
+ "ALTER TABLE tasks ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0;",
115
+ ],
116
+ },
117
+ {
118
+ version: 3,
119
+ name: "session boards",
120
+ statements: [
121
+ "CREATE TABLE IF NOT EXISTS boards (\n id TEXT PRIMARY KEY,\n project_key TEXT NOT NULL,\n name TEXT NOT NULL,\n is_system INTEGER NOT NULL DEFAULT 0,\n created INTEGER NOT NULL,\n updated INTEGER NOT NULL\n);",
122
+ "CREATE INDEX IF NOT EXISTS idx_boards_project ON boards(project_key);",
123
+ "CREATE TABLE IF NOT EXISTS board_nodes (\n id TEXT PRIMARY KEY,\n board_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n ref_id TEXT,\n x REAL NOT NULL,\n y REAL NOT NULL,\n w REAL NOT NULL DEFAULT 0,\n h REAL NOT NULL DEFAULT 0,\n expanded INTEGER NOT NULL DEFAULT 0,\n props TEXT NOT NULL DEFAULT '{}',\n created INTEGER NOT NULL,\n updated INTEGER NOT NULL\n);",
124
+ "CREATE INDEX IF NOT EXISTS idx_board_nodes_board ON board_nodes(board_id);",
125
+ "CREATE TABLE IF NOT EXISTS board_edges (\n id TEXT PRIMARY KEY,\n board_id TEXT NOT NULL,\n from_id TEXT NOT NULL,\n to_id TEXT NOT NULL,\n label TEXT,\n color TEXT,\n dashed INTEGER NOT NULL DEFAULT 0,\n created INTEGER NOT NULL,\n updated INTEGER NOT NULL\n);",
126
+ "CREATE INDEX IF NOT EXISTS idx_board_edges_board ON board_edges(board_id);",
127
+ "CREATE TABLE IF NOT EXISTS board_view (\n board_id TEXT PRIMARY KEY,\n camera_x REAL NOT NULL DEFAULT 0,\n camera_y REAL NOT NULL DEFAULT 0,\n camera_z REAL NOT NULL DEFAULT 1,\n updated INTEGER NOT NULL\n);",
128
+ ],
129
+ },
130
+ {
131
+ version: 4,
132
+ name: "boards.sort_order",
133
+ statements: [
134
+ "ALTER TABLE boards ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0;",
135
+ ],
136
+ },
137
+ {
138
+ version: 5,
139
+ name: "boards.task_id",
140
+ statements: [
141
+ "ALTER TABLE boards ADD COLUMN task_id TEXT NULL;",
142
+ "CREATE INDEX IF NOT EXISTS idx_boards_task ON boards(task_id);",
143
+ ],
144
+ },
145
+ {
146
+ version: 6,
147
+ name: "boards.task_id unique (防并发重复创建任务看板)",
148
+ statements: [
149
+ // 任务看板 id = 任务 id,task_id 必须唯一。SQLite UNIQUE 索引允许多个 NULL,
150
+ // 手动看板(task_id=NULL)不受影响。并发懒创建时由数据库层拒绝重复插入。
151
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_boards_task_unique ON boards(task_id);",
152
+ ],
153
+ },
154
+ {
155
+ version: 7,
156
+ name: "task cards",
157
+ statements: [
158
+ "CREATE TABLE IF NOT EXISTS task_cards (\n id TEXT PRIMARY KEY,\n board_id TEXT NOT NULL,\n project_key TEXT NOT NULL,\n number INTEGER NOT NULL,\n name TEXT NOT NULL,\n description TEXT NOT NULL DEFAULT '',\n ready_status TEXT NOT NULL DEFAULT 'draft',\n exec_status TEXT NOT NULL DEFAULT 'not_started',\n priority INTEGER NOT NULL DEFAULT 0,\n due INTEGER,\n attachments TEXT NOT NULL DEFAULT '[]',\n cwd TEXT,\n use_worktree INTEGER NOT NULL DEFAULT 0,\n max_retries INTEGER NOT NULL DEFAULT 3,\n retry_count INTEGER NOT NULL DEFAULT 0,\n session_id TEXT,\n created INTEGER NOT NULL,\n updated INTEGER NOT NULL\n);",
159
+ "CREATE INDEX IF NOT EXISTS idx_task_cards_board ON task_cards(board_id);",
160
+ "CREATE INDEX IF NOT EXISTS idx_task_cards_project ON task_cards(project_key);",
161
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_task_cards_number ON task_cards(project_key, number);",
162
+ "CREATE INDEX IF NOT EXISTS idx_task_cards_status ON task_cards(ready_status, exec_status);",
163
+ "CREATE TABLE IF NOT EXISTS task_card_links (\n id TEXT PRIMARY KEY,\n card_id TEXT NOT NULL,\n target_card_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n created INTEGER NOT NULL,\n UNIQUE(card_id, target_card_id, kind)\n);",
164
+ "CREATE INDEX IF NOT EXISTS idx_task_links_card ON task_card_links(card_id);",
165
+ "CREATE TABLE IF NOT EXISTS task_card_questions (\n id TEXT PRIMARY KEY,\n card_id TEXT NOT NULL,\n session_id TEXT NOT NULL,\n question TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n answer TEXT,\n created INTEGER NOT NULL,\n answered INTEGER\n);",
166
+ "CREATE INDEX IF NOT EXISTS idx_task_questions_status ON task_card_questions(status);",
167
+ ],
168
+ },
169
+ {
170
+ version: 8,
171
+ name: "task_cards.dispatch_token (跨进程派发互斥锁)",
172
+ statements: [
173
+ // 多实例(dev + 全局 CLI + 残留服务)共库时,同一张卡可能被多个调度器
174
+ // 同时捞到并发派发 → 双会话。派发前条件 UPDATE 原子抢 token,谁抢到谁执行。
175
+ "ALTER TABLE task_cards ADD COLUMN dispatch_token TEXT NULL;",
176
+ ],
177
+ },
178
+ ];
179
+
180
+ function migrate(db: DatabaseSync): void {
181
+ const row = db.prepare("PRAGMA user_version").get() as { user_version: number };
182
+ let version = row.user_version;
183
+ for (const m of MIGRATIONS) {
184
+ if (m.version <= version) continue;
185
+ db.exec("BEGIN");
186
+ try {
187
+ for (const stmt of m.statements) {
188
+ try {
189
+ db.exec(stmt);
190
+ } catch (error) {
191
+ // 老库在无版本管理时代可能已手动加过列(幂等 ADD COLUMN 路径),
192
+ // 列已存在时跳过该语句,不阻断整批迁移。
193
+ const msg = error instanceof Error ? error.message : String(error);
194
+ if (!/duplicate column name/i.test(msg)) throw error;
195
+ }
196
+ }
197
+ db.exec(`PRAGMA user_version = ${m.version}`);
198
+ db.exec("COMMIT");
199
+ version = m.version;
200
+ } catch (error) {
201
+ db.exec("ROLLBACK");
202
+ throw error;
203
+ }
204
+ }
205
+ }
@@ -0,0 +1,130 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, mkdir, readFile, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+ import { SettingsManager } from "@earendil-works/pi-coding-agent";
7
+ import { createJiti } from "jiti";
8
+
9
+ const { persistExplicitStartupPreferences } = await createJiti(import.meta.url)
10
+ .import("./startup-preferences.ts");
11
+
12
+ async function withSettings(run) {
13
+ const root = await mkdtemp(join(tmpdir(), "pi-web-startup-preferences-"));
14
+ const cwd = join(root, "cwd");
15
+ const agentDir = join(root, "agent");
16
+ await mkdir(cwd);
17
+ await mkdir(agentDir);
18
+
19
+ try {
20
+ const settings = SettingsManager.create(cwd, agentDir);
21
+ await run({ settings, settingsPath: join(agentDir, "settings.json") });
22
+ } finally {
23
+ await rm(root, { recursive: true, force: true });
24
+ }
25
+ }
26
+
27
+ test("persists explicit effective model and thinking defaults", async () => {
28
+ await withSettings(async ({ settings, settingsPath }) => {
29
+ const result = await persistExplicitStartupPreferences(
30
+ settings,
31
+ {
32
+ model: { provider: "deepseek", modelId: "deepseek-chat" },
33
+ thinkingLevel: "xhigh",
34
+ },
35
+ {
36
+ model: { provider: "deepseek", modelId: "deepseek-chat" },
37
+ thinkingLevel: "high",
38
+ supportsThinking: true,
39
+ },
40
+ );
41
+
42
+ const saved = JSON.parse(await readFile(settingsPath, "utf8"));
43
+ assert.deepEqual(
44
+ {
45
+ defaultProvider: saved.defaultProvider,
46
+ defaultModel: saved.defaultModel,
47
+ defaultThinkingLevel: saved.defaultThinkingLevel,
48
+ },
49
+ {
50
+ defaultProvider: "deepseek",
51
+ defaultModel: "deepseek-chat",
52
+ defaultThinkingLevel: "high",
53
+ },
54
+ );
55
+ assert.equal(result.modelDefaultChanged, true);
56
+ });
57
+ });
58
+
59
+ test("does not persist implicit scope selections", async () => {
60
+ await withSettings(async ({ settings }) => {
61
+ settings.setDefaultModelAndProvider("saved", "saved-model");
62
+ settings.setDefaultThinkingLevel("medium");
63
+ await settings.flush();
64
+
65
+ const result = await persistExplicitStartupPreferences(
66
+ settings,
67
+ {},
68
+ {
69
+ model: { provider: "scoped", modelId: "scoped-model" },
70
+ thinkingLevel: "high",
71
+ supportsThinking: true,
72
+ },
73
+ );
74
+
75
+ assert.equal(settings.getDefaultProvider(), "saved");
76
+ assert.equal(settings.getDefaultModel(), "saved-model");
77
+ assert.equal(settings.getDefaultThinkingLevel(), "medium");
78
+ assert.equal(result.modelDefaultChanged, false);
79
+ });
80
+ });
81
+
82
+ test("does not persist a model when startup resolved a different model", async () => {
83
+ await withSettings(async ({ settings }) => {
84
+ const result = await persistExplicitStartupPreferences(
85
+ settings,
86
+ { model: { provider: "requested", modelId: "requested-model" } },
87
+ {
88
+ model: { provider: "fallback", modelId: "fallback-model" },
89
+ thinkingLevel: "off",
90
+ supportsThinking: false,
91
+ },
92
+ );
93
+
94
+ assert.equal(settings.getDefaultProvider(), undefined);
95
+ assert.equal(settings.getDefaultModel(), undefined);
96
+ assert.equal(result.modelDefaultChanged, false);
97
+ });
98
+ });
99
+
100
+ test("deletes the thinking default when the effective level is invalid (auto)", async () => {
101
+ await withSettings(async ({ settings, settingsPath }) => {
102
+ settings.setDefaultThinkingLevel("high");
103
+ await settings.flush();
104
+
105
+ await persistExplicitStartupPreferences(
106
+ settings,
107
+ { thinkingLevel: "auto" },
108
+ { thinkingLevel: "auto", supportsThinking: true },
109
+ );
110
+
111
+ const saved = JSON.parse(await readFile(settingsPath, "utf8"));
112
+ assert.equal("defaultThinkingLevel" in saved, false);
113
+ assert.equal(settings.getDefaultThinkingLevel(), undefined);
114
+ });
115
+ });
116
+
117
+ test("does not replace a reasoning default with off for a non-thinking model", async () => {
118
+ await withSettings(async ({ settings }) => {
119
+ settings.setDefaultThinkingLevel("high");
120
+ await settings.flush();
121
+
122
+ await persistExplicitStartupPreferences(
123
+ settings,
124
+ { thinkingLevel: "off" },
125
+ { thinkingLevel: "off", supportsThinking: false },
126
+ );
127
+
128
+ assert.equal(settings.getDefaultThinkingLevel(), "high");
129
+ });
130
+ });
@@ -0,0 +1,65 @@
1
+ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
2
+ import type { SettingsManager } from "@earendil-works/pi-coding-agent";
3
+
4
+ /** pi 的合法思考等级;UI 的 "auto" 只是占位,不在此列(写库会被 SDK 兜底成 off)。 */
5
+ const VALID_THINKING_LEVELS = new Set<ThinkingLevel>([
6
+ "off", "minimal", "low", "medium", "high", "xhigh", "max",
7
+ ]);
8
+
9
+ function isThinkingLevel(value: string): value is ThinkingLevel {
10
+ return VALID_THINKING_LEVELS.has(value as ThinkingLevel);
11
+ }
12
+
13
+ export interface ExplicitStartupPreferences {
14
+ model?: { provider: string; modelId: string };
15
+ thinkingLevel?: ThinkingLevel;
16
+ }
17
+
18
+ export interface EffectiveStartupPreferences {
19
+ model?: { provider: string; modelId: string };
20
+ thinkingLevel: ThinkingLevel;
21
+ supportsThinking: boolean;
22
+ }
23
+
24
+ /**
25
+ * Persist explicit browser selections without re-running AgentSession setters.
26
+ *
27
+ * The session constructor already records the effective model and thinking
28
+ * level. Calling setModel()/setThinkingLevel() again would append duplicate
29
+ * session entries and emit duplicate extension events.
30
+ */
31
+ export async function persistExplicitStartupPreferences(
32
+ settingsManager: SettingsManager,
33
+ explicit: ExplicitStartupPreferences,
34
+ effective: EffectiveStartupPreferences,
35
+ ): Promise<{ modelDefaultChanged: boolean }> {
36
+ if (!explicit.model && !explicit.thinkingLevel) {
37
+ return { modelDefaultChanged: false };
38
+ }
39
+
40
+ let modelDefaultChanged = false;
41
+
42
+ if (
43
+ explicit.model
44
+ && effective.model
45
+ && explicit.model.provider === effective.model.provider
46
+ && explicit.model.modelId === effective.model.modelId
47
+ ) {
48
+ settingsManager.setDefaultModelAndProvider(
49
+ effective.model.provider,
50
+ effective.model.modelId,
51
+ );
52
+ modelDefaultChanged = true;
53
+ }
54
+
55
+ if (explicit.thinkingLevel && (effective.supportsThinking || effective.thinkingLevel !== "off")) {
56
+ // auto 是 pi-web 的占位值,pi 不认(SDK 收到会把新会话钳成 off)。
57
+ // 这里删除字段 = 回到 pi 默认(不写 defaultThinkingLevel),而不是固化 off。
58
+ // JSON.stringify 会丢弃 undefined 字段值,SDK 落盘时即删除该键。
59
+ const level = isThinkingLevel(effective.thinkingLevel) ? effective.thinkingLevel : undefined;
60
+ settingsManager.setDefaultThinkingLevel(level as ThinkingLevel);
61
+ }
62
+
63
+ await settingsManager.flush();
64
+ return { modelDefaultChanged };
65
+ }
@@ -0,0 +1,212 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { createJiti } from "jiti";
4
+
5
+ const jiti = createJiti(import.meta.url);
6
+ const {
7
+ INITIAL_STREAMING_STATE,
8
+ streamReducer,
9
+ } = await jiti.import("./streaming-message.ts");
10
+
11
+ function assistant(content = []) {
12
+ return {
13
+ role: "assistant",
14
+ content,
15
+ model: "claude-sonnet-4-6",
16
+ provider: "anthropic",
17
+ timestamp: 123,
18
+ };
19
+ }
20
+
21
+ function snapshot(state, message) {
22
+ return streamReducer(state, { type: "snapshot", message });
23
+ }
24
+
25
+ function delta(state, event) {
26
+ return streamReducer(state, { type: "delta", event });
27
+ }
28
+
29
+ test("builds thinking and text blocks from official assistant deltas", () => {
30
+ let state = streamReducer(INITIAL_STREAMING_STATE, { type: "start" });
31
+ state = snapshot(state, assistant());
32
+ state = delta(state, { type: "thinking_start", contentIndex: 0 });
33
+ state = delta(state, { type: "thinking_delta", contentIndex: 0, delta: "Plan" });
34
+ state = delta(state, { type: "thinking_end", contentIndex: 0, content: "Plan." });
35
+ state = delta(state, { type: "text_start", contentIndex: 1 });
36
+ state = delta(state, { type: "text_delta", contentIndex: 1, delta: "Hel" });
37
+ state = delta(state, { type: "text_delta", contentIndex: 1, delta: "lo" });
38
+ state = delta(state, { type: "text_end", contentIndex: 1, content: "Hello" });
39
+
40
+ assert.equal(state.isStreaming, true);
41
+ assert.equal(state.streamingMessage.model, "claude-sonnet-4-6");
42
+ assert.equal(state.streamingMessage.provider, "anthropic");
43
+ assert.equal(state.streamingMessage.timestamp, 123);
44
+ assert.deepEqual(state.streamingMessage.content, [
45
+ { type: "thinking", thinking: "Plan." },
46
+ { type: "text", text: "Hello" },
47
+ ]);
48
+ });
49
+
50
+ test("a reconnect snapshot replaces the old partial before deltas continue", () => {
51
+ let state = snapshot(INITIAL_STREAMING_STATE, assistant([
52
+ { type: "text", text: "stale" },
53
+ ]));
54
+ state = snapshot(state, assistant([
55
+ { type: "text", text: "Hello wor" },
56
+ ]));
57
+ state = delta(state, { type: "text_delta", contentIndex: 0, delta: "ld" });
58
+
59
+ assert.deepEqual(state.streamingMessage.content, [
60
+ { type: "text", text: "Hello world" },
61
+ ]);
62
+ });
63
+
64
+ test("text deltas update immutably so React observes each chunk", () => {
65
+ const previous = snapshot(INITIAL_STREAMING_STATE, assistant([
66
+ { type: "text", text: "Hello" },
67
+ ]));
68
+ const next = delta(previous, { type: "text_delta", contentIndex: 0, delta: "!" });
69
+
70
+ assert.notStrictEqual(next, previous);
71
+ assert.notStrictEqual(next.streamingMessage, previous.streamingMessage);
72
+ assert.notStrictEqual(next.streamingMessage.content, previous.streamingMessage.content);
73
+ assert.equal(previous.streamingMessage.content[0].text, "Hello");
74
+ assert.equal(next.streamingMessage.content[0].text, "Hello!");
75
+ });
76
+
77
+ test("shows and streams a tool call after thinking, then accepts the authoritative end", () => {
78
+ let state = snapshot(INITIAL_STREAMING_STATE, assistant([
79
+ { type: "thinking", thinking: "I will write the file." },
80
+ ]));
81
+
82
+ state = delta(state, {
83
+ type: "toolcall_start",
84
+ contentIndex: 1,
85
+ id: "call-1",
86
+ toolName: "write",
87
+ });
88
+ assert.deepEqual(state.streamingMessage.content, [
89
+ { type: "thinking", thinking: "I will write the file." },
90
+ {
91
+ type: "toolCall",
92
+ toolCallId: "call-1",
93
+ toolName: "write",
94
+ input: {},
95
+ rawInput: "",
96
+ },
97
+ ]);
98
+
99
+ const beforeDelta = state;
100
+ state = delta(state, {
101
+ type: "toolcall_delta",
102
+ contentIndex: 1,
103
+ delta: '{"path":',
104
+ id: "call-1",
105
+ toolName: "write",
106
+ });
107
+ state = delta(state, { type: "toolcall_delta", contentIndex: 1, delta: '"/tmp/file"' });
108
+ assert.notStrictEqual(state, beforeDelta);
109
+ assert.equal(state.streamingMessage.content[1].rawInput, '{"path":"/tmp/file"');
110
+
111
+ state = delta(state, {
112
+ type: "toolcall_end",
113
+ contentIndex: 1,
114
+ toolCall: {
115
+ type: "toolCall",
116
+ id: "call-1",
117
+ name: "write",
118
+ arguments: { path: "/tmp/file" },
119
+ },
120
+ });
121
+ assert.deepEqual(state.streamingMessage.content, [
122
+ { type: "thinking", thinking: "I will write the file." },
123
+ {
124
+ type: "toolCall",
125
+ toolCallId: "call-1",
126
+ toolName: "write",
127
+ input: { path: "/tmp/file" },
128
+ },
129
+ ]);
130
+ assert.equal(Object.hasOwn(state.streamingMessage.content[1], "rawInput"), false);
131
+ });
132
+
133
+ test("restores the raw tool input from a reconnect snapshot", () => {
134
+ const state = snapshot(INITIAL_STREAMING_STATE, assistant([{
135
+ type: "toolCall",
136
+ id: "call-2",
137
+ name: "write",
138
+ arguments: { path: "/tmp/reconnected" },
139
+ partialJson: '{"path":"/tmp/reconnected","content":"hel',
140
+ }]));
141
+
142
+ assert.deepEqual(state.streamingMessage.content, [{
143
+ type: "toolCall",
144
+ toolCallId: "call-2",
145
+ toolName: "write",
146
+ input: { path: "/tmp/reconnected" },
147
+ rawInput: '{"path":"/tmp/reconnected","content":"hel',
148
+ }]);
149
+ });
150
+
151
+ test("ignores deltas without a baseline and unknown future deltas", () => {
152
+ const started = streamReducer(INITIAL_STREAMING_STATE, { type: "start" });
153
+ assert.strictEqual(
154
+ delta(started, { type: "text_delta", contentIndex: 0, delta: "lost" }),
155
+ started,
156
+ );
157
+
158
+ const withMessage = snapshot(started, assistant());
159
+ assert.strictEqual(
160
+ delta(withMessage, { type: "future_delta", contentIndex: 0, delta: "ignored" }),
161
+ withMessage,
162
+ );
163
+ });
164
+
165
+ test("normalizes tool calls in snapshots and clears on end", () => {
166
+ const state = snapshot(INITIAL_STREAMING_STATE, assistant([{
167
+ type: "toolCall",
168
+ id: "call-2",
169
+ name: "bash",
170
+ arguments: { command: "pwd" },
171
+ }]));
172
+ assert.deepEqual(state.streamingMessage.content, [{
173
+ type: "toolCall",
174
+ toolCallId: "call-2",
175
+ toolName: "bash",
176
+ input: { command: "pwd" },
177
+ }]);
178
+ assert.strictEqual(streamReducer(state, { type: "end" }), INITIAL_STREAMING_STATE);
179
+ });
180
+
181
+ // Switch-back race: while a session is streaming, the SSE reconnect replays
182
+ // the partial message via `snapshot`. The mount-time state restore runs two
183
+ // fetches AFTER that connection is already open, and its `start` dispatch
184
+ // must NOT wipe the replayed streaming content — otherwise every subsequent
185
+ // text delta is dropped and the typewriter never resumes (the message only
186
+ // pops in whole at message_end).
187
+ test("start preserves an already-replayed streaming message (switch-back race)", () => {
188
+ let state = snapshot(INITIAL_STREAMING_STATE, assistant([
189
+ { type: "text", text: "partial reply" },
190
+ ]));
191
+ assert.equal(state.isStreaming, true);
192
+ assert.equal(state.streamingMessage.content[0].text, "partial reply");
193
+
194
+ // A late `start` (mount-time state restore completing after the SSE
195
+ // snapshot replay) must keep the replayed content alive.
196
+ state = streamReducer(state, { type: "start" });
197
+ assert.equal(state.isStreaming, true);
198
+ assert.equal(
199
+ state.streamingMessage.content[0].text,
200
+ "partial reply",
201
+ "start must not wipe replayed streaming content",
202
+ );
203
+
204
+ // Deltas keep appending to the preserved message.
205
+ state = delta(state, { type: "text_delta", contentIndex: 0, delta: " more" });
206
+ assert.equal(state.streamingMessage.content[0].text, "partial reply more");
207
+ });
208
+
209
+ test("start on an empty stream still behaves as before", () => {
210
+ const state = streamReducer(INITIAL_STREAMING_STATE, { type: "start" });
211
+ assert.deepEqual(state, { isStreaming: true, streamingMessage: null });
212
+ });