@alisio/server 0.1.0-alpha.1

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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/auth/guard.d.ts +37 -0
  4. package/dist/auth/guard.js +83 -0
  5. package/dist/auth/token.d.ts +6 -0
  6. package/dist/auth/token.js +22 -0
  7. package/dist/bridges/approval-bridge.d.ts +53 -0
  8. package/dist/bridges/approval-bridge.js +162 -0
  9. package/dist/bridges/interaction-bridge.d.ts +52 -0
  10. package/dist/bridges/interaction-bridge.js +163 -0
  11. package/dist/host/git.d.ts +22 -0
  12. package/dist/host/git.js +99 -0
  13. package/dist/host/presets.d.ts +21 -0
  14. package/dist/host/presets.js +60 -0
  15. package/dist/host/run-scheduler.d.ts +67 -0
  16. package/dist/host/run-scheduler.js +143 -0
  17. package/dist/host/sessions.d.ts +81 -0
  18. package/dist/host/sessions.js +211 -0
  19. package/dist/host/workspace-host.d.ts +87 -0
  20. package/dist/host/workspace-host.js +195 -0
  21. package/dist/http/body.d.ts +7 -0
  22. package/dist/http/body.js +33 -0
  23. package/dist/http/errors.d.ts +15 -0
  24. package/dist/http/errors.js +58 -0
  25. package/dist/http/router.d.ts +37 -0
  26. package/dist/http/router.js +62 -0
  27. package/dist/http/static.d.ts +27 -0
  28. package/dist/http/static.js +132 -0
  29. package/dist/index.d.ts +66 -0
  30. package/dist/index.js +350 -0
  31. package/dist/log.d.ts +21 -0
  32. package/dist/log.js +19 -0
  33. package/dist/routes/approvals.d.ts +8 -0
  34. package/dist/routes/approvals.js +33 -0
  35. package/dist/routes/blobs.d.ts +13 -0
  36. package/dist/routes/blobs.js +46 -0
  37. package/dist/routes/commands.d.ts +21 -0
  38. package/dist/routes/commands.js +149 -0
  39. package/dist/routes/events.d.ts +14 -0
  40. package/dist/routes/events.js +32 -0
  41. package/dist/routes/files.d.ts +21 -0
  42. package/dist/routes/files.js +262 -0
  43. package/dist/routes/health.d.ts +18 -0
  44. package/dist/routes/health.js +38 -0
  45. package/dist/routes/images.d.ts +12 -0
  46. package/dist/routes/images.js +60 -0
  47. package/dist/routes/management.d.ts +32 -0
  48. package/dist/routes/management.js +326 -0
  49. package/dist/routes/prompts.d.ts +22 -0
  50. package/dist/routes/prompts.js +153 -0
  51. package/dist/routes/providers.d.ts +3 -0
  52. package/dist/routes/providers.js +230 -0
  53. package/dist/routes/session-views.d.ts +12 -0
  54. package/dist/routes/session-views.js +76 -0
  55. package/dist/routes/sessions.d.ts +16 -0
  56. package/dist/routes/sessions.js +160 -0
  57. package/dist/routes/workspaces.d.ts +7 -0
  58. package/dist/routes/workspaces.js +32 -0
  59. package/dist/schemas.d.ts +25 -0
  60. package/dist/schemas.js +54 -0
  61. package/dist/sse/hub.d.ts +80 -0
  62. package/dist/sse/hub.js +287 -0
  63. package/dist/sse/inflight.d.ts +16 -0
  64. package/dist/sse/inflight.js +84 -0
  65. package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  66. package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  67. package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  68. package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  69. package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  70. package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  71. package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  72. package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  73. package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  74. package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  75. package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  76. package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  77. package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  78. package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  79. package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  80. package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  81. package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  82. package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  83. package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  84. package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  85. package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  86. package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  87. package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  88. package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  89. package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  90. package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  91. package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  92. package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  93. package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  94. package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  95. package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  96. package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  97. package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  98. package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  99. package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  100. package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  101. package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  102. package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  103. package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  104. package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  105. package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  106. package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  107. package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  108. package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  109. package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  110. package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  111. package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  112. package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  113. package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  114. package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  115. package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  116. package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  117. package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  118. package/dist/web/assets/KaTeX_Size3-Regular-gV2CO0n9.woff2 +0 -0
  119. package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  120. package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  121. package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  122. package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  123. package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  124. package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  125. package/dist/web/assets/SettingsModal-Dj5G3JIz.js +1 -0
  126. package/dist/web/assets/SettingsModal-YCahs6O5.css +1 -0
  127. package/dist/web/assets/abnfDiagram-VCTEODGH-Ct0-FsLe.js +1 -0
  128. package/dist/web/assets/arc-Pry-7G1_.js +1 -0
  129. package/dist/web/assets/architecture-7GRP2DOG-B5Qnm7io.js +1 -0
  130. package/dist/web/assets/architectureDiagram-5GKGNRK7-BvjnUlvj.js +36 -0
  131. package/dist/web/assets/array-BifhSqXX.js +1 -0
  132. package/dist/web/assets/blockDiagram-I7D4REHJ-CBHCj2Mn.js +129 -0
  133. package/dist/web/assets/c4Diagram-7LVT6UL2-9I13K7Yy.js +38 -0
  134. package/dist/web/assets/channel-B-TykkBN.js +1 -0
  135. package/dist/web/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  136. package/dist/web/assets/chunk-4HAMMTFA-Du4xf36t.js +62 -0
  137. package/dist/web/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  138. package/dist/web/assets/chunk-75Z2AOVW-93KpRXCT.js +2 -0
  139. package/dist/web/assets/chunk-DU6HZSFF-DP6dsL6K.js +125 -0
  140. package/dist/web/assets/chunk-F27PBJKO-CxLLRIN4.js +1 -0
  141. package/dist/web/assets/chunk-FOHPRMQF-DzxwRta7.js +161 -0
  142. package/dist/web/assets/chunk-GMAD6QVW-BGv500CR.js +72 -0
  143. package/dist/web/assets/chunk-GVQU2GXP-BmXv3St8.js +1 -0
  144. package/dist/web/assets/chunk-IMKFNOWR-tmbCLM1n.js +231 -0
  145. package/dist/web/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  146. package/dist/web/assets/chunk-L3NEJ4N5-BULbKNtl.js +1 -0
  147. package/dist/web/assets/chunk-OSK3NFVY-DyvADXPn.js +10 -0
  148. package/dist/web/assets/chunk-P2QGCYS3-YhA2sAhR.js +1 -0
  149. package/dist/web/assets/chunk-POPQ4Y6H-BLr1KktA.js +1 -0
  150. package/dist/web/assets/chunk-PWAF6VOD-DJCZC9ie.js +1 -0
  151. package/dist/web/assets/chunk-SHT3W25Y-B8xH9z5t.js +168 -0
  152. package/dist/web/assets/chunk-SVP7TREG-DxynkM9y.js +88 -0
  153. package/dist/web/assets/chunk-TICWLB2K-CZPXqwaW.js +206 -0
  154. package/dist/web/assets/chunk-XXDRQBXY-DqAYeljN.js +1 -0
  155. package/dist/web/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  156. package/dist/web/assets/classDiagram-ZZMXUADV-B6LKfKSk.js +1 -0
  157. package/dist/web/assets/classDiagram-v2-VYDZK3BY-B6LKfKSk.js +1 -0
  158. package/dist/web/assets/cose-bilkent-JH36ORCC-BEpcWcmK.js +1 -0
  159. package/dist/web/assets/css-BsVw1vtW.js +1 -0
  160. package/dist/web/assets/cynefin-OW5HDTMX-rct1-Bf_.js +1 -0
  161. package/dist/web/assets/cynefinDiagram-5FMLGOSQ-CuBJbnvx.js +62 -0
  162. package/dist/web/assets/cytoscape.esm-Yq6u8L66.js +321 -0
  163. package/dist/web/assets/dagre-C-rnJpZF.js +1 -0
  164. package/dist/web/assets/dagre-GXQ25YYZ-w8zs2moB.js +4 -0
  165. package/dist/web/assets/defaultLocale-BFoDCU3G.js +1 -0
  166. package/dist/web/assets/diagram-S7CK7UJ4-BMBhFewM.js +30 -0
  167. package/dist/web/assets/diagram-UQ7AKVKN-C7BN6534.js +41 -0
  168. package/dist/web/assets/diagram-VSXAHHWV-Dd8_wR6U.js +3 -0
  169. package/dist/web/assets/diagram-VX7I27RA-DR3aOC1b.js +24 -0
  170. package/dist/web/assets/diagram-Z3DM3KII-5aBBRZ2Z.js +24 -0
  171. package/dist/web/assets/diff-woXpYk--.js +1 -0
  172. package/dist/web/assets/dist-v5Q1xZ2K.js +1 -0
  173. package/dist/web/assets/ebnfDiagram-PWID7BFC-46Gc2APT.js +1 -0
  174. package/dist/web/assets/engine-CknQqOvU.js +44 -0
  175. package/dist/web/assets/erDiagram-RLTQ6QDP-CNfUFQmD.js +99 -0
  176. package/dist/web/assets/eventmodeling-NTZA5JFV-Dx_J4ait.js +1 -0
  177. package/dist/web/assets/flowDiagram-HODETNUW-Bs7QheBl.js +1 -0
  178. package/dist/web/assets/ganttDiagram-EL5Y4UJY-B1RLLE0F.js +292 -0
  179. package/dist/web/assets/gitGraph-4MIJSDKK-DmzdoCuA.js +1 -0
  180. package/dist/web/assets/gitGraphDiagram-WWUBYQGX-DN5-fMOS.js +106 -0
  181. package/dist/web/assets/github-dark-C-LZuMrd.js +1 -0
  182. package/dist/web/assets/github-light-EUqPIrTm.js +1 -0
  183. package/dist/web/assets/go-rLFTqkRN.js +1 -0
  184. package/dist/web/assets/graphlib-DS17s2tU.js +1 -0
  185. package/dist/web/assets/highlight-C1LfZ1aI.js +152 -0
  186. package/dist/web/assets/index-CGolUSWa.js +75 -0
  187. package/dist/web/assets/index-vUqwyH-2.css +1 -0
  188. package/dist/web/assets/info-A6RAGUB7-CebZRx1l.js +1 -0
  189. package/dist/web/assets/infoDiagram-27XIBGKW-BeY-irSX.js +2 -0
  190. package/dist/web/assets/init-C-OQMol4.js +1 -0
  191. package/dist/web/assets/ishikawaDiagram-5VMMS53U-Djn1dIqp.js +70 -0
  192. package/dist/web/assets/javascript-BgS3c2Ky.js +1 -0
  193. package/dist/web/assets/journeyDiagram-3NMN7TZE-CV_b2MnE.js +139 -0
  194. package/dist/web/assets/json-qhed-kSA.js +1 -0
  195. package/dist/web/assets/jsx-Bz0zcwM4.js +1 -0
  196. package/dist/web/assets/jsxRuntime-D9k5B_Bf.js +1 -0
  197. package/dist/web/assets/kanban-definition-UXKFOSKX-CXTukynL.js +89 -0
  198. package/dist/web/assets/katex-ZlcWpGUi.js +257 -0
  199. package/dist/web/assets/line-DIt63x6x.js +1 -0
  200. package/dist/web/assets/linear-SzVmGSw4.js +1 -0
  201. package/dist/web/assets/mermaid-parser.core-UsYAxtjN.js +7 -0
  202. package/dist/web/assets/mindmap-definition-YA3MSWOX-G0K_7S5N.js +96 -0
  203. package/dist/web/assets/ordinal-BDEzSJ7C.js +1 -0
  204. package/dist/web/assets/packet-AYTQ26CC-D3_L-O-_.js +1 -0
  205. package/dist/web/assets/path-fybaL0A-.js +1 -0
  206. package/dist/web/assets/pegDiagram-XKGWAZYB-BoSJtVsR.js +1 -0
  207. package/dist/web/assets/pie-WAS4IAKB-DUARjLYc.js +1 -0
  208. package/dist/web/assets/pieDiagram-E7YTZNPT-DaFXBymx.js +39 -0
  209. package/dist/web/assets/purify.es--06brOip.js +3 -0
  210. package/dist/web/assets/python-gzcpVVnB.js +1 -0
  211. package/dist/web/assets/quadrantDiagram-AXDQQJYC-DvJJGxS3.js +7 -0
  212. package/dist/web/assets/radar-RG4KPBEZ-CoTA2z0h.js +1 -0
  213. package/dist/web/assets/railroad-74A4TZTK-D22_YeTS.js +1 -0
  214. package/dist/web/assets/railroad-abnf-HS5TGJTU-Mvl9LvJX.js +1 -0
  215. package/dist/web/assets/railroad-ebnf-LZEXJU2U-hvVPkAs6.js +1 -0
  216. package/dist/web/assets/railroad-peg-WCYAUIDC-XX_5aYEK.js +1 -0
  217. package/dist/web/assets/railroadDiagram-O6MQD6OU-Cxnojv6Q.js +1 -0
  218. package/dist/web/assets/requirementDiagram-BXWQKSXE-gW2PlZJn.js +84 -0
  219. package/dist/web/assets/rolldown-runtime-hePW80VL.js +1 -0
  220. package/dist/web/assets/rough.esm-Dy-Kn_BL.js +1 -0
  221. package/dist/web/assets/rust-Cfkwpbl8.js +1 -0
  222. package/dist/web/assets/sankeyDiagram-P5KCCOFB-DcoJ0sYw.js +40 -0
  223. package/dist/web/assets/sequenceDiagram-WJ2MYXX4-kRLbkAfp.js +162 -0
  224. package/dist/web/assets/shellscript-CLZ0U2zV.js +1 -0
  225. package/dist/web/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  226. package/dist/web/assets/sql-BsFa4tDR.js +1 -0
  227. package/dist/web/assets/src-B6xuSHsQ.js +1 -0
  228. package/dist/web/assets/stateDiagram-D77RDMKH-CEWdwxTm.js +1 -0
  229. package/dist/web/assets/stateDiagram-v2-MP3YSRHH-DZ8dTBXf.js +1 -0
  230. package/dist/web/assets/swimlanes-42K2YHIH-oyoSxsbH.js +1 -0
  231. package/dist/web/assets/swimlanesDiagram-VR7AAH4N-McbEeiYz.js +8 -0
  232. package/dist/web/assets/timeline-definition-24CTP7MA-D97QYUq5.js +120 -0
  233. package/dist/web/assets/toml-CcmNWLt0.js +1 -0
  234. package/dist/web/assets/treeView-Q6P3EWNA-FwhRnXTt.js +1 -0
  235. package/dist/web/assets/treemap-WGGIJYW6-ByFNzzzU.js +1 -0
  236. package/dist/web/assets/tsx-udAQXfEw.js +1 -0
  237. package/dist/web/assets/typescript-bsJCZSQ-.js +1 -0
  238. package/dist/web/assets/vennDiagram-4TSXK5OY-CcqjaQy9.js +34 -0
  239. package/dist/web/assets/view-0zE8CdnP.js +1 -0
  240. package/dist/web/assets/view-BQZA99Bp.js +1 -0
  241. package/dist/web/assets/view-BqpAWkcP.css +1 -0
  242. package/dist/web/assets/view-C9vvQ0AK.js +1 -0
  243. package/dist/web/assets/view-CS-jPsy6.js +2 -0
  244. package/dist/web/assets/view-CYRKKlxP.js +1 -0
  245. package/dist/web/assets/view-CyO_m5dR.css +1 -0
  246. package/dist/web/assets/view-D7KCHoy1.css +1 -0
  247. package/dist/web/assets/view-DE2OI6Vv.js +5 -0
  248. package/dist/web/assets/view-DPqI4qBt.css +1 -0
  249. package/dist/web/assets/view-DWcZ1gKW.css +1 -0
  250. package/dist/web/assets/view-Dl8ihdis.css +1 -0
  251. package/dist/web/assets/view-DrJSW55A.css +1 -0
  252. package/dist/web/assets/view-DzlpughI.js +257 -0
  253. package/dist/web/assets/view-NDb0VvVU.js +6 -0
  254. package/dist/web/assets/wardley-WFR3VGLG-Bt4CePrl.js +1 -0
  255. package/dist/web/assets/wardleyDiagram-VM6X3IG4-CcgXimi3.js +78 -0
  256. package/dist/web/assets/xychartDiagram-S5SC5T6Z-0Jx20wNz.js +7 -0
  257. package/dist/web/assets/yaml-rwi0_p6S.js +1 -0
  258. package/dist/web/index.html +17 -0
  259. package/package.json +55 -0
@@ -0,0 +1,80 @@
1
+ import type { Message, RunEvent, ServerFrame, ToolResult } from "@alisio/sdk";
2
+ /** The part of `ServerResponse` the hub writes to (a fake one in unit tests). */
3
+ export interface SseSink {
4
+ write(chunk: string): boolean;
5
+ end(): void;
6
+ once(event: "drain", listener: () => void): unknown;
7
+ }
8
+ export interface SseHubOptions {
9
+ /** Snapshot of one session, built synchronously (node:sqlite reads are synchronous). */
10
+ snapshot(sessionId: string): Extract<ServerFrame, {
11
+ t: "snapshot";
12
+ }>;
13
+ /** Durable message rows after `seq`, ascending. */
14
+ messagesAfter(sessionId: string, seq: number): {
15
+ items: Array<{
16
+ seq: number;
17
+ message: Message;
18
+ }>;
19
+ hasMore: boolean;
20
+ };
21
+ /** The persisted result of a completed tool call. */
22
+ toolResult(sessionId: string, callId: string): ToolResult | undefined;
23
+ /** Workspace id of a session (for workspace-wide frames and eviction). */
24
+ workspaceOf(sessionId: string): string | undefined;
25
+ /** Called when a session gains or loses its last subscriber. */
26
+ onSubscribersChanged?: (sessionId: string) => void;
27
+ heartbeatMs?: number;
28
+ coalesceMs?: number;
29
+ maxQueueFrames?: number;
30
+ maxQueueBytes?: number;
31
+ }
32
+ /**
33
+ * One multiplexed SSE stream per browser tab (§8.5): snapshot-then-deltas per subscribed
34
+ * session, durable frames with `id: <eventId>`, ephemeral deltas coalesced per session, a
35
+ * heartbeat comment, and a bounded per-client queue that ends the stream with `resync` when
36
+ * a slow client overflows it.
37
+ */
38
+ export declare class SseHub {
39
+ private options;
40
+ private clients;
41
+ private pending;
42
+ private heartbeat;
43
+ private readonly coalesceMs;
44
+ private readonly maxFrames;
45
+ private readonly maxBytes;
46
+ /** Streams ended because their client could not keep up. */
47
+ dropped: number;
48
+ constructor(options: SseHubOptions);
49
+ get size(): number;
50
+ /**
51
+ * Registers a stream: `hello`, then one snapshot per session. Each snapshot is built and the
52
+ * subscription registered in the same tick, so no durable event can fall between them.
53
+ */
54
+ attach(sink: SseSink, sessionIds: string[]): () => void;
55
+ private detach;
56
+ subscribers(sessionId: string): number;
57
+ /** Whether any stream watches a session of the workspace (blocks idle eviction). */
58
+ watchesWorkspace(workspaceId: string): boolean;
59
+ workspaceSubscribers(workspaceId: string): number;
60
+ /** Feeds a runner event (the app's `onEvent`). */
61
+ publish(event: RunEvent): void;
62
+ private coalesce;
63
+ /** Sends the session's buffered deltas as one `delta` frame now. */
64
+ flush(sessionId: string): void;
65
+ private toolResult;
66
+ /** Emits durable message rows each subscriber does not have yet. */
67
+ private messages;
68
+ /** A frame for every stream subscribed to `sessionId`. */
69
+ toSession(sessionId: string, frame: ServerFrame, id?: string): void;
70
+ /** A frame for every stream watching a session of the workspace. */
71
+ toWorkspace(workspaceId: string, frame: ServerFrame): void;
72
+ /** A frame for every stream (e.g. `session_status` for sidebars). */
73
+ broadcast(frame: ServerFrame): void;
74
+ private frame;
75
+ /** Writes with backpressure: queue while the socket drains; overflow ends with `resync`. */
76
+ private write;
77
+ private drain;
78
+ /** Ends every stream (shutdown). */
79
+ closeAll(): void;
80
+ }
@@ -0,0 +1,287 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { isEphemeralRunEventType } from "@alisio/sdk";
3
+ import { PROTOCOL_VERSION } from "../routes/health.js";
4
+ import { progressChunk } from "./inflight.js";
5
+ /** Durable events after which new message rows may exist. */
6
+ const MESSAGE_BOUNDARIES = new Set([
7
+ "run_started",
8
+ "turn_completed",
9
+ "run_completed",
10
+ "run_failed",
11
+ "run_cancelled",
12
+ "run_turns_exceeded",
13
+ "compaction_completed",
14
+ "context_reduced",
15
+ "session_context_injected",
16
+ ]);
17
+ const MAX_TOOL_RESULT_BYTES = 256 * 1024;
18
+ /**
19
+ * One multiplexed SSE stream per browser tab (§8.5): snapshot-then-deltas per subscribed
20
+ * session, durable frames with `id: <eventId>`, ephemeral deltas coalesced per session, a
21
+ * heartbeat comment, and a bounded per-client queue that ends the stream with `resync` when
22
+ * a slow client overflows it.
23
+ */
24
+ export class SseHub {
25
+ options;
26
+ clients = new Map();
27
+ pending = new Map();
28
+ heartbeat;
29
+ coalesceMs;
30
+ maxFrames;
31
+ maxBytes;
32
+ /** Streams ended because their client could not keep up. */
33
+ dropped = 0;
34
+ constructor(options) {
35
+ this.options = options;
36
+ this.coalesceMs = Math.min(50, Math.max(16, options.coalesceMs ?? 33));
37
+ this.maxFrames = options.maxQueueFrames ?? 256;
38
+ this.maxBytes = options.maxQueueBytes ?? 1024 * 1024;
39
+ this.heartbeat = setInterval(() => {
40
+ for (const client of this.clients.values())
41
+ this.write(client, ": ping\n\n", true);
42
+ }, options.heartbeatMs ?? 15_000);
43
+ this.heartbeat.unref();
44
+ }
45
+ get size() {
46
+ return this.clients.size;
47
+ }
48
+ /**
49
+ * Registers a stream: `hello`, then one snapshot per session. Each snapshot is built and the
50
+ * subscription registered in the same tick, so no durable event can fall between them.
51
+ */
52
+ attach(sink, sessionIds) {
53
+ const client = {
54
+ id: randomUUID(),
55
+ sink,
56
+ sessions: new Map(),
57
+ workspaces: new Set(),
58
+ queue: [],
59
+ queuedBytes: 0,
60
+ draining: false,
61
+ closed: false,
62
+ };
63
+ this.clients.set(client.id, client);
64
+ this.write(client, "retry: 2000\n\n", false);
65
+ this.frame(client, {
66
+ t: "hello",
67
+ protocolVersion: PROTOCOL_VERSION,
68
+ streamId: client.id,
69
+ serverTime: Date.now(),
70
+ });
71
+ for (const sessionId of new Set(sessionIds)) {
72
+ // Deltas buffered before this client joined are already part of the snapshot's inflight.
73
+ this.flush(sessionId);
74
+ const snapshot = this.options.snapshot(sessionId);
75
+ client.sessions.set(sessionId, { lastSeq: snapshot.messages.items.at(-1)?.seq ?? 0 });
76
+ const workspace = this.options.workspaceOf(sessionId);
77
+ if (workspace)
78
+ client.workspaces.add(workspace);
79
+ this.frame(client, snapshot);
80
+ this.options.onSubscribersChanged?.(sessionId);
81
+ }
82
+ return () => this.detach(client);
83
+ }
84
+ detach(client) {
85
+ if (!this.clients.delete(client.id))
86
+ return;
87
+ client.closed = true;
88
+ client.queue = [];
89
+ for (const sessionId of client.sessions.keys())
90
+ this.options.onSubscribersChanged?.(sessionId);
91
+ }
92
+ subscribers(sessionId) {
93
+ let count = 0;
94
+ for (const client of this.clients.values())
95
+ if (client.sessions.has(sessionId))
96
+ count++;
97
+ return count;
98
+ }
99
+ /** Whether any stream watches a session of the workspace (blocks idle eviction). */
100
+ watchesWorkspace(workspaceId) {
101
+ for (const client of this.clients.values())
102
+ if (client.workspaces.has(workspaceId))
103
+ return true;
104
+ return false;
105
+ }
106
+ workspaceSubscribers(workspaceId) {
107
+ let count = 0;
108
+ for (const client of this.clients.values())
109
+ if (client.workspaces.has(workspaceId))
110
+ count++;
111
+ return count;
112
+ }
113
+ /** Feeds a runner event (the app's `onEvent`). */
114
+ publish(event) {
115
+ const sessionId = event.sessionId;
116
+ if (isEphemeralRunEventType(event.type)) {
117
+ if (!this.subscribers(sessionId))
118
+ return;
119
+ this.coalesce(event);
120
+ return;
121
+ }
122
+ this.flush(sessionId);
123
+ this.toSession(sessionId, { t: "event", sessionId, event }, event.eventId);
124
+ const data = (event.data ?? {});
125
+ if (event.type === "tool_completed" && data.id)
126
+ this.toolResult(event, data.id);
127
+ if (MESSAGE_BOUNDARIES.has(event.type))
128
+ this.messages(sessionId);
129
+ }
130
+ coalesce(event) {
131
+ const sessionId = event.sessionId;
132
+ let pending = this.pending.get(sessionId);
133
+ if (pending && pending.runId !== event.runId) {
134
+ this.flush(sessionId);
135
+ pending = undefined;
136
+ }
137
+ if (!pending) {
138
+ pending = { runId: event.runId, text: "", reasoning: "", progress: [] };
139
+ this.pending.set(sessionId, pending);
140
+ }
141
+ const data = (event.data ?? {});
142
+ if (event.type === "text_delta")
143
+ pending.text += String(data.delta ?? "");
144
+ else if (event.type === "reasoning_delta")
145
+ pending.reasoning += String(data.delta ?? "");
146
+ else
147
+ pending.progress.push({ toolId: String(data.id), chunk: progressChunk(data.data) });
148
+ pending.timer ??= setTimeout(() => this.flush(sessionId), this.coalesceMs);
149
+ }
150
+ /** Sends the session's buffered deltas as one `delta` frame now. */
151
+ flush(sessionId) {
152
+ const pending = this.pending.get(sessionId);
153
+ if (!pending)
154
+ return;
155
+ this.pending.delete(sessionId);
156
+ if (pending.timer)
157
+ clearTimeout(pending.timer);
158
+ const frame = {
159
+ t: "delta",
160
+ sessionId,
161
+ runId: pending.runId,
162
+ ...(pending.text ? { text: pending.text } : {}),
163
+ ...(pending.reasoning ? { reasoning: pending.reasoning } : {}),
164
+ ...(pending.progress.length ? { progress: pending.progress } : {}),
165
+ };
166
+ for (const client of this.clients.values())
167
+ if (client.sessions.has(sessionId))
168
+ this.frame(client, frame, undefined, true);
169
+ }
170
+ toolResult(event, callId) {
171
+ const result = this.options.toolResult(event.sessionId, callId);
172
+ if (!result)
173
+ return;
174
+ const size = Buffer.byteLength(JSON.stringify(result));
175
+ const truncated = size > MAX_TOOL_RESULT_BYTES;
176
+ this.toSession(event.sessionId, {
177
+ t: "tool_result",
178
+ sessionId: event.sessionId,
179
+ runId: event.runId,
180
+ callId,
181
+ result: truncated
182
+ ? {
183
+ ...result,
184
+ content: result.content.filter((part) => part.type === "text").slice(0, 1),
185
+ }
186
+ : result,
187
+ ...(truncated ? { truncated: true } : {}),
188
+ });
189
+ }
190
+ /** Emits durable message rows each subscriber does not have yet. */
191
+ messages(sessionId) {
192
+ const subs = [...this.clients.values()].filter((c) => c.sessions.has(sessionId));
193
+ if (!subs.length)
194
+ return;
195
+ let after = Math.min(...subs.map((c) => c.sessions.get(sessionId)?.lastSeq ?? 0));
196
+ for (let page = 0; page < 20; page++) {
197
+ const { items, hasMore } = this.options.messagesAfter(sessionId, after);
198
+ for (const row of items)
199
+ for (const client of subs) {
200
+ const sub = client.sessions.get(sessionId);
201
+ if (!sub || row.seq <= sub.lastSeq)
202
+ continue;
203
+ sub.lastSeq = row.seq;
204
+ this.frame(client, { t: "message", sessionId, seq: row.seq, message: row.message });
205
+ }
206
+ after = items.at(-1)?.seq ?? after;
207
+ if (!hasMore)
208
+ break;
209
+ }
210
+ }
211
+ /** A frame for every stream subscribed to `sessionId`. */
212
+ toSession(sessionId, frame, id) {
213
+ for (const client of this.clients.values())
214
+ if (client.sessions.has(sessionId))
215
+ this.frame(client, frame, id);
216
+ }
217
+ /** A frame for every stream watching a session of the workspace. */
218
+ toWorkspace(workspaceId, frame) {
219
+ for (const client of this.clients.values())
220
+ if (client.workspaces.has(workspaceId))
221
+ this.frame(client, frame);
222
+ }
223
+ /** A frame for every stream (e.g. `session_status` for sidebars). */
224
+ broadcast(frame) {
225
+ for (const client of this.clients.values())
226
+ this.frame(client, frame);
227
+ }
228
+ frame(client, frame, id, droppable = false) {
229
+ this.write(client, `${id ? `id: ${id}\n` : ""}data: ${JSON.stringify(frame)}\n\n`, droppable);
230
+ }
231
+ /** Writes with backpressure: queue while the socket drains; overflow ends with `resync`. */
232
+ write(client, chunk, droppable) {
233
+ if (client.closed)
234
+ return;
235
+ if (!client.draining) {
236
+ if (!client.sink.write(chunk)) {
237
+ client.draining = true;
238
+ client.sink.once("drain", () => this.drain(client));
239
+ }
240
+ return;
241
+ }
242
+ client.queue.push({ chunk, droppable });
243
+ client.queuedBytes += chunk.length;
244
+ if (client.queue.length <= this.maxFrames && client.queuedBytes <= this.maxBytes)
245
+ return;
246
+ client.queue = client.queue.filter((item) => !item.droppable);
247
+ client.queuedBytes = client.queue.reduce((sum, item) => sum + item.chunk.length, 0);
248
+ if (client.queue.length <= this.maxFrames && client.queuedBytes <= this.maxBytes)
249
+ return;
250
+ // Still over: the client must rebuild from a snapshot. End the stream; EventSource reopens.
251
+ this.dropped++;
252
+ client.queue = [];
253
+ client.closed = true;
254
+ client.sink.write(`data: ${JSON.stringify({ t: "resync", reason: "overflow" })}\n\n`);
255
+ client.sink.end();
256
+ this.detach(client);
257
+ }
258
+ drain(client) {
259
+ client.draining = false;
260
+ while (client.queue.length && !client.closed) {
261
+ const item = client.queue.shift();
262
+ client.queuedBytes -= item.chunk.length;
263
+ if (!client.sink.write(item.chunk)) {
264
+ client.draining = true;
265
+ client.sink.once("drain", () => this.drain(client));
266
+ return;
267
+ }
268
+ }
269
+ }
270
+ /** Ends every stream (shutdown). */
271
+ closeAll() {
272
+ clearInterval(this.heartbeat);
273
+ for (const pending of this.pending.values())
274
+ if (pending.timer)
275
+ clearTimeout(pending.timer);
276
+ this.pending.clear();
277
+ for (const client of [...this.clients.values()]) {
278
+ this.detach(client);
279
+ try {
280
+ client.sink.end();
281
+ }
282
+ catch {
283
+ /* already closed */
284
+ }
285
+ }
286
+ }
287
+ }
@@ -0,0 +1,16 @@
1
+ import type { InflightState, RunEvent } from "@alisio/sdk";
2
+ /** A tool progress payload as text (strings verbatim, anything else as JSON). */
3
+ export declare const progressChunk: (data: unknown) => string;
4
+ /**
5
+ * In-flight state of each session's current run, rebuilt from the runner's events, so a
6
+ * snapshot can carry what the durable store does not have yet: streamed text since the last
7
+ * turn, reasoning (never persisted) and running tools with their output tail.
8
+ */
9
+ export declare class InflightTracker {
10
+ private runs;
11
+ /** A scheduled run waiting for a slot (or started but not yet streaming). */
12
+ mark(sessionId: string, runId: string, status: "queued" | "running"): void;
13
+ clear(sessionId: string, runId?: string): void;
14
+ apply(event: RunEvent): void;
15
+ get(sessionId: string): InflightState | undefined;
16
+ }
@@ -0,0 +1,84 @@
1
+ const MAX_TEXT = 1_000_000;
2
+ const MAX_TAIL = 4_096;
3
+ /** A tool progress payload as text (strings verbatim, anything else as JSON). */
4
+ export const progressChunk = (data) => typeof data === "string" ? data : JSON.stringify(data);
5
+ /**
6
+ * In-flight state of each session's current run, rebuilt from the runner's events, so a
7
+ * snapshot can carry what the durable store does not have yet: streamed text since the last
8
+ * turn, reasoning (never persisted) and running tools with their output tail.
9
+ */
10
+ export class InflightTracker {
11
+ runs = new Map();
12
+ /** A scheduled run waiting for a slot (or started but not yet streaming). */
13
+ mark(sessionId, runId, status) {
14
+ const current = this.runs.get(sessionId);
15
+ if (current && current.runId === runId)
16
+ current.status = status;
17
+ else
18
+ this.runs.set(sessionId, { runId, status, text: "", reasoning: "", tools: new Map() });
19
+ }
20
+ clear(sessionId, runId) {
21
+ if (!runId || this.runs.get(sessionId)?.runId === runId)
22
+ this.runs.delete(sessionId);
23
+ }
24
+ apply(event) {
25
+ const sessionId = event.sessionId;
26
+ if (event.type === "run_started") {
27
+ this.mark(sessionId, event.runId, "running");
28
+ return;
29
+ }
30
+ const run = this.runs.get(sessionId);
31
+ if (!run || run.runId !== event.runId)
32
+ return;
33
+ const data = (event.data ?? {});
34
+ switch (event.type) {
35
+ case "text_delta":
36
+ run.text = (run.text + String(data.delta ?? "")).slice(-MAX_TEXT);
37
+ break;
38
+ case "reasoning_delta":
39
+ run.reasoning = (run.reasoning + String(data.delta ?? "")).slice(-MAX_TEXT);
40
+ break;
41
+ case "turn_completed":
42
+ run.text = "";
43
+ run.reasoning = "";
44
+ break;
45
+ case "tool_started":
46
+ run.tools.set(String(data.id), {
47
+ id: String(data.id),
48
+ name: String(data.name ?? ""),
49
+ arguments: String(data.arguments ?? ""),
50
+ effect: data.effect ?? "external",
51
+ startedAt: Date.parse(event.timestamp) || Date.now(),
52
+ tail: "",
53
+ });
54
+ break;
55
+ case "tool_progress": {
56
+ const tool = run.tools.get(String(data.id));
57
+ if (tool)
58
+ tool.tail = (tool.tail + progressChunk(data.data)).slice(-MAX_TAIL);
59
+ break;
60
+ }
61
+ case "tool_completed":
62
+ run.tools.delete(String(data.id));
63
+ break;
64
+ case "run_completed":
65
+ case "run_failed":
66
+ case "run_cancelled":
67
+ case "run_turns_exceeded":
68
+ this.runs.delete(sessionId);
69
+ break;
70
+ }
71
+ }
72
+ get(sessionId) {
73
+ const run = this.runs.get(sessionId);
74
+ if (!run)
75
+ return undefined;
76
+ return {
77
+ runId: run.runId,
78
+ status: run.status,
79
+ text: run.text,
80
+ reasoning: run.reasoning,
81
+ tools: [...run.tools.values()].map((tool) => ({ ...tool })),
82
+ };
83
+ }
84
+ }