@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gustavo Gutiérrez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # @alisio/server
2
+
3
+ **The local HTTP + SSE server behind `alisio serve`.** It gives a browser multi-workspace,
4
+ multi-session access to the [Alisio](https://github.com/GustavoGutierrez/alisio) agent core.
5
+
6
+ ## What it is
7
+
8
+ - `node:http` only (no web framework, no WebSocket): JSON routes under `/api` and one multiplexed
9
+ Server-Sent Events stream per browser tab (`GET /api/events`).
10
+ - One `@alisio/core` `Application` per open workspace, created lazily and evicted when idle.
11
+ - Local security model: binds `127.0.0.1` by default, a per-process launch token exchanged for an
12
+ `HttpOnly; SameSite=Strict` cookie, strict `Host`/`Origin` checks, a strict CSP and fail-closed
13
+ approvals (no answer means deny).
14
+ - Runs on Node.js >= 22.16 and on Bun, with portable Node APIs only.
15
+
16
+ The CLI loads it with a dynamic `import("@alisio/server")` inside `alisio serve`, so `alisio`,
17
+ `alisio run` and the TUI never load it.
18
+
19
+ ## Usage
20
+
21
+ Most people run it through the CLI:
22
+
23
+ ```sh
24
+ alisio serve # prints http://127.0.0.1:4317/?token=… and opens the browser
25
+ alisio serve --no-open --port 0 --allow-write
26
+ ```
27
+
28
+ Embedding it directly:
29
+
30
+ ```ts
31
+ import { startServer } from "@alisio/server";
32
+
33
+ const server = await startServer({ port: 0, app: { allowWrite: true } });
34
+ console.log(server.launchUrl);
35
+ // …
36
+ await server.close();
37
+ ```
38
+
39
+ Plugins are not sandboxed: a trusted project's plugins run inside the server process with its
40
+ operating-system permissions. Remote access (`--host` other than loopback) requires
41
+ `--allow-remote` and has no TLS; prefer an SSH tunnel.
42
+
43
+ Documentation: <https://gustavogutierrez.github.io/alisio/web>.
44
+
45
+ ## License
46
+
47
+ MIT
@@ -0,0 +1,37 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ /** Whether a bind address only accepts local connections. */
3
+ export declare function isLoopbackHost(host: string): boolean;
4
+ /**
5
+ * Launch-token authentication plus the Host/Origin checks of §11.1. The launch token is only
6
+ * accepted once per browser (exchanged for a cookie holding a separate secret); cookies are
7
+ * named per port because browsers do not isolate cookies by port.
8
+ */
9
+ export declare class AuthGuard {
10
+ readonly token: string;
11
+ readonly cookieName: string;
12
+ private readonly secret;
13
+ private readonly hosts;
14
+ private readonly wildcard;
15
+ constructor(options: {
16
+ token?: string;
17
+ host: string;
18
+ port: number;
19
+ });
20
+ /**
21
+ * DNS-rebinding defense: the `Host` header must name this server. When bound to a wildcard
22
+ * address (`--allow-remote`), any IP-literal host on the right port is accepted too, since a
23
+ * rebinding attack needs a DNS name.
24
+ */
25
+ checkHost(req: IncomingMessage): void;
26
+ /**
27
+ * CSRF defense: requests with side effects need an `Origin` equal to this server's origin; any
28
+ * request that carries a foreign `Origin` is refused.
29
+ */
30
+ checkOrigin(req: IncomingMessage): void;
31
+ /** Whether the request carries this process's session cookie. */
32
+ authenticated(req: IncomingMessage): boolean;
33
+ /** Whether `candidate` is the launch token (constant time). */
34
+ validToken(candidate: string | null): boolean;
35
+ /** The `Set-Cookie` value issued after a successful token exchange. */
36
+ sessionCookie(): string;
37
+ }
@@ -0,0 +1,83 @@
1
+ import { isIP } from "node:net";
2
+ import { HttpError } from "../http/errors.js";
3
+ import { newSecret, parseCookies, safeEqual } from "./token.js";
4
+ const LOOPBACK = new Set(["127.0.0.1", "localhost", "::1"]);
5
+ /** Whether a bind address only accepts local connections. */
6
+ export function isLoopbackHost(host) {
7
+ const bare = host.replace(/^\[|\]$/g, "");
8
+ return LOOPBACK.has(bare) || /^127\.\d+\.\d+\.\d+$/.test(bare);
9
+ }
10
+ const WILDCARD = new Set(["0.0.0.0", "::", "[::]"]);
11
+ /**
12
+ * Launch-token authentication plus the Host/Origin checks of §11.1. The launch token is only
13
+ * accepted once per browser (exchanged for a cookie holding a separate secret); cookies are
14
+ * named per port because browsers do not isolate cookies by port.
15
+ */
16
+ export class AuthGuard {
17
+ token;
18
+ cookieName;
19
+ secret = newSecret();
20
+ hosts;
21
+ wildcard;
22
+ constructor(options) {
23
+ this.token = options.token ?? newSecret();
24
+ this.cookieName = `alisio_session_${options.port}`;
25
+ const port = options.port;
26
+ const explicit = options.host.includes(":") && !options.host.startsWith("[")
27
+ ? `[${options.host}]`
28
+ : options.host;
29
+ this.hosts = new Set([
30
+ `127.0.0.1:${port}`,
31
+ `localhost:${port}`,
32
+ `[::1]:${port}`,
33
+ `${explicit}:${port}`,
34
+ ]);
35
+ this.wildcard = WILDCARD.has(options.host);
36
+ }
37
+ /**
38
+ * DNS-rebinding defense: the `Host` header must name this server. When bound to a wildcard
39
+ * address (`--allow-remote`), any IP-literal host on the right port is accepted too, since a
40
+ * rebinding attack needs a DNS name.
41
+ */
42
+ checkHost(req) {
43
+ const host = (req.headers.host ?? "").toLowerCase();
44
+ if (this.hosts.has(host))
45
+ return;
46
+ if (this.wildcard) {
47
+ const match = /^(\[[^\]]+\]|[^:]+):(\d+)$/.exec(host);
48
+ const name = match?.[1]?.replace(/^\[|\]$/g, "");
49
+ if (match && name && isIP(name) && [...this.hosts].some((h) => h.endsWith(`:${match[2]}`)))
50
+ return;
51
+ }
52
+ throw new HttpError("forbidden_host", "Host not allowed");
53
+ }
54
+ /**
55
+ * CSRF defense: requests with side effects need an `Origin` equal to this server's origin; any
56
+ * request that carries a foreign `Origin` is refused.
57
+ */
58
+ checkOrigin(req) {
59
+ const origin = req.headers.origin;
60
+ const expected = `http://${req.headers.host ?? ""}`;
61
+ const effectful = !["GET", "HEAD", "OPTIONS"].includes(req.method ?? "GET");
62
+ if (origin === undefined) {
63
+ if (effectful)
64
+ throw new HttpError("forbidden_origin", "Origin header required");
65
+ return;
66
+ }
67
+ if (origin.toLowerCase() !== expected.toLowerCase())
68
+ throw new HttpError("forbidden_origin", "Origin not allowed");
69
+ }
70
+ /** Whether the request carries this process's session cookie. */
71
+ authenticated(req) {
72
+ const value = parseCookies(req.headers.cookie)[this.cookieName];
73
+ return value !== undefined && safeEqual(value, this.secret);
74
+ }
75
+ /** Whether `candidate` is the launch token (constant time). */
76
+ validToken(candidate) {
77
+ return candidate !== null && safeEqual(candidate, this.token);
78
+ }
79
+ /** The `Set-Cookie` value issued after a successful token exchange. */
80
+ sessionCookie() {
81
+ return `${this.cookieName}=${this.secret}; HttpOnly; SameSite=Strict; Path=/`;
82
+ }
83
+ }
@@ -0,0 +1,6 @@
1
+ /** A fresh 256-bit secret, base64url encoded. Never written to disk. */
2
+ export declare const newSecret: () => string;
3
+ /** Constant-time string comparison (unequal lengths compare as different). */
4
+ export declare function safeEqual(a: string, b: string): boolean;
5
+ /** Parses a `Cookie` header into name → value (first occurrence wins). */
6
+ export declare function parseCookies(header: string | undefined): Record<string, string>;
@@ -0,0 +1,22 @@
1
+ import { randomBytes, timingSafeEqual } from "node:crypto";
2
+ /** A fresh 256-bit secret, base64url encoded. Never written to disk. */
3
+ export const newSecret = () => randomBytes(32).toString("base64url");
4
+ /** Constant-time string comparison (unequal lengths compare as different). */
5
+ export function safeEqual(a, b) {
6
+ const left = Buffer.from(a);
7
+ const right = Buffer.from(b);
8
+ return left.length === right.length && timingSafeEqual(left, right);
9
+ }
10
+ /** Parses a `Cookie` header into name → value (first occurrence wins). */
11
+ export function parseCookies(header) {
12
+ const cookies = {};
13
+ for (const part of (header ?? "").split(";")) {
14
+ const index = part.indexOf("=");
15
+ if (index < 0)
16
+ continue;
17
+ const name = part.slice(0, index).trim();
18
+ if (name && !(name in cookies))
19
+ cookies[name] = part.slice(index + 1).trim();
20
+ }
21
+ return cookies;
22
+ }
@@ -0,0 +1,53 @@
1
+ import type { ApprovalDecision, ApprovalHandler, ExternalDirectoryHandler } from "@alisio/core";
2
+ import type { PendingApproval, ServerFrame } from "@alisio/sdk";
3
+ /** What the bridges need from the SSE hub. */
4
+ export interface BridgeHub {
5
+ toSession(sessionId: string, frame: ServerFrame): void;
6
+ subscribers(sessionId: string): number;
7
+ }
8
+ export interface ApprovalBridgeOptions {
9
+ hub: BridgeHub;
10
+ /** Root of a session (children's approvals show in their root session view). */
11
+ rootOf: (sessionId: string) => string;
12
+ /** Current run of a session, when known. */
13
+ runOf?: (sessionId: string) => string | undefined;
14
+ /** Deny when the root session has no subscriber for this long (default 30 s). */
15
+ graceMs?: number;
16
+ /** Deny after this long even with subscribers (default 10 min; 0 = no limit). */
17
+ timeoutMs?: number;
18
+ /** Called when a root session starts or stops waiting for a decision. */
19
+ onChange?: (rootSessionId: string) => void;
20
+ }
21
+ /**
22
+ * Web approvals for the runner's `ApprovalHandler` and the external-directory handler (§8.7).
23
+ * Fail-closed: the answer is `deny` when the run aborts, when nobody watches the root session
24
+ * for `graceMs`, or after `timeoutMs`. The first answer wins; later ones are refused.
25
+ */
26
+ export declare class ApprovalBridge {
27
+ private options;
28
+ private entries;
29
+ private recent;
30
+ private readonly graceMs;
31
+ private readonly timeoutMs;
32
+ constructor(options: ApprovalBridgeOptions);
33
+ /** Passed as `AppOptions.approve` for every workspace app. */
34
+ readonly handler: ApprovalHandler;
35
+ /** Passed as `AppOptions.approveExternalDirectory` for every workspace app. */
36
+ readonly directoryHandler: ExternalDirectoryHandler;
37
+ private ask;
38
+ private send;
39
+ private watched;
40
+ /** Starts the grace timer when nobody watches; clears it when someone does. */
41
+ private watch;
42
+ /** Re-evaluates grace timers after streams connect or disconnect. */
43
+ subscribersChanged(): void;
44
+ private remember;
45
+ /** Answers an approval; the first answer wins. */
46
+ resolve(approvalId: string, decision: ApprovalDecision): "resolved" | "already_resolved" | "not_found";
47
+ /** Pending approvals, optionally of one root session (or of the session itself). */
48
+ pending(sessionId?: string): PendingApproval[];
49
+ awaiting(rootSessionId: string): boolean;
50
+ get size(): number;
51
+ /** Denies everything (shutdown). */
52
+ denyAll(): void;
53
+ }
@@ -0,0 +1,162 @@
1
+ import { randomUUID } from "node:crypto";
2
+ const MAX_INPUT = 4_096;
3
+ const RECENT = 500;
4
+ const pretty = (value) => {
5
+ let text;
6
+ try {
7
+ text = JSON.stringify(value, null, 2) ?? "";
8
+ }
9
+ catch {
10
+ text = String(value);
11
+ }
12
+ return text.length > MAX_INPUT ? `${text.slice(0, MAX_INPUT)}\n…` : text;
13
+ };
14
+ /**
15
+ * Web approvals for the runner's `ApprovalHandler` and the external-directory handler (§8.7).
16
+ * Fail-closed: the answer is `deny` when the run aborts, when nobody watches the root session
17
+ * for `graceMs`, or after `timeoutMs`. The first answer wins; later ones are refused.
18
+ */
19
+ export class ApprovalBridge {
20
+ options;
21
+ entries = new Map();
22
+ recent = new Set();
23
+ graceMs;
24
+ timeoutMs;
25
+ constructor(options) {
26
+ this.options = options;
27
+ this.graceMs = options.graceMs ?? 30_000;
28
+ this.timeoutMs = options.timeoutMs ?? 10 * 60_000;
29
+ }
30
+ /** Passed as `AppOptions.approve` for every workspace app. */
31
+ handler = (request) => {
32
+ const session = request.session ?? "";
33
+ return this.ask({
34
+ approvalId: `${session}:${request.call.id}`,
35
+ sessionId: session,
36
+ rootSessionId: this.options.rootOf(session),
37
+ kind: "effect",
38
+ callId: request.call.id,
39
+ name: request.call.name,
40
+ effect: request.effect,
41
+ ...(request.label ? { label: request.label } : {}),
42
+ input: pretty(request.input),
43
+ }, request.signal);
44
+ };
45
+ /** Passed as `AppOptions.approveExternalDirectory` for every workspace app. */
46
+ directoryHandler = (request) => {
47
+ const session = request.session ?? "";
48
+ return this.ask({
49
+ approvalId: `${session}:dir:${randomUUID()}`,
50
+ sessionId: session,
51
+ rootSessionId: this.options.rootOf(session),
52
+ kind: "directory",
53
+ directory: request.directory,
54
+ ...(request.label ? { label: request.label } : {}),
55
+ input: pretty({ directory: request.directory, path: request.path }),
56
+ }, request.signal);
57
+ };
58
+ ask(base, signal) {
59
+ if (signal.aborted)
60
+ return Promise.resolve("deny");
61
+ const runId = this.options.runOf?.(base.rootSessionId);
62
+ const approval = {
63
+ ...base,
64
+ ...(runId ? { runId } : {}),
65
+ ...(this.timeoutMs > 0 ? { expiresAt: Date.now() + this.timeoutMs } : {}),
66
+ };
67
+ return new Promise((resolve) => {
68
+ let timeout;
69
+ const onAbort = () => entry.settle("deny", "cancelled");
70
+ const entry = {
71
+ approval,
72
+ settle: (decision, reason) => {
73
+ if (this.entries.get(approval.approvalId) !== entry)
74
+ return;
75
+ this.entries.delete(approval.approvalId);
76
+ this.remember(approval.approvalId);
77
+ if (timeout)
78
+ clearTimeout(timeout);
79
+ if (entry.grace)
80
+ clearTimeout(entry.grace);
81
+ signal.removeEventListener("abort", onAbort);
82
+ this.send(approval, {
83
+ t: "approval_withdrawn",
84
+ approvalId: approval.approvalId,
85
+ reason: reason ?? "resolved_elsewhere",
86
+ });
87
+ this.options.onChange?.(approval.rootSessionId);
88
+ resolve(decision);
89
+ },
90
+ };
91
+ this.entries.set(approval.approvalId, entry);
92
+ signal.addEventListener("abort", onAbort, { once: true });
93
+ if (this.timeoutMs > 0)
94
+ timeout = setTimeout(() => entry.settle("deny", "timeout"), this.timeoutMs);
95
+ this.send(approval, { t: "approval", approval });
96
+ this.watch(entry);
97
+ this.options.onChange?.(approval.rootSessionId);
98
+ });
99
+ }
100
+ send(approval, frame) {
101
+ this.options.hub.toSession(approval.rootSessionId, frame);
102
+ if (approval.sessionId !== approval.rootSessionId)
103
+ this.options.hub.toSession(approval.sessionId, frame);
104
+ }
105
+ watched(approval) {
106
+ return (this.options.hub.subscribers(approval.rootSessionId) > 0 ||
107
+ this.options.hub.subscribers(approval.sessionId) > 0);
108
+ }
109
+ /** Starts the grace timer when nobody watches; clears it when someone does. */
110
+ watch(entry) {
111
+ if (this.watched(entry.approval)) {
112
+ if (entry.grace)
113
+ clearTimeout(entry.grace);
114
+ entry.grace = undefined;
115
+ }
116
+ else
117
+ entry.grace ??= setTimeout(() => {
118
+ if (!this.watched(entry.approval))
119
+ entry.settle("deny", "timeout");
120
+ else
121
+ entry.grace = undefined;
122
+ }, this.graceMs);
123
+ }
124
+ /** Re-evaluates grace timers after streams connect or disconnect. */
125
+ subscribersChanged() {
126
+ for (const entry of this.entries.values())
127
+ this.watch(entry);
128
+ }
129
+ remember(id) {
130
+ this.recent.add(id);
131
+ if (this.recent.size > RECENT)
132
+ this.recent.delete(this.recent.values().next().value);
133
+ }
134
+ /** Answers an approval; the first answer wins. */
135
+ resolve(approvalId, decision) {
136
+ const entry = this.entries.get(approvalId);
137
+ if (!entry)
138
+ return this.recent.has(approvalId) ? "already_resolved" : "not_found";
139
+ entry.settle(decision);
140
+ return "resolved";
141
+ }
142
+ /** Pending approvals, optionally of one root session (or of the session itself). */
143
+ pending(sessionId) {
144
+ return [...this.entries.values()]
145
+ .map((entry) => entry.approval)
146
+ .filter((approval) => !sessionId || approval.rootSessionId === sessionId || approval.sessionId === sessionId);
147
+ }
148
+ awaiting(rootSessionId) {
149
+ for (const entry of this.entries.values())
150
+ if (entry.approval.rootSessionId === rootSessionId)
151
+ return true;
152
+ return false;
153
+ }
154
+ get size() {
155
+ return this.entries.size;
156
+ }
157
+ /** Denies everything (shutdown). */
158
+ denyAll() {
159
+ for (const entry of [...this.entries.values()])
160
+ entry.settle("deny", "cancelled");
161
+ }
162
+ }
@@ -0,0 +1,52 @@
1
+ import type { AskQuestionsRequest, AskQuestionsResult, PendingInteraction, SelectRequest, ServerFrame } from "@alisio/sdk";
2
+ export interface InteractionHub {
3
+ toSession(sessionId: string, frame: ServerFrame): void;
4
+ toWorkspace(workspaceId: string, frame: ServerFrame): void;
5
+ subscribers(sessionId: string): number;
6
+ workspaceSubscribers(workspaceId: string): number;
7
+ }
8
+ export interface InteractionBridgeOptions {
9
+ hub: InteractionHub;
10
+ rootOf: (sessionId: string) => string;
11
+ /** Workspace id of a session (to match workspace-wide requests in snapshots). */
12
+ workspaceOf: (sessionId: string) => string | undefined;
13
+ /** Give up (cancel) when nobody can answer for this long (default 30 s). */
14
+ graceMs?: number;
15
+ /** Give up after this long even with subscribers (default 10 min; 0 = no limit). */
16
+ timeoutMs?: number;
17
+ /** A plugin asked to show a session (`ui.open`). */
18
+ onOpen?: (sessionId: string) => void;
19
+ onChange?: (rootSessionId: string | undefined) => void;
20
+ }
21
+ /**
22
+ * Plugin UI requests (`ui.select`, `ui.askQuestions`, `ui.open`) for web clients (RF-18). A
23
+ * request that names a session goes to its root session's streams; otherwise (every `select`)
24
+ * to every stream watching the workspace. No answer means cancelled (`undefined`), like the
25
+ * TUI without an interactive UI: fail-closed.
26
+ */
27
+ export declare class InteractionBridge {
28
+ private options;
29
+ private entries;
30
+ private recent;
31
+ private readonly graceMs;
32
+ private readonly timeoutMs;
33
+ constructor(options: InteractionBridgeOptions);
34
+ /** The `PluginHost.setInteractiveUI` implementation for one workspace app. */
35
+ uiFor(workspaceId: string): {
36
+ select(request: SelectRequest): Promise<string | undefined>;
37
+ askQuestions(request: AskQuestionsRequest): Promise<AskQuestionsResult>;
38
+ open(sessionId: string): boolean;
39
+ };
40
+ private ask;
41
+ private send;
42
+ private watched;
43
+ private watch;
44
+ subscribersChanged(): void;
45
+ private remember;
46
+ /** Answers an interaction (`null` cancels it); the first valid answer wins. */
47
+ resolve(interactionId: string, answer: unknown): "resolved" | "already_resolved" | "not_found" | "invalid";
48
+ /** Interactions a stream subscribed to `sessionId` should see. */
49
+ pending(sessionId?: string): PendingInteraction[];
50
+ awaiting(rootSessionId: string): boolean;
51
+ cancelAll(): void;
52
+ }
@@ -0,0 +1,163 @@
1
+ import { randomUUID } from "node:crypto";
2
+ const RECENT = 500;
3
+ /**
4
+ * Plugin UI requests (`ui.select`, `ui.askQuestions`, `ui.open`) for web clients (RF-18). A
5
+ * request that names a session goes to its root session's streams; otherwise (every `select`)
6
+ * to every stream watching the workspace. No answer means cancelled (`undefined`), like the
7
+ * TUI without an interactive UI: fail-closed.
8
+ */
9
+ export class InteractionBridge {
10
+ options;
11
+ entries = new Map();
12
+ recent = new Set();
13
+ graceMs;
14
+ timeoutMs;
15
+ constructor(options) {
16
+ this.options = options;
17
+ this.graceMs = options.graceMs ?? 30_000;
18
+ this.timeoutMs = options.timeoutMs ?? 10 * 60_000;
19
+ }
20
+ /** The `PluginHost.setInteractiveUI` implementation for one workspace app. */
21
+ uiFor(workspaceId) {
22
+ return {
23
+ select: (request) => this.ask({
24
+ interactionId: randomUUID(),
25
+ workspaceId,
26
+ request: { kind: "select", select: { title: request.title, options: request.options } },
27
+ }, undefined, undefined, (answer) => answer === null ||
28
+ (typeof answer === "string" && request.options.some((o) => o.value === answer)), (answer) => (typeof answer === "string" ? answer : undefined)),
29
+ askQuestions: (request) => {
30
+ const empty = () => Object.fromEntries(request.questions.map((q) => [q.id, undefined]));
31
+ const ids = new Set(request.questions.map((q) => q.id));
32
+ return this.ask({
33
+ interactionId: randomUUID(),
34
+ ...(request.session ? { sessionId: request.session } : {}),
35
+ workspaceId,
36
+ request: {
37
+ kind: "questions",
38
+ questions: request.questions,
39
+ ...(request.label ? { label: request.label } : {}),
40
+ },
41
+ }, request.signal, empty(), (answer) => answer === null ||
42
+ (!!answer &&
43
+ typeof answer === "object" &&
44
+ !Array.isArray(answer) &&
45
+ Object.entries(answer).every(([id, value]) => ids.has(id) &&
46
+ (value === null ||
47
+ typeof value === "string" ||
48
+ (Array.isArray(value) && value.every((v) => typeof v === "string"))))), (answer) => ({
49
+ ...empty(),
50
+ ...Object.fromEntries(Object.entries((answer ?? {})).map(([id, value]) => [
51
+ id,
52
+ value === null ? undefined : value,
53
+ ])),
54
+ }));
55
+ },
56
+ open: (sessionId) => {
57
+ this.options.onOpen?.(sessionId);
58
+ return this.options.hub.workspaceSubscribers(workspaceId) > 0;
59
+ },
60
+ };
61
+ }
62
+ ask(interaction, signal, cancelled, validate, convert) {
63
+ if (signal?.aborted)
64
+ return Promise.resolve(cancelled);
65
+ const root = interaction.sessionId ? this.options.rootOf(interaction.sessionId) : undefined;
66
+ return new Promise((resolve) => {
67
+ let timeout;
68
+ const onAbort = () => entry.settle(undefined);
69
+ const entry = {
70
+ interaction,
71
+ ...(root ? { root } : {}),
72
+ validate,
73
+ settle: (answer) => {
74
+ if (this.entries.get(interaction.interactionId) !== entry)
75
+ return;
76
+ this.entries.delete(interaction.interactionId);
77
+ this.remember(interaction.interactionId);
78
+ if (timeout)
79
+ clearTimeout(timeout);
80
+ if (entry.grace)
81
+ clearTimeout(entry.grace);
82
+ signal?.removeEventListener("abort", onAbort);
83
+ this.send(entry, {
84
+ t: "interaction_withdrawn",
85
+ interactionId: interaction.interactionId,
86
+ });
87
+ this.options.onChange?.(root);
88
+ resolve(answer === undefined ? cancelled : convert(answer));
89
+ },
90
+ };
91
+ this.entries.set(interaction.interactionId, entry);
92
+ signal?.addEventListener("abort", onAbort, { once: true });
93
+ if (this.timeoutMs > 0)
94
+ timeout = setTimeout(() => entry.settle(undefined), this.timeoutMs);
95
+ this.send(entry, { t: "interaction", interaction });
96
+ this.watch(entry);
97
+ this.options.onChange?.(root);
98
+ });
99
+ }
100
+ send(entry, frame) {
101
+ if (entry.root)
102
+ this.options.hub.toSession(entry.root, frame);
103
+ else
104
+ this.options.hub.toWorkspace(entry.interaction.workspaceId, frame);
105
+ }
106
+ watched(entry) {
107
+ return entry.root
108
+ ? this.options.hub.subscribers(entry.root) > 0
109
+ : this.options.hub.workspaceSubscribers(entry.interaction.workspaceId) > 0;
110
+ }
111
+ watch(entry) {
112
+ if (this.watched(entry)) {
113
+ if (entry.grace)
114
+ clearTimeout(entry.grace);
115
+ entry.grace = undefined;
116
+ }
117
+ else
118
+ entry.grace ??= setTimeout(() => {
119
+ if (!this.watched(entry))
120
+ entry.settle(undefined);
121
+ else
122
+ entry.grace = undefined;
123
+ }, this.graceMs);
124
+ }
125
+ subscribersChanged() {
126
+ for (const entry of this.entries.values())
127
+ this.watch(entry);
128
+ }
129
+ remember(id) {
130
+ this.recent.add(id);
131
+ if (this.recent.size > RECENT)
132
+ this.recent.delete(this.recent.values().next().value);
133
+ }
134
+ /** Answers an interaction (`null` cancels it); the first valid answer wins. */
135
+ resolve(interactionId, answer) {
136
+ const entry = this.entries.get(interactionId);
137
+ if (!entry)
138
+ return this.recent.has(interactionId) ? "already_resolved" : "not_found";
139
+ if (!entry.validate(answer))
140
+ return "invalid";
141
+ entry.settle(answer === null ? undefined : answer);
142
+ return "resolved";
143
+ }
144
+ /** Interactions a stream subscribed to `sessionId` should see. */
145
+ pending(sessionId) {
146
+ const workspace = sessionId ? this.options.workspaceOf(sessionId) : undefined;
147
+ return [...this.entries.values()]
148
+ .filter((entry) => !sessionId ||
149
+ entry.root === sessionId ||
150
+ (!entry.root && entry.interaction.workspaceId === workspace))
151
+ .map((entry) => entry.interaction);
152
+ }
153
+ awaiting(rootSessionId) {
154
+ for (const entry of this.entries.values())
155
+ if (entry.root === rootSessionId)
156
+ return true;
157
+ return false;
158
+ }
159
+ cancelAll() {
160
+ for (const entry of [...this.entries.values()])
161
+ entry.settle(undefined);
162
+ }
163
+ }
@@ -0,0 +1,22 @@
1
+ export interface GitResult {
2
+ code: number;
3
+ stdout: string;
4
+ }
5
+ /** Runs git in `cwd`; resolves with code -1 when git is missing, times out or overflows. */
6
+ export declare function runGit(cwd: string, args: string[],
7
+ /** `literal`: pathspecs are plain paths (no globs or magic); unsupported by check-ignore. */
8
+ options?: {
9
+ input?: string;
10
+ timeoutMs?: number;
11
+ maxBytes?: number;
12
+ literal?: boolean;
13
+ }): Promise<GitResult>;
14
+ /** Top-level directory of the repository containing `cwd`, or undefined outside git. */
15
+ export declare function gitRoot(cwd: string): Promise<string | undefined>;
16
+ /**
17
+ * `git status --porcelain=v1 -z` of the repository as `workspace-relative path → code` (`M`,
18
+ * `A`, `D`, `R`, `??`…). Paths outside `workspace` (sibling directories of a monorepo) are left out.
19
+ */
20
+ export declare function gitStatus(workspace: string, top: string): Promise<Map<string, string>>;
21
+ /** The subset of workspace-relative `paths` (directories end in `/`) that git ignores. */
22
+ export declare function gitIgnored(workspace: string, paths: string[]): Promise<Set<string>>;