@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,83 @@
1
+ import assert from "node:assert/strict";
2
+ import { EventEmitter } from "node:events";
3
+ import test from "node:test";
4
+ import { setTimeout as delay } from "node:timers/promises";
5
+
6
+ import { wireChildProcessLifecycle } from "../bin/process-lifecycle.js";
7
+
8
+ function createProcesses() {
9
+ const parent = new EventEmitter();
10
+ const child = new EventEmitter();
11
+ const forwardedSignals = [];
12
+ const exitCodes = [];
13
+
14
+ child.kill = (signal) => {
15
+ forwardedSignals.push(signal);
16
+ return true;
17
+ };
18
+ parent.exit = (code) => {
19
+ exitCodes.push(code);
20
+ };
21
+
22
+ return { parent, child, forwardedSignals, exitCodes };
23
+ }
24
+
25
+ test("forwards the first shutdown signal and force-kills on repeated signals", () => {
26
+ const { parent, child, forwardedSignals } = createProcesses();
27
+
28
+ wireChildProcessLifecycle(child, parent);
29
+ parent.emit("SIGTERM");
30
+ parent.emit("SIGTERM");
31
+ parent.emit("SIGINT");
32
+
33
+ assert.deepEqual(forwardedSignals, ["SIGTERM", "SIGKILL", "SIGKILL"]);
34
+ child.emit("exit", null, "SIGKILL");
35
+ });
36
+
37
+ test("propagates a child exit code and clears its shutdown wiring", async () => {
38
+ const { parent, child, forwardedSignals, exitCodes } = createProcesses();
39
+ const existingSigtermListener = () => {};
40
+ parent.on("SIGTERM", existingSigtermListener);
41
+
42
+ wireChildProcessLifecycle(child, parent, 10);
43
+ assert.equal(parent.listenerCount("SIGINT"), 1);
44
+ assert.equal(parent.listenerCount("SIGTERM"), 2);
45
+
46
+ parent.emit("SIGTERM");
47
+ child.emit("exit", 23, null);
48
+ await delay(20);
49
+
50
+ assert.deepEqual(exitCodes, [23]);
51
+ assert.equal(parent.listenerCount("SIGINT"), 0);
52
+ assert.deepEqual(parent.listeners("SIGTERM"), [existingSigtermListener]);
53
+
54
+ parent.emit("SIGTERM");
55
+ assert.deepEqual(forwardedSignals, ["SIGTERM"]);
56
+ });
57
+
58
+ test("force-kills the child when graceful shutdown times out", async () => {
59
+ const { parent, child, forwardedSignals } = createProcesses();
60
+
61
+ wireChildProcessLifecycle(child, parent, 10);
62
+ parent.emit("SIGINT");
63
+ assert.deepEqual(forwardedSignals, ["SIGINT"]);
64
+
65
+ await delay(20);
66
+
67
+ assert.deepEqual(forwardedSignals, ["SIGINT", "SIGKILL"]);
68
+ child.emit("exit", null, "SIGKILL");
69
+ });
70
+
71
+ test("uses conventional exit statuses for known child signals", () => {
72
+ for (const [signal, expectedExitCode] of [
73
+ ["SIGTERM", 143],
74
+ ["SIGKILL", 137],
75
+ ]) {
76
+ const { parent, child, exitCodes } = createProcesses();
77
+
78
+ wireChildProcessLifecycle(child, parent);
79
+ child.emit("exit", null, signal);
80
+
81
+ assert.deepEqual(exitCodes, [expectedExitCode]);
82
+ }
83
+ });
@@ -0,0 +1,298 @@
1
+ import assert from "node:assert/strict";
2
+ import { delimiter, join } from "node:path";
3
+ import test from "node:test";
4
+ import {
5
+ DefaultResourceLoader,
6
+ createLocalBashOperations,
7
+ getAgentDir,
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import { createJiti } from "jiti";
10
+
11
+ const {
12
+ createProjectCommandBashExtension,
13
+ createProjectCommandBashOperations,
14
+ preferUserBashExtension,
15
+ sanitizeProjectCommandEnvironment,
16
+ } = await createJiti(import.meta.url).import("./project-command-env.ts");
17
+
18
+ const HOST_ENVIRONMENT = {
19
+ PORT: "30141",
20
+ NODE_ENV: "production",
21
+ NEXT_RUNTIME: "nodejs",
22
+ NEXT_PRIVATE_WORKER: "1",
23
+ PATH: "/usr/local/bin:/usr/bin",
24
+ HOME: "/home/pi",
25
+ HTTPS_PROXY: "http://proxy.example",
26
+ OPENROUTER_API_KEY: "secret",
27
+ PI_USER_SETTING: "preserved",
28
+ };
29
+
30
+ test("sanitizes host variables using platform casing rules", () => {
31
+ assert.deepEqual(
32
+ sanitizeProjectCommandEnvironment(HOST_ENVIRONMENT, "linux"),
33
+ {
34
+ PATH: HOST_ENVIRONMENT.PATH,
35
+ HOME: HOST_ENVIRONMENT.HOME,
36
+ HTTPS_PROXY: HOST_ENVIRONMENT.HTTPS_PROXY,
37
+ OPENROUTER_API_KEY: HOST_ENVIRONMENT.OPENROUTER_API_KEY,
38
+ PI_USER_SETTING: HOST_ENVIRONMENT.PI_USER_SETTING,
39
+ },
40
+ );
41
+ assert.deepEqual(
42
+ sanitizeProjectCommandEnvironment(
43
+ {
44
+ Port: "30141",
45
+ node_env: "production",
46
+ Next_Runtime: "nodejs",
47
+ NEXT_PUBLIC_FLAG: "1",
48
+ Path: "C:\\Windows",
49
+ },
50
+ "win32",
51
+ ),
52
+ { Path: "C:\\Windows" },
53
+ );
54
+ assert.deepEqual(
55
+ sanitizeProjectCommandEnvironment(
56
+ {
57
+ PORT: "30141",
58
+ Port: "project-value",
59
+ NODE_ENV: "production",
60
+ node_env: "project-mode",
61
+ NEXT_RUNTIME: "nodejs",
62
+ Next_Runtime: "project-runtime",
63
+ },
64
+ "linux",
65
+ ),
66
+ {
67
+ Port: "project-value",
68
+ node_env: "project-mode",
69
+ Next_Runtime: "project-runtime",
70
+ },
71
+ );
72
+ });
73
+
74
+ test("agent bash removes host variables while preserving SDK and user environment", async () => {
75
+ const original = {
76
+ PORT: process.env.PORT,
77
+ NODE_ENV: process.env.NODE_ENV,
78
+ NEXT_RUNTIME: process.env.NEXT_RUNTIME,
79
+ NEXT_PRIVATE_WORKER: process.env.NEXT_PRIVATE_WORKER,
80
+ PI_USER_SETTING: process.env.PI_USER_SETTING,
81
+ };
82
+ Object.assign(process.env, {
83
+ PORT: "30141",
84
+ NODE_ENV: "production",
85
+ NEXT_RUNTIME: "nodejs",
86
+ NEXT_PRIVATE_WORKER: "1",
87
+ PI_USER_SETTING: "preserved",
88
+ });
89
+
90
+ try {
91
+ const extension = createProjectCommandBashExtension({
92
+ cwd: process.cwd(),
93
+ settings: {
94
+ getShellCommandPrefix: () => undefined,
95
+ getShellPath: () => undefined,
96
+ },
97
+ });
98
+ let registeredTool;
99
+ await extension.factory({
100
+ registerTool(tool) {
101
+ registeredTool = tool;
102
+ },
103
+ });
104
+
105
+ const result = await registeredTool.execute(
106
+ "issue-484",
107
+ {
108
+ command: `node -e 'console.log(JSON.stringify({PORT:process.env.PORT,NODE_ENV:process.env.NODE_ENV,NEXT_RUNTIME:process.env.NEXT_RUNTIME,NEXT_PRIVATE_WORKER:process.env.NEXT_PRIVATE_WORKER,PI_USER_SETTING:process.env.PI_USER_SETTING,PI_SESSION_ID:process.env.PI_SESSION_ID,PATH:process.env.PATH}))'`,
109
+ },
110
+ undefined,
111
+ undefined,
112
+ {
113
+ model: undefined,
114
+ thinkingLevel: "off",
115
+ sessionManager: {
116
+ getSessionId: () => "session-484",
117
+ getSessionFile: () => undefined,
118
+ },
119
+ },
120
+ );
121
+ const childEnvironment = JSON.parse(result.content[0].text);
122
+
123
+ assert.equal(childEnvironment.PORT, undefined);
124
+ assert.equal(childEnvironment.NODE_ENV, undefined);
125
+ assert.equal(childEnvironment.NEXT_RUNTIME, undefined);
126
+ assert.equal(childEnvironment.NEXT_PRIVATE_WORKER, undefined);
127
+ assert.equal(childEnvironment.PI_USER_SETTING, "preserved");
128
+ assert.equal(childEnvironment.PI_SESSION_ID, "session-484");
129
+ assert.ok(childEnvironment.PATH.split(delimiter).includes(join(getAgentDir(), "bin")));
130
+ } finally {
131
+ for (const [name, value] of Object.entries(original)) {
132
+ if (value === undefined) delete process.env[name];
133
+ else process.env[name] = value;
134
+ }
135
+ }
136
+ });
137
+
138
+ test("agent bash reads current shell settings for every execution", async () => {
139
+ let commandPrefix = "export PI_WEB_PREFIX=first";
140
+ const extension = createProjectCommandBashExtension({
141
+ cwd: process.cwd(),
142
+ settings: {
143
+ getShellCommandPrefix: () => commandPrefix,
144
+ getShellPath: () => undefined,
145
+ },
146
+ });
147
+ let registeredTool;
148
+ await extension.factory({
149
+ registerTool(tool) {
150
+ registeredTool = tool;
151
+ },
152
+ });
153
+ const execute = () => registeredTool.execute(
154
+ "settings-reload",
155
+ { command: "printf %s \"$PI_WEB_PREFIX\"" },
156
+ undefined,
157
+ undefined,
158
+ undefined,
159
+ );
160
+
161
+ assert.equal((await execute()).content[0].text, "first");
162
+ commandPrefix = "export PI_WEB_PREFIX=second";
163
+ assert.equal((await execute()).content[0].text, "second");
164
+ });
165
+
166
+ test("direct bash removes host variables and allows explicit project values", async () => {
167
+ const agentBinDir = join(process.cwd(), ".test-agent", "bin");
168
+ const operations = createProjectCommandBashOperations({
169
+ agentBinDir,
170
+ baseEnvironment: {
171
+ ...HOST_ENVIRONMENT,
172
+ PATH: process.env.PATH,
173
+ PI_SESSION_ID: "stale-host-value",
174
+ },
175
+ localOperations: createLocalBashOperations(),
176
+ });
177
+ let output = "";
178
+
179
+ await operations.exec(
180
+ `NODE_ENV=test PORT=3200 node -e 'console.log(JSON.stringify({PORT:process.env.PORT,NODE_ENV:process.env.NODE_ENV,NEXT_RUNTIME:process.env.NEXT_RUNTIME,PI_USER_SETTING:process.env.PI_USER_SETTING,PATH:process.env.PATH}))'`,
181
+ process.cwd(),
182
+ { onData: (chunk) => { output += chunk.toString(); } },
183
+ );
184
+ const childEnvironment = JSON.parse(output);
185
+
186
+ assert.equal(childEnvironment.PORT, "3200");
187
+ assert.equal(childEnvironment.NODE_ENV, "test");
188
+ assert.equal(childEnvironment.NEXT_RUNTIME, undefined);
189
+ assert.equal(childEnvironment.PI_USER_SETTING, "preserved");
190
+ assert.ok(childEnvironment.PATH.split(delimiter).includes(agentBinDir));
191
+ });
192
+
193
+ test("direct bash preserves execution controls and streaming callbacks", async () => {
194
+ const signal = new AbortController().signal;
195
+ let received;
196
+ let streamed = "";
197
+ const operations = createProjectCommandBashOperations({
198
+ baseEnvironment: HOST_ENVIRONMENT,
199
+ localOperations: {
200
+ async exec(command, cwd, options) {
201
+ received = { command, cwd, options };
202
+ options.onData(Buffer.from("streamed"));
203
+ return { exitCode: 0 };
204
+ },
205
+ },
206
+ });
207
+
208
+ await operations.exec("echo ready", "/project", {
209
+ onData: (chunk) => { streamed += chunk.toString(); },
210
+ signal,
211
+ timeout: 12,
212
+ });
213
+
214
+ assert.equal(received.command, "echo ready");
215
+ assert.equal(received.cwd, "/project");
216
+ assert.equal(received.options.signal, signal);
217
+ assert.equal(received.options.timeout, 12);
218
+ assert.equal(streamed, "streamed");
219
+ });
220
+
221
+ async function captureOperationEnvironment(options) {
222
+ let environment;
223
+ const operations = createProjectCommandBashOperations({
224
+ ...options,
225
+ localOperations: {
226
+ async exec(_command, _cwd, executionOptions) {
227
+ environment = executionOptions.env;
228
+ return { exitCode: 0 };
229
+ },
230
+ },
231
+ });
232
+ await operations.exec("echo ready", "/project", {
233
+ onData() {},
234
+ });
235
+ return environment;
236
+ }
237
+
238
+ test("direct bash updates the platform PATH key", async () => {
239
+ const agentBinDir = join(process.cwd(), ".test-agent", "bin");
240
+ const cases = [
241
+ {
242
+ options: { agentBinDir, baseEnvironment: { Path: "project-metadata", PATH: "/usr/bin" }, platform: "linux" },
243
+ expected: { Path: "project-metadata", PATH: `${agentBinDir}${delimiter}/usr/bin` },
244
+ },
245
+ {
246
+ options: { agentBinDir: "C:\\pi-agent\\bin", baseEnvironment: { Path: "C:\\Windows" }, platform: "win32" },
247
+ expected: { Path: "C:\\pi-agent\\bin;C:\\Windows" },
248
+ },
249
+ ];
250
+
251
+ for (const { options, expected } of cases) {
252
+ assert.deepEqual(await captureOperationEnvironment(options), expected);
253
+ }
254
+ });
255
+
256
+ test("a user extension keeps priority over the Pi Web fallback bash tool", async () => {
257
+ const userBash = {
258
+ name: "user-bash",
259
+ factory: (pi) => {
260
+ pi.registerTool({
261
+ name: "bash",
262
+ label: "user bash",
263
+ description: "user override",
264
+ parameters: { type: "object", properties: {} },
265
+ async execute() {
266
+ return { content: [{ type: "text", text: "user override" }], details: undefined };
267
+ },
268
+ });
269
+ },
270
+ };
271
+ const hostBash = createProjectCommandBashExtension({
272
+ cwd: process.cwd(),
273
+ settings: {
274
+ getShellCommandPrefix: () => undefined,
275
+ getShellPath: () => undefined,
276
+ },
277
+ });
278
+ const loader = new DefaultResourceLoader({
279
+ cwd: process.cwd(),
280
+ agentDir: join(process.cwd(), ".test-agent"),
281
+ extensionFactories: [userBash, hostBash],
282
+ noExtensions: true,
283
+ noSkills: true,
284
+ noPromptTemplates: true,
285
+ noThemes: true,
286
+ noContextFiles: true,
287
+ extensionsOverride: (base) => preferUserBashExtension(base),
288
+ });
289
+ await loader.reload();
290
+ const extensions = loader.getExtensions();
291
+ const bashDefinitions = extensions.extensions
292
+ .map((extension) => extension.tools.get("bash")?.definition)
293
+ .filter(Boolean);
294
+
295
+ assert.equal(bashDefinitions.length, 1);
296
+ assert.equal(bashDefinitions[0].description, "user override");
297
+ assert.deepEqual(extensions.errors, []);
298
+ });
@@ -0,0 +1,129 @@
1
+ import {
2
+ createBashToolDefinition,
3
+ createLocalBashOperations,
4
+ getAgentDir,
5
+ type BashOperations,
6
+ type InlineExtension,
7
+ type LoadExtensionsResult,
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import { join } from "node:path";
10
+
11
+ const HOST_EXTENSION_NAME = "pi-web-project-command-environment";
12
+ const HOST_EXTENSION_PATH = `<inline:${HOST_EXTENSION_NAME}>`;
13
+
14
+ type ProjectShellSettings = {
15
+ getShellCommandPrefix(): string | undefined;
16
+ getShellPath(): string | undefined;
17
+ };
18
+
19
+ type ProjectCommandBashOperationsOptions = {
20
+ agentBinDir?: string;
21
+ baseEnvironment?: NodeJS.ProcessEnv;
22
+ localOperations?: BashOperations;
23
+ platform?: NodeJS.Platform;
24
+ shellPath?: string;
25
+ };
26
+
27
+ function isHostRuntimeVariable(name: string, platform: NodeJS.Platform): boolean {
28
+ const comparableName = platform === "win32" ? name.toUpperCase() : name;
29
+ return comparableName === "PORT"
30
+ || comparableName === "NODE_ENV"
31
+ || comparableName.startsWith("NEXT_");
32
+ }
33
+
34
+ export function sanitizeProjectCommandEnvironment(
35
+ baseEnvironment: NodeJS.ProcessEnv,
36
+ platform: NodeJS.Platform = process.platform,
37
+ ): NodeJS.ProcessEnv {
38
+ const environment = { ...baseEnvironment };
39
+ for (const name of Object.keys(environment)) {
40
+ if (isHostRuntimeVariable(name, platform)) delete environment[name];
41
+ }
42
+ return environment;
43
+ }
44
+
45
+ function withAgentBinDirectory(
46
+ environment: NodeJS.ProcessEnv,
47
+ agentBinDir: string,
48
+ platform: NodeJS.Platform,
49
+ ): NodeJS.ProcessEnv {
50
+ const pathKey = platform === "win32"
51
+ ? Object.keys(environment).find((name) => name.toUpperCase() === "PATH") ?? "PATH"
52
+ : "PATH";
53
+ const pathDelimiter = platform === "win32" ? ";" : ":";
54
+ const currentPath = environment[pathKey] ?? "";
55
+ const pathEntries = currentPath.split(pathDelimiter).filter(Boolean);
56
+ if (!pathEntries.includes(agentBinDir)) {
57
+ environment[pathKey] = [agentBinDir, currentPath].filter(Boolean).join(pathDelimiter);
58
+ }
59
+ return environment;
60
+ }
61
+
62
+ export function createProjectCommandBashOperations(
63
+ options: ProjectCommandBashOperationsOptions = {},
64
+ ): BashOperations {
65
+ const {
66
+ agentBinDir = join(getAgentDir(), "bin"),
67
+ baseEnvironment = process.env,
68
+ localOperations = createLocalBashOperations({ shellPath: options.shellPath }),
69
+ platform = process.platform,
70
+ } = options;
71
+
72
+ return {
73
+ exec(command, cwd, executionOptions) {
74
+ const environment = withAgentBinDirectory(
75
+ sanitizeProjectCommandEnvironment(executionOptions.env ?? baseEnvironment, platform),
76
+ agentBinDir,
77
+ platform,
78
+ );
79
+ return localOperations.exec(command, cwd, {
80
+ ...executionOptions,
81
+ env: environment,
82
+ });
83
+ },
84
+ };
85
+ }
86
+
87
+ export function createProjectCommandBashExtension(options: {
88
+ cwd: string;
89
+ settings: ProjectShellSettings;
90
+ }): InlineExtension {
91
+ return {
92
+ name: HOST_EXTENSION_NAME,
93
+ hidden: true,
94
+ factory: (pi) => {
95
+ const displayDefinition = createBashToolDefinition(options.cwd);
96
+ pi.registerTool({
97
+ ...displayDefinition,
98
+ execute(toolCallId, params, signal, onUpdate, context) {
99
+ const executionDefinition = createBashToolDefinition(options.cwd, {
100
+ commandPrefix: options.settings.getShellCommandPrefix(),
101
+ operations: createProjectCommandBashOperations({
102
+ shellPath: options.settings.getShellPath(),
103
+ }),
104
+ });
105
+ return executionDefinition.execute(toolCallId, params, signal, onUpdate, context);
106
+ },
107
+ });
108
+ },
109
+ };
110
+ }
111
+
112
+ export function preferUserBashExtension(base: LoadExtensionsResult): LoadExtensionsResult {
113
+ const hostExtensionIndex = base.extensions.findIndex((extension) => extension.path === HOST_EXTENSION_PATH);
114
+ if (hostExtensionIndex < 0) return base;
115
+
116
+ const userBashOwner = base.extensions
117
+ .slice(0, hostExtensionIndex)
118
+ .find((extension) => extension.tools.has("bash"));
119
+ if (!userBashOwner) return base;
120
+
121
+ return {
122
+ ...base,
123
+ extensions: base.extensions.filter((_, index) => index !== hostExtensionIndex),
124
+ errors: base.errors.filter((error) => !(
125
+ error.path === HOST_EXTENSION_PATH
126
+ && error.error === `Tool "bash" conflicts with ${userBashOwner.path}`
127
+ )),
128
+ };
129
+ }
@@ -0,0 +1,60 @@
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 { projectIdentityKey } = await jiti.import("./project-identity.ts");
7
+ const {
8
+ getProjectActivity,
9
+ getRecentProjects,
10
+ sessionsForProject,
11
+ } = await jiti.import("./project-groups.ts");
12
+
13
+ function session(id, projectRoot, modified) {
14
+ return {
15
+ id,
16
+ path: `${id}.jsonl`,
17
+ cwd: projectRoot,
18
+ projectRoot,
19
+ projectKey: projectIdentityKey(projectRoot, "win32"),
20
+ created: modified,
21
+ modified,
22
+ messageCount: 1,
23
+ firstMessage: id,
24
+ };
25
+ }
26
+
27
+ test("Windows path variants form one recent project using the newest display path", () => {
28
+ const older = session("older", "C:\\Users\\Alex\\Project\\Study\\ELM", "2026-08-12T00:00:00.000Z");
29
+ const newer = session("newer", "c:/users/ALEX/project/study/elm", "2026-08-13T00:00:00.000Z");
30
+
31
+ assert.deepEqual(getRecentProjects([older, newer]), [{
32
+ key: older.projectKey,
33
+ root: newer.projectRoot,
34
+ }]);
35
+ });
36
+
37
+ test("project filtering includes every session with the stable identity", () => {
38
+ const first = session("first", "C:\\Users\\Alex\\Project", "2026-08-12T00:00:00.000Z");
39
+ const second = session("second", "c:/users/alex/project/", "2026-08-13T00:00:00.000Z");
40
+ const other = session("other", "D:\\Elsewhere", "2026-08-13T01:00:00.000Z");
41
+
42
+ assert.deepEqual(
43
+ sessionsForProject([first, second, other], first.projectKey).map((item) => item.id),
44
+ ["first", "second"],
45
+ );
46
+ });
47
+
48
+ test("running and unread counts aggregate under the stable project identity", () => {
49
+ const first = session("first", "C:\\Users\\Alex\\Project", "2026-08-12T00:00:00.000Z");
50
+ const second = session("second", "c:/users/alex/project/", "2026-08-13T00:00:00.000Z");
51
+
52
+ const activity = getProjectActivity(
53
+ [first, second],
54
+ new Set(["first", "second"]),
55
+ new Set(["second"]),
56
+ );
57
+
58
+ assert.deepEqual(activity.get(first.projectKey), { running: 2, unread: 1 });
59
+ assert.equal(activity.size, 1);
60
+ });
@@ -0,0 +1,53 @@
1
+ import type { SessionInfo } from "./types";
2
+ import { workspaceKeyOf } from "./workspace-memory";
3
+
4
+ export interface RecentProject {
5
+ /** Stable server-provided identity used for comparison and Map keys. */
6
+ key: string;
7
+ /** Original project path used for display and filesystem operations. */
8
+ root: string;
9
+ }
10
+
11
+ /** Projects sorted by most recent activity and deduplicated by stable key. */
12
+ export function getRecentProjects(sessions: readonly SessionInfo[]): RecentProject[] {
13
+ const latestByProject = new Map<string, { root: string; modified: string }>();
14
+ for (const session of sessions) {
15
+ const root = session.projectRoot ?? session.cwd;
16
+ if (!root) continue;
17
+ const key = workspaceKeyOf(session);
18
+ const previous = latestByProject.get(key);
19
+ if (!previous || session.modified > previous.modified) {
20
+ latestByProject.set(key, { root, modified: session.modified });
21
+ }
22
+ }
23
+ return [...latestByProject.entries()]
24
+ .sort((a, b) => b[1].modified.localeCompare(a[1].modified))
25
+ .map(([key, { root }]) => ({ key, root }));
26
+ }
27
+
28
+ export function getProjectActivity(
29
+ sessions: readonly SessionInfo[],
30
+ runningSessionIds: ReadonlySet<string>,
31
+ unreadSessionIds: ReadonlySet<string>,
32
+ ): Map<string, { running: number; unread: number }> {
33
+ const counts = new Map<string, { running: number; unread: number }>();
34
+ for (const session of sessions) {
35
+ const key = workspaceKeyOf(session);
36
+ if (!key) continue;
37
+ let entry = counts.get(key);
38
+ if (!entry) {
39
+ entry = { running: 0, unread: 0 };
40
+ counts.set(key, entry);
41
+ }
42
+ if (runningSessionIds.has(session.id)) entry.running++;
43
+ if (unreadSessionIds.has(session.id)) entry.unread++;
44
+ }
45
+ return counts;
46
+ }
47
+
48
+ export function sessionsForProject(
49
+ sessions: readonly SessionInfo[],
50
+ projectKey: string,
51
+ ): SessionInfo[] {
52
+ return sessions.filter((session) => workspaceKeyOf(session) === projectKey);
53
+ }
@@ -0,0 +1,37 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ async function loadSubject() {
5
+ const { createJiti } = await import("jiti");
6
+ return createJiti(import.meta.url).import("./project-identity.ts");
7
+ }
8
+
9
+ test("Windows project identity ignores full-path casing and separator style", async () => {
10
+ const { projectIdentityKey } = await loadSubject();
11
+
12
+ const expected = projectIdentityKey("C:\\Users\\Alex\\Project\\Study\\ELM", "win32");
13
+ assert.equal(projectIdentityKey("c:/users/ALEX/project/study/elm", "win32"), expected);
14
+ assert.equal(projectIdentityKey("c:\\Users\\Alex\\Project\\Study\\.\\ELM\\", "win32"), expected);
15
+ });
16
+
17
+ test("Windows project identity handles UNC casing and separators", async () => {
18
+ const { projectIdentityKey } = await loadSubject();
19
+
20
+ assert.equal(
21
+ projectIdentityKey("\\\\Server\\Share\\Team\\Agent", "win32"),
22
+ projectIdentityKey("//server/share/team/AGENT/", "win32"),
23
+ );
24
+ });
25
+
26
+ test("project identity preserves case on case-sensitive platforms", async () => {
27
+ const { projectIdentityKey } = await loadSubject();
28
+
29
+ assert.notEqual(
30
+ projectIdentityKey("/Users/Alex/Project", "linux"),
31
+ projectIdentityKey("/users/alex/project", "linux"),
32
+ );
33
+ assert.notEqual(
34
+ projectIdentityKey("/a\\b", "linux"),
35
+ projectIdentityKey("/a/b", "linux"),
36
+ );
37
+ });
@@ -0,0 +1,26 @@
1
+ import path from "node:path";
2
+
3
+ /**
4
+ * Stable, internal identity for a project path.
5
+ *
6
+ * Keep the original cwd/projectRoot for display and filesystem operations.
7
+ * This key is only for grouping and equality: Windows paths are normalized
8
+ * with win32 rules and case-folded because the default Windows filesystem is
9
+ * case-insensitive. The explicit platform argument keeps those semantics
10
+ * testable on non-Windows CI.
11
+ */
12
+ export function projectIdentityKey(
13
+ projectRoot: string,
14
+ platform: NodeJS.Platform = process.platform,
15
+ ): string {
16
+ if (!projectRoot) return projectRoot;
17
+ const pathApi = platform === "win32" ? path.win32 : path.posix;
18
+ const normalized = pathApi.normalize(projectRoot);
19
+ const rootLength = pathApi.parse(normalized).root.length;
20
+ let end = normalized.length;
21
+ while (end > rootLength && normalized[end - 1] === pathApi.sep) end--;
22
+ const withoutTrailingSeparators = normalized.slice(0, end);
23
+ return platform === "win32"
24
+ ? withoutTrailingSeparators.toLowerCase()
25
+ : withoutTrailingSeparators;
26
+ }