@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,245 @@
1
+ import { randomUUID, randomBytes } from "crypto";
2
+ import type { IPty } from "@lydell/node-pty";
3
+
4
+ type IPtyModule = typeof import("@lydell/node-pty");
5
+
6
+ // ============================================================================
7
+ // Terminal session registry — server-side keepalive terminals.
8
+ // Sessions outlive browser refreshes: pty output is kept in a ring buffer and
9
+ // replayed by byte offset when a client re-attaches (SSE ?since=<offset>).
10
+ // Registry lives on globalThis so Next.js hot-reload doesn't kill running
11
+ // shells (same pattern as the agent session registry in rpc-manager.ts).
12
+ // ============================================================================
13
+
14
+ const RING_BUFFER_BYTES = 256 * 1024;
15
+ export const MAX_TERMINALS = 12;
16
+
17
+ export interface TerminalSessionMeta {
18
+ id: string;
19
+ /** 展示用名称:默认 `<cwd basename>-<4位随机nanoid>`,创建时生成、持久稳定,用于区分同项目多终端。 */
20
+ name: string;
21
+ cwd: string;
22
+ projectRoot: string | null;
23
+ projectLabel: string;
24
+ cols: number;
25
+ rows: number;
26
+ running: boolean;
27
+ exitCode: number | null;
28
+ createdAt: number;
29
+ }
30
+
31
+ /** URL 安全字母表上的 4 位随机串(近似 nanoid 的短 id)。 */
32
+ const ID4_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
33
+ export function randomId4(): string {
34
+ const bytes = randomBytes(4);
35
+ let out = "";
36
+ for (let i = 0; i < 4; i++) out += ID4_ALPHABET[bytes[i] % ID4_ALPHABET.length];
37
+ return out;
38
+ }
39
+
40
+ interface TerminalSession extends TerminalSessionMeta {
41
+ pty: IPty | null; // null once exited
42
+ buffer: string; // ring buffer of recent output
43
+ /** Monotonic offset = total bytes ever written minus buffer.length. */
44
+ baseOffset: number;
45
+ listeners: Set<(chunk: string) => void>;
46
+ }
47
+
48
+ type Registry = {
49
+ sessions: Map<string, TerminalSession>;
50
+ };
51
+
52
+ function registry(): Registry {
53
+ const g = globalThis as typeof globalThis & { __piTerminals?: Registry };
54
+ if (!g.__piTerminals) g.__piTerminals = { sessions: new Map() };
55
+ return g.__piTerminals;
56
+ }
57
+
58
+ /**
59
+ * Drop the excess prefix of a ring buffer, cutting just after the last
60
+ * newline at/at-before the overflow point. Replays start from the buffer
61
+ * head, so a byte-granular cut can land inside a multi-byte char or an ANSI
62
+ * escape sequence — xterm does NOT tolerate that (garbage chars at the top
63
+ * of the terminal after a re-attach). Falls back to the plain cut when the
64
+ * overflow lands in a newline-free run (no way to align; rare and benign).
65
+ * Returns [kept, droppedBytes].
66
+ */
67
+ export function trimRingBuffer(buffer: string, maxBytes: number): [string, number] {
68
+ if (buffer.length <= maxBytes) return [buffer, 0];
69
+ let drop = buffer.length - maxBytes;
70
+ const nl = buffer.lastIndexOf("\n", drop);
71
+ if (nl >= 0) drop = nl + 1;
72
+ return [buffer.slice(drop), drop];
73
+ }
74
+
75
+ /** Append output to the ring buffer and fan out to live SSE listeners. */
76
+ function pushOutput(s: TerminalSession, data: string) {
77
+ s.buffer += data;
78
+ if (s.buffer.length > RING_BUFFER_BYTES) {
79
+ const [kept, dropped] = trimRingBuffer(s.buffer, RING_BUFFER_BYTES);
80
+ s.buffer = kept;
81
+ s.baseOffset += dropped;
82
+ }
83
+ for (const fn of s.listeners) fn(data);
84
+ }
85
+
86
+ /**
87
+ * Bytes buffered starting from `since`; null when the offset has fallen out
88
+ * of the ring buffer (caller should fall back to fullBuffer()).
89
+ */
90
+ export function outputSince(id: string, since: number): string | null {
91
+ const s = registry().sessions.get(id);
92
+ if (!s) return null;
93
+ const bufStart = s.baseOffset;
94
+ if (since < bufStart) return null;
95
+ return s.buffer.slice(Math.max(0, since - bufStart));
96
+ }
97
+
98
+ /** Entire ring buffer with its absolute start offset. */
99
+ export function fullBuffer(id: string): { data: string; startOffset: number } | null {
100
+ const s = registry().sessions.get(id);
101
+ if (!s) return null;
102
+ return { data: s.buffer, startOffset: s.baseOffset };
103
+ }
104
+
105
+ function toMeta(s: TerminalSession): TerminalSessionMeta {
106
+ return {
107
+ id: s.id, name: s.name, cwd: s.cwd, projectRoot: s.projectRoot, projectLabel: s.projectLabel,
108
+ cols: s.cols, rows: s.rows, running: s.running, exitCode: s.exitCode,
109
+ createdAt: s.createdAt,
110
+ };
111
+ }
112
+
113
+ export function listTerminals(): TerminalSessionMeta[] {
114
+ return [...registry().sessions.values()].map(toMeta);
115
+ }
116
+
117
+ export function getTerminal(id: string): TerminalSessionMeta | undefined {
118
+ const s = registry().sessions.get(id);
119
+ return s ? toMeta(s) : undefined;
120
+ }
121
+
122
+ export function currentOffset(id: string): number {
123
+ const s = registry().sessions.get(id);
124
+ return s ? s.baseOffset + s.buffer.length : 0;
125
+ }
126
+
127
+ export async function createTerminal(opts: {
128
+ cwd: string;
129
+ projectRoot: string | null;
130
+ projectLabel: string;
131
+ cols?: number;
132
+ rows?: number;
133
+ }): Promise<TerminalSessionMeta> {
134
+ const sessions = registry().sessions;
135
+
136
+ // Cap total sessions; reclaim exited ones first (oldest exit first).
137
+ if (sessions.size >= MAX_TERMINALS) {
138
+ const exited = [...sessions.values()]
139
+ .filter((s) => !s.running)
140
+ .sort((a, b) => a.createdAt - b.createdAt);
141
+ const victim = exited[0];
142
+ if (!victim) throw new Error(`Terminal limit reached (${MAX_TERMINALS})`);
143
+ removeTerminal(victim.id);
144
+ }
145
+
146
+ const cols = opts.cols ?? 80;
147
+ const rows = opts.rows ?? 24;
148
+ const shell = process.platform === "win32" ? "powershell.exe" : process.env.SHELL || "bash";
149
+ const args = process.platform === "win32" || process.env.SHELL?.endsWith("fish") ? [] : ["-l"];
150
+
151
+ // Webpack bundles external packages as plain CJS require()s, so the ESM
152
+ // interop `default` can be missing in the production build — fall back to
153
+ // the module object itself (dev keeps `default`, node-pty exports both).
154
+ const ptyMod = (await import("@lydell/node-pty")) as { default?: IPtyModule };
155
+ const pty = (ptyMod.default ?? ptyMod) as IPtyModule;
156
+ const proc = pty.spawn(shell, args, {
157
+ name: "xterm-256color",
158
+ cwd: opts.cwd,
159
+ cols,
160
+ rows,
161
+ env: process.env as Record<string, string>,
162
+ });
163
+
164
+ const id = randomUUID();
165
+ const base = opts.cwd.split(/[\\/]/).filter(Boolean).pop() || "term";
166
+ const session: TerminalSession = {
167
+ id,
168
+ name: `${base}-${randomId4()}`,
169
+ cwd: opts.cwd,
170
+ projectRoot: opts.projectRoot,
171
+ projectLabel: opts.projectLabel,
172
+ cols,
173
+ rows,
174
+ running: true,
175
+ exitCode: null,
176
+ createdAt: Date.now(),
177
+ pty: proc,
178
+ buffer: "",
179
+ baseOffset: 0,
180
+ listeners: new Set(),
181
+ };
182
+ proc.onData((data) => pushOutput(session, data));
183
+ proc.onExit(({ exitCode }) => {
184
+ session.running = false;
185
+ session.exitCode = exitCode;
186
+ session.pty = null;
187
+ pushOutput(session, `\r\n\x1b[2m[进程已退出,exit code ${exitCode}]\x1b[0m\r\n`);
188
+ });
189
+ sessions.set(id, session);
190
+ return getTerminal(id)!;
191
+ }
192
+
193
+ export function writeTerminal(id: string, data: string): boolean {
194
+ const s = registry().sessions.get(id);
195
+ if (!s?.pty) return false;
196
+ s.pty.write(data);
197
+ return true;
198
+ }
199
+
200
+ export function resizeTerminal(id: string, cols: number, rows: number): boolean {
201
+ const s = registry().sessions.get(id);
202
+ if (!s) return false;
203
+ s.cols = cols;
204
+ s.rows = rows;
205
+ try {
206
+ s.pty?.resize(cols, rows);
207
+ } catch {
208
+ // resize after exit races onExit; harmless.
209
+ }
210
+ return true;
211
+ }
212
+
213
+ /** Kill the process. Output stays inspectable until removeTerminal(). */
214
+ export function killTerminal(id: string): boolean {
215
+ const s = registry().sessions.get(id);
216
+ if (!s) return false;
217
+ if (s.pty) {
218
+ try {
219
+ s.pty.kill();
220
+ } catch {
221
+ // already dead
222
+ }
223
+ }
224
+ return true;
225
+ }
226
+
227
+ export function removeTerminal(id: string): boolean {
228
+ const s = registry().sessions.get(id);
229
+ if (!s) return false;
230
+ killTerminal(id);
231
+ for (const fn of s.listeners) fn(""); // release SSE streams
232
+ s.listeners.clear();
233
+ return registry().sessions.delete(id);
234
+ }
235
+
236
+ export function addListener(id: string, fn: (chunk: string) => void): boolean {
237
+ const s = registry().sessions.get(id);
238
+ if (!s) return false;
239
+ s.listeners.add(fn);
240
+ return true;
241
+ }
242
+
243
+ export function removeListener(id: string, fn: (chunk: string) => void): void {
244
+ registry().sessions.get(id)?.listeners.delete(fn);
245
+ }
@@ -0,0 +1,32 @@
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 { getToolExecutionProgress } = await jiti.import("./tool-execution-progress.ts");
7
+
8
+ test("uses the latest non-empty text line from a partial tool result", () => {
9
+ assert.equal(getToolExecutionProgress({
10
+ content: [
11
+ { type: "text", text: "Phase 1 complete\n" },
12
+ { type: "image", data: "ignored" },
13
+ { type: "text", text: "\nProcessing item 4 of 10\n" },
14
+ ],
15
+ }), "Processing item 4 of 10");
16
+ });
17
+
18
+ test("ignores partial tool results without displayable text", () => {
19
+ assert.equal(getToolExecutionProgress(null), null);
20
+ assert.equal(getToolExecutionProgress({ content: [{ type: "image" }] }), null);
21
+ assert.equal(getToolExecutionProgress({ content: [{ type: "text", text: " \n" }] }), null);
22
+ });
23
+
24
+ test("bounds long progress lines while preserving the latest text", () => {
25
+ const progress = getToolExecutionProgress({
26
+ content: [{ type: "text", text: `prefix-${"x".repeat(600)}-latest` }],
27
+ });
28
+
29
+ assert.equal(progress.length, 500);
30
+ assert.match(progress, /^\.\.\./);
31
+ assert.match(progress, /-latest$/);
32
+ });
@@ -0,0 +1,29 @@
1
+ const MAX_PROGRESS_LENGTH = 500;
2
+
3
+ function isObject(value: unknown): value is Record<string, unknown> {
4
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5
+ }
6
+
7
+ export function getToolExecutionProgress(partialResult: unknown): string | null {
8
+ if (!isObject(partialResult)) return null;
9
+
10
+ const content = partialResult.content;
11
+ if (!Array.isArray(content)) return null;
12
+
13
+ const text = content
14
+ .filter((block) => isObject(block) && block.type === "text" && typeof block.text === "string")
15
+ .map((block) => block.text as string)
16
+ .join("\n");
17
+ const lines = text.split(/\r?\n/);
18
+ let latest = "";
19
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
20
+ latest = lines[index].trim();
21
+ if (latest) break;
22
+ }
23
+ if (!latest) return null;
24
+
25
+ const normalized = latest.replace(/\s+/g, " ");
26
+ return normalized.length <= MAX_PROGRESS_LENGTH
27
+ ? normalized
28
+ : `...${normalized.slice(-(MAX_PROGRESS_LENGTH - 3))}`;
29
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Tool-name predicates shared by the chat views.
3
+ *
4
+ * Pi's built-in names are plain `write` / `edit`, but MCP servers expose the
5
+ * same operations under prefixed or namespaced names, so each predicate also
6
+ * accepts the common decorated forms.
7
+ */
8
+
9
+ export function isWriteToolName(toolName: string): boolean {
10
+ const name = toolName.toLowerCase();
11
+ return name === "write" ||
12
+ name.startsWith("write_") ||
13
+ name.endsWith(".write") ||
14
+ name.endsWith("_write");
15
+ }
16
+
17
+ export function isEditToolName(toolName: string): boolean {
18
+ const name = toolName.toLowerCase();
19
+ return name === "edit" ||
20
+ name.startsWith("edit_") ||
21
+ name.endsWith(".edit") ||
22
+ name.endsWith("_edit") ||
23
+ name.includes("str_replace") ||
24
+ name.includes("replace_editor");
25
+ }
@@ -0,0 +1,72 @@
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
+ getPreferredToolPreset,
8
+ setPreferredToolPreset,
9
+ } = await jiti.import("./tool-preset-preference.ts");
10
+ const { TOOL_PRESET_VALUES } = await jiti.import("./tool-presets.ts");
11
+
12
+ function createStorage(initial = {}) {
13
+ const values = new Map(Object.entries(initial));
14
+ return {
15
+ values,
16
+ getItem(key) {
17
+ return values.get(key) ?? null;
18
+ },
19
+ setItem(key, value) {
20
+ values.set(key, value);
21
+ },
22
+ };
23
+ }
24
+
25
+ test("defaults missing or invalid preferences to the default preset", () => {
26
+ assert.equal(getPreferredToolPreset(createStorage()), "default");
27
+ assert.equal(
28
+ getPreferredToolPreset(createStorage({ "pi-tool-preset": "legacy-value" })),
29
+ "default",
30
+ );
31
+ });
32
+
33
+ test("round-trips every supported tool preset", () => {
34
+ const storage = createStorage();
35
+ for (const preset of TOOL_PRESET_VALUES) {
36
+ setPreferredToolPreset(preset, storage);
37
+ assert.equal(storage.values.get("pi-tool-preset"), preset);
38
+ assert.equal(getPreferredToolPreset(storage), preset);
39
+ }
40
+ });
41
+
42
+ test("falls back safely when browser storage is unavailable", () => {
43
+ const unavailable = {
44
+ getItem() { throw new Error("blocked"); },
45
+ setItem() { throw new Error("blocked"); },
46
+ };
47
+
48
+ assert.equal(getPreferredToolPreset(unavailable), "default");
49
+ assert.doesNotThrow(() => setPreferredToolPreset("full", unavailable));
50
+ assert.equal(getPreferredToolPreset(null), "default");
51
+ assert.doesNotThrow(() => setPreferredToolPreset("full", null));
52
+ });
53
+
54
+ test("falls back when accessing window.localStorage throws", () => {
55
+ const previousWindow = Object.getOwnPropertyDescriptor(globalThis, "window");
56
+ const blockedWindow = {};
57
+ Object.defineProperty(blockedWindow, "localStorage", {
58
+ get() { throw new DOMException("blocked", "SecurityError"); },
59
+ });
60
+ Object.defineProperty(globalThis, "window", {
61
+ configurable: true,
62
+ value: blockedWindow,
63
+ });
64
+
65
+ try {
66
+ assert.equal(getPreferredToolPreset(), "default");
67
+ assert.doesNotThrow(() => setPreferredToolPreset("full"));
68
+ } finally {
69
+ if (previousWindow) Object.defineProperty(globalThis, "window", previousWindow);
70
+ else delete globalThis.window;
71
+ }
72
+ });
@@ -0,0 +1,41 @@
1
+ import { isToolPreset, type ToolPreset } from "./tool-presets";
2
+
3
+ const STORAGE_KEY = "pi-tool-preset";
4
+
5
+ interface StorageLike {
6
+ getItem(key: string): string | null;
7
+ setItem(key: string, value: string): void;
8
+ }
9
+
10
+ function getBrowserStorage(): StorageLike | null {
11
+ if (typeof window === "undefined") return null;
12
+ try {
13
+ return window.localStorage;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+
19
+ export function getPreferredToolPreset(
20
+ storage: StorageLike | null = getBrowserStorage(),
21
+ ): ToolPreset {
22
+ if (!storage) return "default";
23
+ try {
24
+ const value = storage.getItem(STORAGE_KEY);
25
+ return isToolPreset(value) ? value : "default";
26
+ } catch {
27
+ return "default";
28
+ }
29
+ }
30
+
31
+ export function setPreferredToolPreset(
32
+ preset: ToolPreset,
33
+ storage: StorageLike | null = getBrowserStorage(),
34
+ ): void {
35
+ if (!storage) return;
36
+ try {
37
+ storage.setItem(STORAGE_KEY, preset);
38
+ } catch {
39
+ // Browser storage is best-effort.
40
+ }
41
+ }
@@ -0,0 +1,56 @@
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
+ PRESET_DEFAULT,
8
+ PRESET_FULL,
9
+ PRESET_NONE,
10
+ PRESET_READ_ONLY,
11
+ getPresetFromTools,
12
+ getToolNamesForPreset,
13
+ } = await jiti.import("./tool-presets.ts");
14
+
15
+ const BUILTIN_NAMES = ["bash", "read", "edit", "write", "grep", "find", "ls"];
16
+
17
+ function toolEntries(activeNames, customNames = []) {
18
+ const active = new Set(activeNames);
19
+ return [...BUILTIN_NAMES, ...customNames].map((name) => ({
20
+ name,
21
+ description: name,
22
+ active: active.has(name),
23
+ }));
24
+ }
25
+
26
+ test("maps every tool preset to its built-in tools", () => {
27
+ assert.deepEqual(getToolNamesForPreset("none"), PRESET_NONE);
28
+ assert.deepEqual(getToolNamesForPreset("read-only"), PRESET_READ_ONLY);
29
+ assert.deepEqual(getToolNamesForPreset("default"), PRESET_DEFAULT);
30
+ assert.deepEqual(getToolNamesForPreset("full"), PRESET_FULL);
31
+ assert.deepEqual(PRESET_READ_ONLY, ["read", "grep", "find", "ls"]);
32
+ });
33
+
34
+ test("recognizes presets while ignoring active custom tools", () => {
35
+ const customNames = ["web_search", "delegate"];
36
+
37
+ assert.equal(getPresetFromTools(toolEntries([], customNames)), "none");
38
+ assert.equal(
39
+ getPresetFromTools(toolEntries([...PRESET_READ_ONLY, ...customNames], customNames)),
40
+ "read-only",
41
+ );
42
+ assert.equal(
43
+ getPresetFromTools(toolEntries([...PRESET_DEFAULT, ...customNames], customNames)),
44
+ "default",
45
+ );
46
+ assert.equal(
47
+ getPresetFromTools(toolEntries([...PRESET_FULL, ...customNames], customNames)),
48
+ "full",
49
+ );
50
+ });
51
+
52
+ test("returns fresh tool arrays that callers can safely modify", () => {
53
+ const names = getToolNamesForPreset("read-only");
54
+ names.push("custom");
55
+ assert.deepEqual(getToolNamesForPreset("read-only"), PRESET_READ_ONLY);
56
+ });
@@ -0,0 +1,42 @@
1
+ export interface ToolEntry {
2
+ name: string;
3
+ description: string;
4
+ active: boolean;
5
+ }
6
+
7
+ export const TOOL_PRESET_VALUES = ["none", "read-only", "default", "full"] as const;
8
+ export type ToolPreset = typeof TOOL_PRESET_VALUES[number];
9
+
10
+ export const PRESET_NONE: string[] = [];
11
+ export const PRESET_READ_ONLY: string[] = ["read", "grep", "find", "ls"];
12
+ export const PRESET_DEFAULT: string[] = ["read", "bash", "edit", "write"];
13
+ export const PRESET_FULL: string[] = ["bash", "read", "edit", "write", "grep", "find", "ls"];
14
+
15
+ const BUILTIN_TOOL_NAMES = new Set(PRESET_FULL);
16
+
17
+ export function isToolPreset(value: unknown): value is ToolPreset {
18
+ return typeof value === "string" && (TOOL_PRESET_VALUES as readonly string[]).includes(value);
19
+ }
20
+
21
+ export function getPresetFromTools(tools: ToolEntry[]): ToolPreset {
22
+ const activeTools = tools.filter((t) => t.active);
23
+ if (activeTools.length === 0) return "none";
24
+
25
+ const active = activeTools
26
+ .map((t) => t.name)
27
+ .filter((name) => BUILTIN_TOOL_NAMES.has(name))
28
+ .sort()
29
+ .join(",");
30
+
31
+ if (active === [...PRESET_READ_ONLY].sort().join(",")) return "read-only";
32
+ if (active === [...PRESET_DEFAULT].sort().join(",")) return "default";
33
+ if (active === [...PRESET_FULL].sort().join(",")) return "full";
34
+ return "default";
35
+ }
36
+
37
+ export function getToolNamesForPreset(preset: ToolPreset): string[] {
38
+ if (preset === "none") return [...PRESET_NONE];
39
+ if (preset === "read-only") return [...PRESET_READ_ONLY];
40
+ if (preset === "full") return [...PRESET_FULL];
41
+ return [...PRESET_DEFAULT];
42
+ }
@@ -0,0 +1,137 @@
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, { tsconfigPaths: true });
6
+ const { extractTurnWrittenFiles } = await jiti.import("./turn-written-files.ts");
7
+
8
+ function toolCall(toolCallId, toolName, input) {
9
+ return { type: "toolCall", toolCallId, toolName, input };
10
+ }
11
+
12
+ function okResult(toolCallId) {
13
+ return { role: "toolResult", toolCallId, content: [{ type: "text", text: "ok" }] };
14
+ }
15
+
16
+ function errorResult(toolCallId) {
17
+ return { role: "toolResult", toolCallId, content: [{ type: "text", text: "boom" }], isError: true };
18
+ }
19
+
20
+ function results(...entries) {
21
+ return new Map(entries.map((r) => [r.toolCallId, r]));
22
+ }
23
+
24
+ function paths(content, toolResults, cwd) {
25
+ return extractTurnWrittenFiles(content, toolResults, cwd).map((f) => f.filePath);
26
+ }
27
+
28
+ test("extracts a file from a successful write tool call", () => {
29
+ const content = [toolCall("1", "write", { file_path: "/abs/out/report.html" })];
30
+ assert.deepEqual(paths(content, results(okResult("1"))), ["/abs/out/report.html"]);
31
+ });
32
+
33
+ test("extracts a file from a successful edit tool call using input.path", () => {
34
+ const content = [toolCall("1", "edit", { path: "/abs/src/a.ts" })];
35
+ assert.deepEqual(paths(content, results(okResult("1"))), ["/abs/src/a.ts"]);
36
+ });
37
+
38
+ test("accepts namespaced write/edit tool names from MCP servers", () => {
39
+ const content = [
40
+ toolCall("1", "write_file", { file_path: "/abs/a.txt" }),
41
+ toolCall("2", "fs.edit", { file_path: "/abs/b.txt" }),
42
+ toolCall("3", "str_replace_editor", { file_path: "/abs/c.txt" }),
43
+ ];
44
+ assert.deepEqual(
45
+ paths(content, results(okResult("1"), okResult("2"), okResult("3"))),
46
+ ["/abs/a.txt", "/abs/b.txt", "/abs/c.txt"],
47
+ );
48
+ });
49
+
50
+ test("skips a tool call whose result errored", () => {
51
+ const content = [toolCall("1", "write", { file_path: "/abs/out/report.html" })];
52
+ assert.deepEqual(paths(content, results(errorResult("1"))), []);
53
+ });
54
+
55
+ test("skips a tool call whose result has not arrived (streaming)", () => {
56
+ const content = [toolCall("1", "write", { file_path: "/abs/out/report.html" })];
57
+ assert.deepEqual(paths(content, results()), []);
58
+ assert.deepEqual(paths(content, undefined), []);
59
+ });
60
+
61
+ test("deduplicates the same file written then edited", () => {
62
+ const content = [
63
+ toolCall("1", "write", { file_path: "/abs/out/report.html" }),
64
+ toolCall("2", "edit", { path: "/abs/out/report.html" }),
65
+ ];
66
+ assert.deepEqual(paths(content, results(okResult("1"), okResult("2"))), ["/abs/out/report.html"]);
67
+ });
68
+
69
+ test("resolves a relative path against cwd", () => {
70
+ const content = [toolCall("1", "write", { file_path: "out/report.html" })];
71
+ assert.deepEqual(paths(content, results(okResult("1")), "/abs"), ["/abs/out/report.html"]);
72
+ });
73
+
74
+ test("resolves extensionless and dot-prefixed filenames against cwd", () => {
75
+ const content = [
76
+ toolCall("1", "write", { path: "LICENSE" }),
77
+ toolCall("2", "write", { path: ".env" }),
78
+ ];
79
+ assert.deepEqual(
80
+ paths(content, results(okResult("1"), okResult("2")), "/repo"),
81
+ ["/repo/LICENSE", "/repo/.env"],
82
+ );
83
+ });
84
+
85
+ test("preserves path characters that have special meaning in hrefs", () => {
86
+ const content = [
87
+ toolCall("1", "write", { path: "release#1.md" }),
88
+ toolCall("2", "write", { path: "query?.json" }),
89
+ toolCall("3", "write", { path: "report.md:42" }),
90
+ ];
91
+ assert.deepEqual(
92
+ paths(content, results(okResult("1"), okResult("2"), okResult("3")), "/repo"),
93
+ ["/repo/release#1.md", "/repo/query?.json", "/repo/report.md:42"],
94
+ );
95
+ });
96
+
97
+ test("normalizes Windows-relative tool paths against a Windows cwd", () => {
98
+ const content = [toolCall("1", "write", { path: "src\\report.html" })];
99
+ assert.deepEqual(
100
+ paths(content, results(okResult("1")), "C:\\repo"),
101
+ ["C:/repo/src/report.html"],
102
+ );
103
+ });
104
+
105
+ test("skips non-writing tools like read and bash", () => {
106
+ const content = [
107
+ toolCall("1", "read", { file_path: "/abs/a.ts" }),
108
+ toolCall("2", "bash", { command: "echo hi > /abs/a.txt" }),
109
+ ];
110
+ assert.deepEqual(paths(content, results(okResult("1"), okResult("2"))), []);
111
+ });
112
+
113
+ test("ignores paths that only appear in the reply text", () => {
114
+ // A path the assistant merely writes in prose is not evidence of a write.
115
+ const content = [
116
+ { type: "text", text: "I saved the report to /abs/out/report.html for you." },
117
+ ];
118
+ assert.deepEqual(paths(content, results()), []);
119
+ });
120
+
121
+ test("lists only the file actually written, not others named in the text", () => {
122
+ const content = [
123
+ toolCall("1", "write", { file_path: "/abs/out/real.html" }),
124
+ { type: "text", text: "See also /abs/out/imagined.html and /etc/passwd" },
125
+ ];
126
+ assert.deepEqual(paths(content, results(okResult("1"))), ["/abs/out/real.html"]);
127
+ });
128
+
129
+ test("skips a write call missing both file_path and path", () => {
130
+ const content = [toolCall("1", "write", { content: "hi" })];
131
+ assert.deepEqual(paths(content, results(okResult("1"))), []);
132
+ });
133
+
134
+ test("returns an empty array for an empty or text-only turn", () => {
135
+ assert.deepEqual(paths([], results()), []);
136
+ assert.deepEqual(paths([{ type: "text", text: "hi" }], results()), []);
137
+ });