@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,64 @@
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
+ interopDefault: true,
7
+ moduleCache: false,
8
+ });
9
+ const { AgentCommandError, isPromptRejectedError, sendAgentCommand } = await jiti.import("./agent-client.ts");
10
+
11
+ test("agent command HTTP rejections are distinguishable from transport failures", async (t) => {
12
+ const originalFetch = globalThis.fetch;
13
+ t.after(() => {
14
+ globalThis.fetch = originalFetch;
15
+ });
16
+
17
+ globalThis.fetch = async () => new Response(
18
+ JSON.stringify({
19
+ error: "Authentication failed",
20
+ code: "prompt_rejected",
21
+ accepted: false,
22
+ }),
23
+ { status: 500, headers: { "Content-Type": "application/json" } },
24
+ );
25
+
26
+ await assert.rejects(
27
+ sendAgentCommand("session-id", { type: "prompt", message: "hello" }),
28
+ (error) => {
29
+ assert.equal(error instanceof AgentCommandError, true);
30
+ assert.equal(error.status, 500);
31
+ assert.equal(error.message, "Authentication failed");
32
+ assert.equal(error.code, "prompt_rejected");
33
+ assert.equal(error.accepted, false);
34
+ assert.equal(isPromptRejectedError(error), true);
35
+ return true;
36
+ },
37
+ );
38
+
39
+ const transportError = new TypeError("connection reset");
40
+ globalThis.fetch = async () => {
41
+ throw transportError;
42
+ };
43
+
44
+ await assert.rejects(
45
+ sendAgentCommand("session-id", { type: "prompt", message: "hello" }),
46
+ (error) => {
47
+ assert.equal(error, transportError);
48
+ assert.equal(error instanceof AgentCommandError, false);
49
+ assert.equal(isPromptRejectedError(error), false);
50
+ return true;
51
+ },
52
+ );
53
+ });
54
+
55
+ test("only an explicit negative prompt acknowledgement is definitive", () => {
56
+ assert.equal(
57
+ isPromptRejectedError(new AgentCommandError("proxy failure", 502)),
58
+ false,
59
+ );
60
+ assert.equal(
61
+ isPromptRejectedError(new AgentCommandError("generic API failure", 500, "internal_error", false)),
62
+ false,
63
+ );
64
+ });
@@ -0,0 +1,53 @@
1
+ // Client-side helper for POST /api/agent/[id].
2
+ //
3
+ // Every /api/agent/[id] route returns one of:
4
+ // { success: true, data: <result> }
5
+ // { error: string } (non-2xx)
6
+ //
7
+ // Call sites previously repeated the same 5-line fetch block 13× in
8
+ // hooks/useAgentSession.ts. This helper collapses that down to one line.
9
+
10
+ export class AgentCommandError extends Error {
11
+ constructor(
12
+ message: string,
13
+ public readonly status: number,
14
+ public readonly code?: string,
15
+ public readonly accepted?: boolean,
16
+ ) {
17
+ super(message);
18
+ this.name = "AgentCommandError";
19
+ }
20
+ }
21
+
22
+ export function isPromptRejectedError(error: unknown): error is AgentCommandError {
23
+ return error instanceof AgentCommandError
24
+ && error.code === "prompt_rejected"
25
+ && error.accepted === false;
26
+ }
27
+
28
+ export async function sendAgentCommand<T = unknown>(
29
+ sessionId: string,
30
+ command: Record<string, unknown>,
31
+ ): Promise<T> {
32
+ const res = await fetch(`/api/agent/${encodeURIComponent(sessionId)}`, {
33
+ method: "POST",
34
+ headers: { "Content-Type": "application/json" },
35
+ body: JSON.stringify(command),
36
+ });
37
+ const body = (await res.json().catch(() => ({}))) as {
38
+ success?: boolean;
39
+ data?: T;
40
+ error?: string;
41
+ code?: string;
42
+ accepted?: boolean;
43
+ };
44
+ if (!res.ok || body.error) {
45
+ throw new AgentCommandError(
46
+ body.error ?? `HTTP ${res.status}`,
47
+ res.status,
48
+ body.code,
49
+ body.accepted,
50
+ );
51
+ }
52
+ return body.data as T;
53
+ }
@@ -0,0 +1,193 @@
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 { AgentEventConnection } = await jiti.import("./agent-event-connection.ts");
7
+
8
+ const CONNECTING = 0;
9
+ const OPEN = 1;
10
+ const CLOSED = 2;
11
+
12
+ class FakeEventSource {
13
+ readyState = CONNECTING;
14
+ onmessage = null;
15
+ onerror = null;
16
+ closeCount = 0;
17
+
18
+ open() { this.readyState = OPEN; }
19
+ message(event) { this.onmessage?.({ data: JSON.stringify(event) }); }
20
+ error() {
21
+ this.readyState = CLOSED;
22
+ this.onerror?.(new Event("error"));
23
+ }
24
+ close() {
25
+ this.readyState = CLOSED;
26
+ this.closeCount += 1;
27
+ }
28
+ }
29
+
30
+ function createHarness({ readinessTimeoutMs = 100, reconnectDelayMs = 5 } = {}) {
31
+ const sources = [];
32
+ const events = [];
33
+ const unexpectedErrors = [];
34
+ let activeSession = "session-a";
35
+ let demand = false;
36
+ const connection = new AgentEventConnection({
37
+ createSource(sessionId) {
38
+ const source = new FakeEventSource();
39
+ source.sessionId = sessionId;
40
+ sources.push(source);
41
+ return source;
42
+ },
43
+ onEvent(event) { events.push(event); },
44
+ shouldMaintain: (sessionId) => demand && sessionId === activeSession,
45
+ readinessTimeoutMs,
46
+ reconnectDelayMs,
47
+ onUnexpectedError(error) { unexpectedErrors.push(error); },
48
+ });
49
+ return {
50
+ connection,
51
+ sources,
52
+ events,
53
+ unexpectedErrors,
54
+ setActiveSession(sessionId) { activeSession = sessionId; },
55
+ setDemand(value) { demand = value; },
56
+ };
57
+ }
58
+
59
+ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
60
+ const nextTurn = () => new Promise((resolve) => setImmediate(resolve));
61
+
62
+ test("shares one source and waits past the former transport deadline", async () => {
63
+ const { connection, sources, events } = createHarness();
64
+ const first = connection.ensureConnected("session-a");
65
+ const second = connection.ensureConnected("session-a");
66
+
67
+ assert.equal(sources.length, 1);
68
+ sources[0].open();
69
+ await wait(20);
70
+ sources[0].message({ type: "connected" });
71
+ await Promise.all([first, second]);
72
+
73
+ sources[0].message({ type: "agent_start" });
74
+ assert.deepEqual(events.map((event) => event.type), ["connected", "agent_start"]);
75
+ });
76
+
77
+ test("times out all waiters and closes the stalled source", async () => {
78
+ const { connection, sources } = createHarness({ readinessTimeoutMs: 10 });
79
+ const first = connection.ensureConnected("session-a");
80
+ const second = connection.ensureConnected("session-a");
81
+
82
+ await assert.rejects(first, (error) => error.status === "ready_timeout");
83
+ await assert.rejects(second, (error) => error.status === "ready_timeout");
84
+ assert.equal(sources[0].closeCount, 1);
85
+ });
86
+
87
+ test("manual close rejects waiters and isolates late events", async () => {
88
+ const { connection, sources, events } = createHarness();
89
+ const abandoned = connection.ensureConnected("session-a");
90
+ const oldSource = sources[0];
91
+ connection.close();
92
+ await assert.rejects(abandoned, (error) => error.status === "closed");
93
+
94
+ const replacement = connection.ensureConnected("session-a");
95
+ oldSource.open();
96
+ oldSource.message({ type: "connected" });
97
+ await nextTurn();
98
+ assert.deepEqual(events, []);
99
+
100
+ sources[1].open();
101
+ sources[1].message({ type: "connected" });
102
+ await replacement;
103
+ });
104
+
105
+ test("surfaces startup errors without retrying or dispatching them", async () => {
106
+ const { connection, sources, events } = createHarness();
107
+ const ready = connection.ensureConnected("session-a");
108
+ sources[0].open();
109
+ sources[0].message({ type: "startup_error", errorMessage: "broken config" });
110
+
111
+ await assert.rejects(ready, (error) => (
112
+ error.status === "startup_error" && error.message === "broken config"
113
+ ));
114
+ await wait(10);
115
+ assert.equal(sources.length, 1);
116
+ assert.deepEqual(events, []);
117
+ });
118
+
119
+ test("replaces a previously connected CONNECTING source only once", async () => {
120
+ const { connection, sources } = createHarness();
121
+ const initial = connection.ensureConnected("session-a");
122
+ sources[0].open();
123
+ sources[0].message({ type: "connected" });
124
+ await initial;
125
+
126
+ sources[0].readyState = CONNECTING;
127
+ const first = connection.ensureConnected("session-a");
128
+ const second = connection.ensureConnected("session-a");
129
+ await nextTurn();
130
+ assert.equal(sources.length, 2);
131
+
132
+ sources[1].open();
133
+ sources[1].message({ type: "connected" });
134
+ await Promise.all([first, second]);
135
+ });
136
+
137
+ test("passive maintenance retries a transient failure once", async () => {
138
+ const { connection, sources, setDemand } = createHarness();
139
+ setDemand(true);
140
+ connection.maintain("session-a");
141
+ sources[0].error();
142
+ await wait(10);
143
+ assert.equal(sources.length, 2);
144
+
145
+ sources[1].open();
146
+ sources[1].message({ type: "connected" });
147
+ await wait(10);
148
+ assert.equal(sources.length, 2);
149
+ });
150
+
151
+ test("an explicit startup error cancels a pending passive retry", async () => {
152
+ const { connection, sources, setDemand } = createHarness();
153
+ setDemand(true);
154
+ connection.maintain("session-a");
155
+ sources[0].error();
156
+
157
+ const explicit = connection.ensureConnected("session-a");
158
+ sources[1].open();
159
+ sources[1].message({ type: "startup_error", errorMessage: "terminal" });
160
+ await assert.rejects(explicit, (error) => error.status === "startup_error");
161
+ await wait(10);
162
+ assert.equal(sources.length, 2);
163
+ });
164
+
165
+ test("dropped demand, session switches, and close cannot revive an old retry", async () => {
166
+ const first = createHarness();
167
+ first.setDemand(true);
168
+ first.connection.maintain("session-a");
169
+ first.sources[0].error();
170
+ first.setDemand(false);
171
+ await wait(10);
172
+ assert.equal(first.sources.length, 1);
173
+
174
+ const second = createHarness();
175
+ second.setDemand(true);
176
+ second.connection.maintain("session-a");
177
+ second.sources[0].error();
178
+ second.setActiveSession("session-b");
179
+ const ready = second.connection.ensureConnected("session-b");
180
+ second.sources[1].open();
181
+ second.sources[1].message({ type: "connected" });
182
+ await ready;
183
+ await wait(10);
184
+ assert.equal(second.sources.length, 2);
185
+
186
+ const closed = createHarness();
187
+ closed.setDemand(true);
188
+ closed.connection.maintain("session-a");
189
+ closed.sources[0].error();
190
+ closed.connection.close();
191
+ await wait(10);
192
+ assert.equal(closed.sources.length, 1);
193
+ });
@@ -0,0 +1,202 @@
1
+ import type { AgentEventLike } from "./agent-event-wire";
2
+
3
+ export interface AgentEventSourceLike {
4
+ readonly readyState: number;
5
+ onmessage: ((event: MessageEvent<string>) => void) | null;
6
+ onerror: ((event: Event) => void) | null;
7
+ close(): void;
8
+ }
9
+
10
+ export type AgentEventConnectionStatus = "ready_timeout" | "startup_error" | "closed";
11
+
12
+ export class AgentEventConnectionError extends Error {
13
+ constructor(public readonly status: AgentEventConnectionStatus, message?: string) {
14
+ super(message ?? (
15
+ status === "ready_timeout"
16
+ ? "Timed out starting the agent session. Please try again."
17
+ : "Failed to connect to the agent event stream. Please try again."
18
+ ));
19
+ this.name = "AgentEventConnectionError";
20
+ }
21
+ }
22
+
23
+ type Attempt = {
24
+ promise: Promise<void>;
25
+ ready: boolean;
26
+ succeed(): void;
27
+ fail(error: AgentEventConnectionError): void;
28
+ };
29
+
30
+ type Connection = {
31
+ sessionId: string;
32
+ source: AgentEventSourceLike;
33
+ attempt: Attempt;
34
+ };
35
+
36
+ export interface AgentEventConnectionOptions {
37
+ createSource(sessionId: string): AgentEventSourceLike;
38
+ onEvent(event: AgentEventLike): void;
39
+ shouldMaintain(sessionId: string): boolean;
40
+ readinessTimeoutMs: number;
41
+ reconnectDelayMs: number;
42
+ onUnexpectedError?(error: unknown): void;
43
+ }
44
+
45
+ const EVENT_SOURCE_OPEN = 1;
46
+
47
+ /** Owns the EventSource, agent-readiness handshake, and passive reconnect. */
48
+ export class AgentEventConnection {
49
+ private current: Connection | null = null;
50
+ private retry: { sessionId: string; timer: ReturnType<typeof setTimeout> } | null = null;
51
+ private retryGeneration = 0;
52
+
53
+ constructor(private readonly options: AgentEventConnectionOptions) {}
54
+
55
+ close(): void {
56
+ this.stopRetrying();
57
+ if (this.current) this.discard(this.current, new AgentEventConnectionError("closed"));
58
+ }
59
+
60
+ maintain(sessionId: string): void {
61
+ if (!this.options.shouldMaintain(sessionId)) return;
62
+ const retryGeneration = this.retryGeneration;
63
+ void this.ensureConnected(sessionId).catch((error) => {
64
+ if (retryGeneration !== this.retryGeneration) return;
65
+ if (error instanceof AgentEventConnectionError) {
66
+ if (error.status === "startup_error") this.stopRetrying();
67
+ else this.scheduleRetry(sessionId);
68
+ } else {
69
+ this.options.onUnexpectedError?.(error);
70
+ }
71
+ });
72
+ }
73
+
74
+ async ensureConnected(sessionId: string): Promise<void> {
75
+ while (true) {
76
+ let connection = this.current;
77
+ if (!connection || connection.sessionId !== sessionId) {
78
+ connection = this.open(sessionId);
79
+ } else if (
80
+ connection.attempt.ready
81
+ && connection.source.readyState === EVENT_SOURCE_OPEN
82
+ ) {
83
+ return;
84
+ }
85
+
86
+ await connection.attempt.promise;
87
+ if (this.current !== connection) {
88
+ if (this.current?.sessionId === sessionId) continue;
89
+ throw new AgentEventConnectionError("closed");
90
+ }
91
+ if (connection.source.readyState === EVENT_SOURCE_OPEN) return;
92
+
93
+ // A once-ready EventSource may otherwise remain CONNECTING indefinitely.
94
+ this.discard(connection, new AgentEventConnectionError("closed"));
95
+ }
96
+ }
97
+
98
+ private open(sessionId: string): Connection {
99
+ if (this.current) this.discard(this.current, new AgentEventConnectionError("closed"));
100
+
101
+ let source: AgentEventSourceLike;
102
+ try {
103
+ source = this.options.createSource(sessionId);
104
+ } catch (error) {
105
+ throw new AgentEventConnectionError(
106
+ "closed",
107
+ error instanceof Error ? error.message : String(error),
108
+ );
109
+ }
110
+
111
+ let settled = false;
112
+ let resolve!: () => void;
113
+ let reject!: (error: AgentEventConnectionError) => void;
114
+ const promise = new Promise<void>((resolvePromise, rejectPromise) => {
115
+ resolve = resolvePromise;
116
+ reject = rejectPromise;
117
+ });
118
+ const timeout = setTimeout(() => {
119
+ this.fail(connection, new AgentEventConnectionError("ready_timeout"));
120
+ }, this.options.readinessTimeoutMs);
121
+ const attempt: Attempt = {
122
+ promise,
123
+ ready: false,
124
+ succeed() {
125
+ if (settled) return;
126
+ settled = true;
127
+ clearTimeout(timeout);
128
+ resolve();
129
+ },
130
+ fail(error) {
131
+ if (settled) return;
132
+ settled = true;
133
+ clearTimeout(timeout);
134
+ reject(error);
135
+ },
136
+ };
137
+ const connection: Connection = { sessionId, source, attempt };
138
+ this.current = connection;
139
+
140
+ source.onmessage = (message) => {
141
+ if (this.current !== connection) return;
142
+ let event: AgentEventLike;
143
+ try {
144
+ event = JSON.parse(message.data) as AgentEventLike;
145
+ } catch {
146
+ return;
147
+ }
148
+
149
+ if (event.type === "connected") {
150
+ attempt.ready = true;
151
+ attempt.succeed();
152
+ this.stopRetrying();
153
+ } else if (event.type === "startup_error") {
154
+ const message = typeof event.errorMessage === "string" ? event.errorMessage : undefined;
155
+ this.fail(connection, new AgentEventConnectionError("startup_error", message));
156
+ return;
157
+ }
158
+ this.options.onEvent(event);
159
+ };
160
+ source.onerror = () => {
161
+ this.fail(connection, new AgentEventConnectionError("closed"));
162
+ };
163
+
164
+ return connection;
165
+ }
166
+
167
+ private fail(connection: Connection, error: AgentEventConnectionError): void {
168
+ if (this.current !== connection) return;
169
+ this.discard(connection, error);
170
+ if (error.status === "startup_error") this.stopRetrying();
171
+ else this.scheduleRetry(connection.sessionId);
172
+ }
173
+
174
+ private discard(connection: Connection, error: AgentEventConnectionError): void {
175
+ connection.attempt.fail(error);
176
+ connection.source.close();
177
+ if (this.current === connection) this.current = null;
178
+ }
179
+
180
+ private scheduleRetry(sessionId: string): void {
181
+ if (!this.options.shouldMaintain(sessionId)) return;
182
+ if (this.retry?.sessionId === sessionId) return;
183
+ this.clearRetry();
184
+
185
+ const timer = setTimeout(() => {
186
+ if (this.retry?.timer !== timer) return;
187
+ this.retry = null;
188
+ this.maintain(sessionId);
189
+ }, this.options.reconnectDelayMs);
190
+ this.retry = { sessionId, timer };
191
+ }
192
+
193
+ private stopRetrying(): void {
194
+ this.retryGeneration += 1;
195
+ this.clearRetry();
196
+ }
197
+
198
+ private clearRetry(): void {
199
+ if (this.retry) clearTimeout(this.retry.timer);
200
+ this.retry = null;
201
+ }
202
+ }
@@ -0,0 +1,155 @@
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 { createAgentEventStream } = await jiti.import("./agent-event-stream.ts");
7
+ const decoder = new TextDecoder();
8
+
9
+ function deferred() {
10
+ let resolve;
11
+ let reject;
12
+ const promise = new Promise((resolvePromise, rejectPromise) => {
13
+ resolve = resolvePromise;
14
+ reject = rejectPromise;
15
+ });
16
+ return { promise, resolve, reject };
17
+ }
18
+
19
+ async function readWithin(reader, timeoutMs = 1_000) {
20
+ let timeout;
21
+ try {
22
+ return await Promise.race([
23
+ reader.read(),
24
+ new Promise((_, reject) => {
25
+ timeout = setTimeout(() => reject(new Error("Timed out reading SSE chunk")), timeoutMs);
26
+ }),
27
+ ]);
28
+ } finally {
29
+ clearTimeout(timeout);
30
+ }
31
+ }
32
+
33
+ function decodeData(chunk) {
34
+ const text = decoder.decode(chunk.value);
35
+ assert.match(text, /^data: /);
36
+ return JSON.parse(text.slice("data: ".length));
37
+ }
38
+
39
+ test("opens the transport before a slow session is ready and snapshots after subscribing", async () => {
40
+ const startup = deferred();
41
+ const abortController = new AbortController();
42
+ const stream = createAgentEventStream(
43
+ new Request("http://localhost/events", { signal: abortController.signal }),
44
+ "session-id",
45
+ startup.promise,
46
+ );
47
+ const reader = stream.getReader();
48
+
49
+ const transport = await readWithin(reader);
50
+ assert.equal(decoder.decode(transport.value), ":\n\n");
51
+
52
+ const snapshot = { role: "assistant", content: [{ type: "text", text: "Hello" }] };
53
+ let listener;
54
+ let subscribeCount = 0;
55
+ let unsubscribeCount = 0;
56
+ startup.resolve({
57
+ isStreaming: true,
58
+ streamingMessage: snapshot,
59
+ onEvent(nextListener) {
60
+ subscribeCount += 1;
61
+ listener = nextListener;
62
+ nextListener({
63
+ type: "message_update",
64
+ message: snapshot,
65
+ assistantMessageEvent: { type: "text_delta", delta: "ignored" },
66
+ });
67
+ nextListener({ type: "agent_start" });
68
+ return () => { unsubscribeCount += 1; };
69
+ },
70
+ });
71
+
72
+ const connected = decodeData(await readWithin(reader));
73
+ const messageStart = decodeData(await readWithin(reader));
74
+ const replayedEvent = decodeData(await readWithin(reader));
75
+ assert.equal(subscribeCount, 1);
76
+ assert.deepEqual(connected, {
77
+ type: "connected",
78
+ sessionId: "session-id",
79
+ isStreaming: true,
80
+ });
81
+ assert.deepEqual(messageStart, { type: "agent_start" });
82
+ assert.deepEqual(replayedEvent, { type: "message_start", message: snapshot });
83
+
84
+ listener({
85
+ type: "message_update",
86
+ message: { ...snapshot },
87
+ assistantMessageEvent: {
88
+ type: "text_delta",
89
+ delta: "!",
90
+ partial: { ...snapshot },
91
+ },
92
+ });
93
+ assert.deepEqual(decodeData(await readWithin(reader)), {
94
+ type: "message_update",
95
+ assistantMessageEvent: { type: "text_delta", delta: "!" },
96
+ });
97
+
98
+ abortController.abort();
99
+ assert.equal(unsubscribeCount, 1);
100
+ assert.equal((await readWithin(reader)).done, true);
101
+ });
102
+
103
+ test("reports a startup failure in-band after opening the transport", async () => {
104
+ const stream = createAgentEventStream(
105
+ new Request("http://localhost/events"),
106
+ "session-id",
107
+ Promise.reject(new Error("broken config")),
108
+ );
109
+ const reader = stream.getReader();
110
+
111
+ assert.equal(decoder.decode((await readWithin(reader)).value), ":\n\n");
112
+ assert.deepEqual(decodeData(await readWithin(reader)), {
113
+ type: "startup_error",
114
+ errorMessage: "Failed to start agent: broken config",
115
+ });
116
+ assert.equal((await readWithin(reader)).done, true);
117
+ });
118
+
119
+ test("does not subscribe when the client cancels during startup", async () => {
120
+ const startup = deferred();
121
+ let subscribeCount = 0;
122
+ const stream = createAgentEventStream(
123
+ new Request("http://localhost/events"),
124
+ "session-id",
125
+ startup.promise,
126
+ );
127
+ const reader = stream.getReader();
128
+
129
+ await readWithin(reader);
130
+ await reader.cancel();
131
+ startup.resolve({
132
+ isStreaming: false,
133
+ streamingMessage: undefined,
134
+ onEvent() {
135
+ subscribeCount += 1;
136
+ return () => {};
137
+ },
138
+ });
139
+ await new Promise((resolve) => setImmediate(resolve));
140
+
141
+ assert.equal(subscribeCount, 0);
142
+ });
143
+
144
+ test("closes an already-aborted request and handles a later startup rejection", async () => {
145
+ const abortController = new AbortController();
146
+ abortController.abort();
147
+ const stream = createAgentEventStream(
148
+ new Request("http://localhost/events", { signal: abortController.signal }),
149
+ "session-id",
150
+ Promise.reject(new Error("startup failed after disconnect")),
151
+ );
152
+
153
+ assert.equal((await readWithin(stream.getReader())).done, true);
154
+ await new Promise((resolve) => setImmediate(resolve));
155
+ });