@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,96 @@
1
+ /**
2
+ * 会话看板类型定义(客户端/服务端共用,SDK-free)。
3
+ * 只存布局与状态标记 —— 会话内容仍由 .jsonl 与 /api/sessions 提供。
4
+ */
5
+
6
+ /** 系统看板固定 id:运行中(跨项目自动聚合,不可改名/删除)。 */
7
+ export const SYSTEM_RUNNING_BOARD_ID = "__running__";
8
+
9
+ export interface BoardInfo {
10
+ id: string;
11
+ projectKey: string;
12
+ name: string;
13
+ isSystem: boolean;
14
+ /** 任务型看板:非空时即任务 id(看板 id = 任务 id);手动看板为 null */
15
+ taskId: string | null;
16
+ /** 项目内手动排序(系统看板恒置顶) */
17
+ sortOrder: number;
18
+ created: number;
19
+ updated: number;
20
+ /** 节点数(列表展示用,不落库) */
21
+ nodeCount: number;
22
+ }
23
+
24
+ export type BoardNodeKind = "session" | "shape" | "taskcard";
25
+
26
+ export interface BoardNode {
27
+ id: string;
28
+ boardId: string;
29
+ kind: BoardNodeKind;
30
+ /** sessionId(kind=session);失效时 refId 仍保留但 session 不存在 */
31
+ refId: string | null;
32
+ x: number;
33
+ y: number;
34
+ w: number;
35
+ h: number;
36
+ expanded: boolean;
37
+ props: Record<string, unknown>;
38
+ created: number;
39
+ updated: number;
40
+ }
41
+
42
+ export interface BoardEdge {
43
+ id: string;
44
+ boardId: string;
45
+ fromId: string;
46
+ toId: string;
47
+ label: string | null;
48
+ color: string | null;
49
+ dashed: boolean;
50
+ created: number;
51
+ updated: number;
52
+ }
53
+
54
+ export interface BoardView {
55
+ boardId: string;
56
+ cameraX: number;
57
+ cameraY: number;
58
+ cameraZ: number;
59
+ updated: number;
60
+ }
61
+
62
+ /** GET /api/boards/[id]/canvas 响应(整张画布) */
63
+ export interface BoardCanvas {
64
+ board: BoardInfo;
65
+ nodes: BoardNode[];
66
+ edges: BoardEdge[];
67
+ view: BoardView | null;
68
+ }
69
+
70
+ /** 运行中会话细分状态(GET /api/agent/running 扩展) */
71
+ export type RunningPhase = "waiting_model" | "running_tools" | "running_command" | "waiting_input";
72
+
73
+ export interface RunningSessionState {
74
+ phase: RunningPhase;
75
+ /** 当前模型 `provider/modelId`(可能为空) */
76
+ model: string | null;
77
+ /** 本次运行开始时间(ms epoch;未知为 0) */
78
+ startedAt: number;
79
+ }
80
+
81
+ export interface RunningSnapshot {
82
+ runningSessionIds: string[];
83
+ /** 细分状态:只含运行中的会话 id */
84
+ states: Record<string, RunningSessionState>;
85
+ /** 任务卡状态:调度器活跃态卡(running/review/waiting_reply),前端按 boardId 过滤本画布后更新徽章 */
86
+ taskCards: TaskCardRunningState[];
87
+ }
88
+
89
+ /** 任务卡运行状态(轮询器透传给前端,供看板卡片自动刷新 execStatus 徽章) */
90
+ export interface TaskCardRunningState {
91
+ cardId: string;
92
+ boardId: string;
93
+ number: number;
94
+ name: string;
95
+ execStatus: string;
96
+ }
@@ -0,0 +1,17 @@
1
+ /** 看板纯函数工具(SDK-free,浏览器 + node 测试均可用)。 */
2
+
3
+ /**
4
+ * 刚结束卡片 30s 保留判定:phase=just-ended 且 endedAt 超过窗口则移除。
5
+ * @param phase 卡片当前阶段
6
+ * @param endedAt 结束时刻(ms epoch);0 = 未知,不判定
7
+ * @param nowTs 当前时刻(ms epoch)
8
+ * @param windowMs 保留窗口(默认 30s)
9
+ */
10
+ export function shouldRemoveEndedCard(
11
+ phase: string,
12
+ endedAt: number,
13
+ nowTs: number,
14
+ windowMs = 30_000,
15
+ ): boolean {
16
+ return phase === "just-ended" && endedAt > 0 && nowTs - endedAt > windowMs;
17
+ }
@@ -0,0 +1,54 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ async function loadSubject() {
5
+ return import("./bounded-form-data.ts");
6
+ }
7
+
8
+ function multipartBody(boundary, value) {
9
+ return `--${boundary}\r\nContent-Disposition: form-data; name="value"\r\n\r\n${value}\r\n--${boundary}--\r\n`;
10
+ }
11
+
12
+ test("rejects a declared oversized request before reading its body", async () => {
13
+ const { parseFormDataWithinLimit, RequestBodyTooLargeError } = await loadSubject();
14
+ const request = new Request("http://localhost/upload", {
15
+ method: "POST",
16
+ headers: { "content-length": "99", "content-type": "multipart/form-data; boundary=test" },
17
+ body: multipartBody("test", "small"),
18
+ });
19
+
20
+ await assert.rejects(() => parseFormDataWithinLimit(request, 10), RequestBodyTooLargeError);
21
+ });
22
+
23
+ test("stops a chunked request once its body exceeds the limit", async () => {
24
+ const { parseFormDataWithinLimit, RequestBodyTooLargeError } = await loadSubject();
25
+ const stream = new ReadableStream({
26
+ start(controller) {
27
+ controller.enqueue(new TextEncoder().encode("1234"));
28
+ controller.enqueue(new TextEncoder().encode("5678"));
29
+ controller.close();
30
+ },
31
+ });
32
+ const request = new Request("http://localhost/upload", {
33
+ method: "POST",
34
+ headers: { "content-type": "multipart/form-data; boundary=test" },
35
+ body: stream,
36
+ duplex: "half",
37
+ });
38
+
39
+ await assert.rejects(() => parseFormDataWithinLimit(request, 6), RequestBodyTooLargeError);
40
+ });
41
+
42
+ test("parses multipart data inside the request limit", async () => {
43
+ const { parseFormDataWithinLimit } = await loadSubject();
44
+ const boundary = "test";
45
+ const body = multipartBody(boundary, "small");
46
+ const request = new Request("http://localhost/upload", {
47
+ method: "POST",
48
+ headers: { "content-type": `multipart/form-data; boundary=${boundary}` },
49
+ body,
50
+ });
51
+
52
+ const formData = await parseFormDataWithinLimit(request, body.length + 1);
53
+ assert.equal(formData.get("value"), "small");
54
+ });
@@ -0,0 +1,49 @@
1
+ export class RequestBodyTooLargeError extends Error {
2
+ constructor() {
3
+ super("Request body exceeds the allowed size");
4
+ }
5
+ }
6
+
7
+ function declaredContentLength(request: Request): number | null {
8
+ const value = request.headers.get("content-length");
9
+ if (!value || !/^\d+$/.test(value)) return null;
10
+ const length = Number(value);
11
+ return Number.isSafeInteger(length) ? length : null;
12
+ }
13
+
14
+ /**
15
+ * Parse multipart data only after constraining the complete wire body. This
16
+ * bounds chunked requests too, where Content-Length is unavailable or false.
17
+ */
18
+ export async function parseFormDataWithinLimit(request: Request, maxBytes: number): Promise<FormData> {
19
+ const declared = declaredContentLength(request);
20
+ if (declared !== null && declared > maxBytes) {
21
+ throw new RequestBodyTooLargeError();
22
+ }
23
+
24
+ const reader = request.body?.getReader();
25
+ if (!reader) return request.formData();
26
+
27
+ const chunks: BlobPart[] = [];
28
+ let size = 0;
29
+ try {
30
+ while (true) {
31
+ const { done, value } = await reader.read();
32
+ if (done) break;
33
+ if (size + value.byteLength > maxBytes) {
34
+ await reader.cancel().catch(() => {});
35
+ throw new RequestBodyTooLargeError();
36
+ }
37
+ size += value.byteLength;
38
+ const chunk = new Uint8Array(value.byteLength);
39
+ chunk.set(value);
40
+ chunks.push(chunk);
41
+ }
42
+ } finally {
43
+ reader.releaseLock();
44
+ }
45
+
46
+ const contentType = request.headers.get("content-type");
47
+ const headers = contentType ? { "content-type": contentType } : undefined;
48
+ return new Response(new Blob(chunks), { headers }).formData();
49
+ }
@@ -0,0 +1,167 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ async function loadSubject() {
5
+ return import("./browser-notifications.ts");
6
+ }
7
+
8
+ test("uses a service worker notification when a registration is available", async () => {
9
+ const { showBrowserNotification } = await loadSubject();
10
+ const shown = [];
11
+ let constructorCalled = false;
12
+
13
+ const delivery = await showBrowserNotification({
14
+ title: "Session complete",
15
+ body: "Task finished.",
16
+ sessionUrl: "/?session=session-1",
17
+ tag: "session-complete:session-1",
18
+ onClick: () => assert.fail("service worker owns the click handler"),
19
+ }, {
20
+ createWindowNotification: () => {
21
+ constructorCalled = true;
22
+ throw new Error("unexpected constructor call");
23
+ },
24
+ getServiceWorkerRegistration: async () => ({
25
+ showNotification: async (title, options) => shown.push({ title, options }),
26
+ }),
27
+ });
28
+
29
+ assert.equal(delivery, "service-worker");
30
+ assert.equal(constructorCalled, false);
31
+ assert.deepEqual(shown, [{
32
+ title: "Session complete",
33
+ options: {
34
+ body: "Task finished.",
35
+ tag: "session-complete:session-1",
36
+ data: { url: "/?session=session-1" },
37
+ },
38
+ }]);
39
+ });
40
+
41
+ test("falls back to a page notification and wires its click handler", async () => {
42
+ const { showBrowserNotification } = await loadSubject();
43
+ let notificationOptions;
44
+ let clicked = false;
45
+ let closed = false;
46
+ const notification = {
47
+ onclick: null,
48
+ close: () => { closed = true; },
49
+ };
50
+
51
+ const delivery = await showBrowserNotification({
52
+ title: "Session complete",
53
+ body: "Task finished.",
54
+ sessionUrl: "/?session=session-1",
55
+ onClick: () => { clicked = true; },
56
+ }, {
57
+ createWindowNotification: (title, options) => {
58
+ notificationOptions = { title, options };
59
+ return notification;
60
+ },
61
+ getServiceWorkerRegistration: async () => {
62
+ throw new Error("service worker unavailable");
63
+ },
64
+ });
65
+
66
+ assert.equal(delivery, "window");
67
+ assert.deepEqual(notificationOptions, {
68
+ title: "Session complete",
69
+ options: { body: "Task finished." },
70
+ });
71
+
72
+ notification.onclick();
73
+ assert.equal(closed, true);
74
+ assert.equal(clicked, true);
75
+ });
76
+
77
+ test("silently skips notification when neither delivery mechanism works", async () => {
78
+ const { showBrowserNotification } = await loadSubject();
79
+
80
+ const delivery = await showBrowserNotification({
81
+ title: "Session complete",
82
+ body: "Task finished.",
83
+ sessionUrl: "/",
84
+ onClick: () => {},
85
+ }, {
86
+ createWindowNotification: () => {
87
+ throw new TypeError("Illegal constructor");
88
+ },
89
+ getServiceWorkerRegistration: null,
90
+ });
91
+
92
+ assert.equal(delivery, null);
93
+ });
94
+
95
+ test("shows notifications when the page is hidden", async () => {
96
+ const { shouldShowBrowserNotification } = await loadSubject();
97
+ let focusChecked = false;
98
+
99
+ assert.equal(shouldShowBrowserNotification({
100
+ visibilityState: "hidden",
101
+ hasFocus: () => {
102
+ focusChecked = true;
103
+ return true;
104
+ },
105
+ }), true);
106
+ assert.equal(focusChecked, false);
107
+ });
108
+
109
+ test("shows notifications when the visible page is unfocused", async () => {
110
+ const { shouldShowBrowserNotification } = await loadSubject();
111
+
112
+ assert.equal(shouldShowBrowserNotification({
113
+ visibilityState: "visible",
114
+ hasFocus: () => false,
115
+ }), true);
116
+ });
117
+
118
+ test("skips notifications only when the visible page is focused", async () => {
119
+ const { shouldShowBrowserNotification } = await loadSubject();
120
+
121
+ assert.equal(shouldShowBrowserNotification({
122
+ visibilityState: "visible",
123
+ hasFocus: () => true,
124
+ }), false);
125
+ });
126
+
127
+ test("claims only blocking extension requests and deduplicates their ids", async () => {
128
+ const { claimExtensionAttentionNotification } = await loadSubject();
129
+ const notifiedIds = new Set();
130
+ const confirmRequest = {
131
+ type: "extension_ui_request",
132
+ id: "confirm-1",
133
+ method: "confirm",
134
+ title: "Approve",
135
+ message: "Continue?",
136
+ };
137
+
138
+ assert.equal(claimExtensionAttentionNotification(confirmRequest, notifiedIds), true);
139
+ assert.equal(claimExtensionAttentionNotification(confirmRequest, notifiedIds), false);
140
+ for (const request of [
141
+ { type: "extension_ui_request", id: "select-1", method: "select", title: "Choose", options: ["A"] },
142
+ { type: "extension_ui_request", id: "input-1", method: "input", title: "Enter value" },
143
+ { type: "extension_ui_request", id: "editor-1", method: "editor", title: "Edit value" },
144
+ ]) {
145
+ assert.equal(claimExtensionAttentionNotification(request, notifiedIds), true);
146
+ }
147
+ assert.equal(claimExtensionAttentionNotification({
148
+ type: "extension_ui_request",
149
+ id: "notice-1",
150
+ method: "notify",
151
+ message: "Informational",
152
+ }, notifiedIds), false);
153
+ assert.equal(claimExtensionAttentionNotification({
154
+ type: "extension_ui_request",
155
+ id: "custom-closed",
156
+ method: "custom",
157
+ lines: [],
158
+ closed: true,
159
+ }, notifiedIds), false);
160
+ assert.equal(claimExtensionAttentionNotification({
161
+ type: "extension_ui_request",
162
+ id: "custom-open",
163
+ method: "custom",
164
+ lines: ["Waiting for input"],
165
+ }, notifiedIds), true);
166
+ assert.deepEqual([...notifiedIds], ["confirm-1", "select-1", "input-1", "editor-1", "custom-open"]);
167
+ });
@@ -0,0 +1,104 @@
1
+ import type { BlockingExtensionUiRequest, ExtensionUiRequest } from "./types";
2
+
3
+ interface WindowNotificationLike {
4
+ onclick: Notification["onclick"];
5
+ close: () => void;
6
+ }
7
+
8
+ interface ServiceWorkerRegistrationLike {
9
+ showNotification: (title: string, options?: NotificationOptions) => Promise<void>;
10
+ }
11
+
12
+ export interface BrowserNotificationEnvironment {
13
+ createWindowNotification: (title: string, options?: NotificationOptions) => WindowNotificationLike;
14
+ getServiceWorkerRegistration: (() => Promise<ServiceWorkerRegistrationLike | undefined>) | null;
15
+ }
16
+
17
+ export interface BrowserNotificationOptions {
18
+ title: string;
19
+ body: string;
20
+ sessionUrl: string;
21
+ onClick: () => void;
22
+ tag?: string;
23
+ }
24
+
25
+ export type NotificationDelivery = "service-worker" | "window" | null;
26
+
27
+ type DocumentAttentionState = Pick<Document, "visibilityState" | "hasFocus">;
28
+
29
+ export function shouldShowBrowserNotification(
30
+ attentionState: DocumentAttentionState = document,
31
+ ): boolean {
32
+ return attentionState.visibilityState !== "visible" || !attentionState.hasFocus();
33
+ }
34
+
35
+ export function isBlockingExtensionUiRequest(
36
+ request: ExtensionUiRequest,
37
+ ): request is BlockingExtensionUiRequest {
38
+ switch (request.method) {
39
+ case "select":
40
+ case "confirm":
41
+ case "input":
42
+ case "editor":
43
+ return true;
44
+ case "custom":
45
+ return request.closed !== true;
46
+ default:
47
+ return false;
48
+ }
49
+ }
50
+
51
+ export function claimExtensionAttentionNotification(
52
+ request: ExtensionUiRequest,
53
+ notifiedRequestIds: Set<string>,
54
+ ): request is BlockingExtensionUiRequest {
55
+ if (!isBlockingExtensionUiRequest(request) || notifiedRequestIds.has(request.id)) return false;
56
+ notifiedRequestIds.add(request.id);
57
+ return true;
58
+ }
59
+
60
+ function getBrowserEnvironment(): BrowserNotificationEnvironment {
61
+ return {
62
+ createWindowNotification: (title, options) => new Notification(title, options),
63
+ getServiceWorkerRegistration: "serviceWorker" in navigator
64
+ ? () => navigator.serviceWorker.getRegistration()
65
+ : null,
66
+ };
67
+ }
68
+
69
+ export async function showBrowserNotification(
70
+ options: BrowserNotificationOptions,
71
+ environment: BrowserNotificationEnvironment = getBrowserEnvironment(),
72
+ ): Promise<NotificationDelivery> {
73
+ const notificationOptions: NotificationOptions = {
74
+ body: options.body,
75
+ ...(options.tag ? { tag: options.tag } : {}),
76
+ };
77
+
78
+ if (environment.getServiceWorkerRegistration) {
79
+ try {
80
+ const registration = await environment.getServiceWorkerRegistration();
81
+ if (registration) {
82
+ await registration.showNotification(options.title, {
83
+ ...notificationOptions,
84
+ data: { url: options.sessionUrl },
85
+ });
86
+ return "service-worker";
87
+ }
88
+ } catch {
89
+ // Fall back to a page notification where the constructor is supported.
90
+ }
91
+ }
92
+
93
+ try {
94
+ const notification = environment.createWindowNotification(options.title, notificationOptions);
95
+ notification.onclick = () => {
96
+ notification.close();
97
+ options.onClick();
98
+ };
99
+ return "window";
100
+ } catch {
101
+ // Most mobile browsers expose Notification but require service-worker delivery.
102
+ return null;
103
+ }
104
+ }
@@ -0,0 +1,140 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ async function loadSubject() {
5
+ return import("./chat-lazy-load.ts");
6
+ }
7
+
8
+ test("shows only the last visible render items", async () => {
9
+ const { getVisibleRenderWindow } = await loadSubject();
10
+ assert.deepEqual(getVisibleRenderWindow(200, 50), { startIndex: 150, hasMore: true });
11
+ });
12
+
13
+ test("shows all render items when the visible count reaches the total", async () => {
14
+ const { getVisibleRenderWindow } = await loadSubject();
15
+ assert.deepEqual(getVisibleRenderWindow(30, 50), { startIndex: 0, hasMore: false });
16
+ assert.deepEqual(getVisibleRenderWindow(50, 50), { startIndex: 0, hasMore: false });
17
+ assert.deepEqual(getVisibleRenderWindow(0, 50), { startIndex: 0, hasMore: false });
18
+ });
19
+
20
+ test("continues paging when render items outnumber source messages", async () => {
21
+ const { getNextVisibleCount, getVisibleRenderWindow } = await loadSubject();
22
+ let visibleCount = 50;
23
+
24
+ visibleCount = getNextVisibleCount(visibleCount);
25
+ assert.deepEqual(getVisibleRenderWindow(120, visibleCount), { startIndex: 20, hasMore: true });
26
+
27
+ visibleCount = getNextVisibleCount(visibleCount);
28
+ assert.deepEqual(getVisibleRenderWindow(120, visibleCount), { startIndex: 0, hasMore: false });
29
+ });
30
+
31
+ test("restores the viewport after prepending content", async () => {
32
+ const { captureScrollDistance, restoreScrollTop } = await loadSubject();
33
+ const savedDistance = captureScrollDistance(2000, 500);
34
+
35
+ assert.equal(savedDistance, 1500);
36
+ assert.equal(restoreScrollTop(2500, savedDistance), 1000);
37
+ });
38
+
39
+ test("restores top and bottom boundary positions", async () => {
40
+ const { captureScrollDistance, restoreScrollTop } = await loadSubject();
41
+ assert.equal(restoreScrollTop(3000, captureScrollDistance(2000, 0)), 1000);
42
+ assert.equal(restoreScrollTop(3000, captureScrollDistance(2000, 2000)), 3000);
43
+ });
44
+
45
+ test("treats only the real message tail as live-follow attached", async () => {
46
+ const {
47
+ CHAT_SCROLL_REATTACH_TOLERANCE,
48
+ CHAT_SCROLL_TAIL_TOLERANCE,
49
+ getLiveFollowAttached,
50
+ isScrollAtTail,
51
+ } = await loadSubject();
52
+
53
+ assert.equal(CHAT_SCROLL_TAIL_TOLERANCE, 8);
54
+ assert.equal(CHAT_SCROLL_REATTACH_TOLERANCE, 96);
55
+ assert.deepEqual(
56
+ [392, 391.99, 400].map((scrollTop) => isScrollAtTail(scrollTop, 600, 1000)),
57
+ [true, false, true],
58
+ );
59
+ assert.equal(isScrollAtTail(0, 600, 400), true);
60
+
61
+ // Layout growth alone must not detach a view that was already following.
62
+ assert.equal(getLiveFollowAttached(true, 400, 400, 600, 1040), true);
63
+ // Any upward movement outside the strict tail tolerance detaches, even inside
64
+ // the wider downward-only reattach window.
65
+ assert.equal(getLiveFollowAttached(true, 400, 380, 600, 1000), false);
66
+ assert.equal(getLiveFollowAttached(false, 380, 370, 600, 1000), false);
67
+ // A detached view stays detached until downward movement enters the reattach window.
68
+ assert.equal(getLiveFollowAttached(false, 280, 303, 600, 1000), false);
69
+ assert.equal(getLiveFollowAttached(false, 303, 304, 600, 1000), true);
70
+ // Reaching the old tail still reattaches after one to four new rendered lines.
71
+ for (const lines of [1, 2, 3, 4]) {
72
+ assert.equal(getLiveFollowAttached(false, 380, 400, 600, 1000 + lines * 24), true);
73
+ }
74
+ assert.equal(getLiveFollowAttached(false, 380, 400, 600, 1120), false);
75
+ // Streaming growth without downward user movement must not reattach.
76
+ assert.equal(getLiveFollowAttached(false, 400, 400, 600, 1096), false);
77
+ assert.equal(getLiveFollowAttached(false, 391, 392, 600, 1000), true);
78
+ });
79
+
80
+ test("prompt anchor spacer uses the rendered content end", async () => {
81
+ const { getPromptAnchorSpacerHeight } = await loadSubject();
82
+ const initial = getPromptAnchorSpacerHeight(400, 700, 600);
83
+ const afterRender = getPromptAnchorSpacerHeight(400, 700, 600);
84
+
85
+ assert.equal(initial, 300);
86
+ assert.equal(afterRender, initial);
87
+ });
88
+
89
+ test("prompt anchor spacer includes the viewport deficit for short conversations", async () => {
90
+ const { getPromptAnchorSpacerHeight } = await loadSubject();
91
+ const targetTop = 168;
92
+ const clientHeight = 579;
93
+
94
+ let contentHeight = 411;
95
+ let spacerHeight = getPromptAnchorSpacerHeight(targetTop, contentHeight, clientHeight);
96
+ assert.equal(spacerHeight, 336);
97
+ assert.equal(contentHeight + spacerHeight - clientHeight, targetTop);
98
+
99
+ contentHeight += 24;
100
+ spacerHeight = getPromptAnchorSpacerHeight(targetTop, contentHeight, clientHeight);
101
+ assert.equal(spacerHeight, 312);
102
+ assert.equal(contentHeight + spacerHeight - clientHeight, targetTop);
103
+ });
104
+
105
+ test("prompt anchor spacer clamps at zero once content can reach the target", async () => {
106
+ const { getPromptAnchorSpacerHeight } = await loadSubject();
107
+ assert.equal(getPromptAnchorSpacerHeight(100, 750, 600), 0);
108
+ assert.equal(getPromptAnchorSpacerHeight(-20, 200, 600), 0);
109
+ });
110
+
111
+ test("prompt anchor spacer rounds fractional layout measurements up", async () => {
112
+ const { getPromptAnchorSpacerHeight } = await loadSubject();
113
+ assert.equal(getPromptAnchorSpacerHeight(400.25, 700, 600), 301);
114
+ });
115
+
116
+ test("streaming content consumes the prompt anchor before advancing the tail", async () => {
117
+ const { getPromptAnchorSpacerHeight } = await loadSubject();
118
+ const targetTop = 400;
119
+ const clientHeight = 600;
120
+
121
+ let contentHeight = 700;
122
+ let spacerHeight = getPromptAnchorSpacerHeight(targetTop, contentHeight, clientHeight);
123
+ assert.equal(spacerHeight, 300);
124
+ assert.equal(contentHeight + spacerHeight - clientHeight, targetTop);
125
+
126
+ contentHeight += 120;
127
+ spacerHeight = getPromptAnchorSpacerHeight(targetTop, contentHeight, clientHeight);
128
+ assert.equal(spacerHeight, 180);
129
+ assert.equal(contentHeight + spacerHeight - clientHeight, targetTop);
130
+
131
+ contentHeight += 180;
132
+ spacerHeight = getPromptAnchorSpacerHeight(targetTop, contentHeight, clientHeight);
133
+ assert.equal(spacerHeight, 0);
134
+ assert.equal(contentHeight - clientHeight, targetTop);
135
+
136
+ contentHeight += 40;
137
+ spacerHeight = getPromptAnchorSpacerHeight(targetTop, contentHeight, clientHeight);
138
+ assert.equal(spacerHeight, 0);
139
+ assert.equal(contentHeight - clientHeight, targetTop + 40);
140
+ });
@@ -0,0 +1,64 @@
1
+ export const VISIBLE_PAGE_SIZE = 50;
2
+ export const CHAT_SCROLL_TAIL_TOLERANCE = 8;
3
+ export const CHAT_SCROLL_REATTACH_TOLERANCE = 96;
4
+
5
+ export function getVisibleRenderWindow(totalCount: number, visibleCount: number): {
6
+ startIndex: number;
7
+ hasMore: boolean;
8
+ } {
9
+ const clampedVisibleCount = Math.min(Math.max(visibleCount, 0), Math.max(totalCount, 0));
10
+ const startIndex = Math.max(0, totalCount - clampedVisibleCount);
11
+ return { startIndex, hasMore: startIndex > 0 };
12
+ }
13
+
14
+ export function getNextVisibleCount(currentVisibleCount: number, pageSize = VISIBLE_PAGE_SIZE): number {
15
+ return currentVisibleCount + pageSize;
16
+ }
17
+
18
+ export function captureScrollDistance(scrollHeight: number, scrollTop: number): number {
19
+ return scrollHeight - scrollTop;
20
+ }
21
+
22
+ export function restoreScrollTop(scrollHeight: number, savedDistance: number): number {
23
+ return Math.max(0, scrollHeight - savedDistance);
24
+ }
25
+
26
+ export function isScrollAtTail(
27
+ scrollTop: number,
28
+ clientHeight: number,
29
+ scrollHeight: number,
30
+ tolerance = CHAT_SCROLL_TAIL_TOLERANCE,
31
+ ): boolean {
32
+ return scrollTop + clientHeight >= scrollHeight - tolerance;
33
+ }
34
+
35
+ export function getLiveFollowAttached(
36
+ wasAttached: boolean,
37
+ previousScrollTop: number,
38
+ scrollTop: number,
39
+ clientHeight: number,
40
+ scrollHeight: number,
41
+ reattachTolerance = CHAT_SCROLL_REATTACH_TOLERANCE,
42
+ ): boolean {
43
+ if (isScrollAtTail(scrollTop, clientHeight, scrollHeight)) return true;
44
+ if (scrollTop < previousScrollTop) return false;
45
+ if (
46
+ !wasAttached
47
+ && scrollTop > previousScrollTop
48
+ && isScrollAtTail(scrollTop, clientHeight, scrollHeight, reattachTolerance)
49
+ ) return true;
50
+ return wasAttached;
51
+ }
52
+
53
+ export function getPromptAnchorSpacerHeight(
54
+ targetTop: number,
55
+ contentEnd: number,
56
+ clientHeight: number,
57
+ ): number {
58
+ const clampedTargetTop = Math.max(0, targetTop);
59
+ if (clampedTargetTop === 0) return 0;
60
+
61
+ return Math.max(0, Math.ceil(
62
+ clampedTargetTop + clientHeight - Math.max(0, contentEnd),
63
+ ));
64
+ }