@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,66 @@
1
+ import { type ServerResponse } from "node:http";
2
+ import { type ServerAppOptions } from "./host/workspace-host.ts";
3
+ import { type Logger } from "./log.ts";
4
+ export { isLoopbackHost } from "./auth/guard.ts";
5
+ export { ApprovalBridge } from "./bridges/approval-bridge.ts";
6
+ export { InteractionBridge } from "./bridges/interaction-bridge.ts";
7
+ export { type Application, type OpenWorkspace, type ServerAppOptions, WorkspaceHost, workspaceId, } from "./host/workspace-host.ts";
8
+ export { HttpError } from "./http/errors.ts";
9
+ export { contentSecurityPolicy, PLACEHOLDER_HTML } from "./http/static.ts";
10
+ export { createLogger, type Logger, type LogLevel, logLevel } from "./log.ts";
11
+ export { PROTOCOL_VERSION } from "./routes/health.ts";
12
+ export interface ServerOptions {
13
+ /** Bind address (default `127.0.0.1`). Anything but loopback requires `allowRemote`. */
14
+ host?: string;
15
+ /** Port; `0` picks a free one (default 4317). */
16
+ port?: number;
17
+ /** Required to bind a non-loopback address (no TLS; prefer an SSH tunnel). */
18
+ allowRemote?: boolean;
19
+ /** Launch token (a fresh 256-bit secret by default). */
20
+ token?: string;
21
+ /** Version reported by `/api/health` (the CLI passes its own). */
22
+ version?: string;
23
+ /** Directory with the web UI build (default: `dist/web` next to the server). */
24
+ webRoot?: string;
25
+ logger?: Logger;
26
+ /**
27
+ * Base options for every workspace `Application` (launch flags, builtins, prompts, db). The
28
+ * policy flags are the capability ceiling of every web session.
29
+ */
30
+ app?: ServerAppOptions;
31
+ /** Listed as a workspace even before it has sessions (the CLI passes its cwd). */
32
+ defaultWorkspace?: string;
33
+ /** Maximum workspace applications open at once (default 4). */
34
+ maxOpenWorkspaces?: number;
35
+ /** Maximum runs executing at once across all sessions (default 4). */
36
+ maxConcurrentRuns?: number;
37
+ /** Close a workspace app after this long without activity (default 10 min). */
38
+ idleEvictMs?: number;
39
+ /** SSE heartbeat interval (default 15 s). */
40
+ heartbeatMs?: number;
41
+ /** Delta coalescing window, clamped to 16–50 ms (default 33 ms). */
42
+ coalesceMs?: number;
43
+ /** Maximum concurrent event streams (default 16). */
44
+ maxStreams?: number;
45
+ /** Deny an approval when nobody watches its session for this long (default 30 s). */
46
+ approvalGraceMs?: number;
47
+ /** Deny an approval after this long even when watched (default 10 min; 0 = no limit). */
48
+ approvalTimeoutMs?: number;
49
+ }
50
+ export interface RunningServer {
51
+ host: string;
52
+ port: number;
53
+ /** Base URL of the server, e.g. `http://127.0.0.1:4317`. */
54
+ url: string;
55
+ /** The launch token (exchanged once for the session cookie). */
56
+ token: string;
57
+ /** `url` plus `/?token=…`: what the CLI prints and opens. */
58
+ launchUrl: string;
59
+ /** Whether the web UI build is installed (otherwise a placeholder page is served). */
60
+ webInstalled: boolean;
61
+ close(): Promise<void>;
62
+ }
63
+ /** Starts the server and resolves once it listens. */
64
+ export declare function startServer(options?: ServerOptions): Promise<RunningServer>;
65
+ /** Writes a JSON response. */
66
+ export declare function sendJson(res: ServerResponse, status: number, body: unknown): void;
package/dist/index.js ADDED
@@ -0,0 +1,350 @@
1
+ /**
2
+ * @alisio/server: the local HTTP + SSE server behind `alisio serve`. Loaded only through a
3
+ * dynamic `import("@alisio/server")`, so `alisio`, `alisio run` and the TUI never pay for it.
4
+ */
5
+ import { randomUUID } from "node:crypto";
6
+ import { createServer } from "node:http";
7
+ import { dirname, join } from "node:path";
8
+ import { BlobStore, SQLiteStore, stateHome } from "@alisio/core";
9
+ import { AuthGuard, isLoopbackHost } from "./auth/guard.js";
10
+ import { ApprovalBridge } from "./bridges/approval-bridge.js";
11
+ import { InteractionBridge } from "./bridges/interaction-bridge.js";
12
+ import { RunScheduler } from "./host/run-scheduler.js";
13
+ import { SessionService } from "./host/sessions.js";
14
+ import { WorkspaceHost, workspaceId } from "./host/workspace-host.js";
15
+ import { HttpError, toApiError } from "./http/errors.js";
16
+ import { Router } from "./http/router.js";
17
+ import { StaticAssets } from "./http/static.js";
18
+ import { createLogger } from "./log.js";
19
+ import { registerApprovalRoutes } from "./routes/approvals.js";
20
+ import { registerBlobRoutes } from "./routes/blobs.js";
21
+ import { registerCommandRoutes } from "./routes/commands.js";
22
+ import { registerEventRoutes } from "./routes/events.js";
23
+ import { registerFileRoutes } from "./routes/files.js";
24
+ import { registerHealthRoutes } from "./routes/health.js";
25
+ import { registerManagementRoutes, WorkspaceRecycler } from "./routes/management.js";
26
+ import { registerPromptRoutes } from "./routes/prompts.js";
27
+ import { registerProviderRoutes } from "./routes/providers.js";
28
+ import { registerSessionViewRoutes } from "./routes/session-views.js";
29
+ import { registerSessionRoutes } from "./routes/sessions.js";
30
+ import { registerWorkspaceRoutes } from "./routes/workspaces.js";
31
+ import { SseHub } from "./sse/hub.js";
32
+ import { InflightTracker } from "./sse/inflight.js";
33
+ export { isLoopbackHost } from "./auth/guard.js";
34
+ export { ApprovalBridge } from "./bridges/approval-bridge.js";
35
+ export { InteractionBridge } from "./bridges/interaction-bridge.js";
36
+ export { WorkspaceHost, workspaceId, } from "./host/workspace-host.js";
37
+ export { HttpError } from "./http/errors.js";
38
+ export { contentSecurityPolicy, PLACEHOLDER_HTML } from "./http/static.js";
39
+ export { createLogger, logLevel } from "./log.js";
40
+ export { PROTOCOL_VERSION } from "./routes/health.js";
41
+ const REQUEST_ID = /^[A-Za-z0-9_-]{1,64}$/;
42
+ /** Security headers of §11.2 (the CSP comes from the served web build). */
43
+ function securityHeaders(res, csp, api) {
44
+ res.setHeader("Content-Security-Policy", csp);
45
+ res.setHeader("X-Content-Type-Options", "nosniff");
46
+ res.setHeader("Referrer-Policy", "no-referrer");
47
+ res.setHeader("X-Frame-Options", "DENY");
48
+ res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
49
+ res.setHeader("Cross-Origin-Resource-Policy", "same-origin");
50
+ if (api)
51
+ res.setHeader("Cache-Control", "no-store");
52
+ }
53
+ /** Starts the server and resolves once it listens. */
54
+ export async function startServer(options = {}) {
55
+ const host = options.host ?? "127.0.0.1";
56
+ if (!isLoopbackHost(host) && !options.allowRemote)
57
+ throw new Error(`Refusing to listen on ${host}: it is not a loopback address. Pass --allow-remote to expose the server on the network (no TLS; prefer an SSH tunnel).`);
58
+ const logger = options.logger ?? createLogger();
59
+ const assets = new StaticAssets(options.webRoot);
60
+ const router = new Router();
61
+ let guard;
62
+ let closing = false;
63
+ const base = options.app ?? {};
64
+ // Server-level connection to the shared session database (lists, metadata, snapshots).
65
+ const dbPath = base.db ?? join(stateHome(), "sessions.sqlite");
66
+ const catalog = new SQLiteStore(dbPath);
67
+ // The same content-addressed store every workspace app resolves attachments from.
68
+ const blobs = new BlobStore({
69
+ root: dbPath !== ":memory:" ? join(dirname(dbPath), "blobs") : join(stateHome(), "blobs"),
70
+ db: catalog.db,
71
+ });
72
+ // Startup reconciliation (RNF-09): runs and child sessions left queued/running by a dead
73
+ // process become interrupted before any client lists them.
74
+ catalog.interruptRuns();
75
+ catalog.interruptStale();
76
+ let shutdown;
77
+ let sessions;
78
+ const inflight = new InflightTracker();
79
+ let recycler;
80
+ const scheduler = new RunScheduler({
81
+ ...(options.maxConcurrentRuns ? { maxConcurrent: options.maxConcurrentRuns } : {}),
82
+ onChange: (job, status) => {
83
+ if (status === "finished") {
84
+ inflight.clear(job.sessionId, job.runId);
85
+ // A deferred plugin change applies once the workspace has no runs left.
86
+ setImmediate(() => recycler?.idle(job.workspaceId));
87
+ }
88
+ else
89
+ inflight.mark(job.sessionId, job.runId, status);
90
+ sessions?.notify(job.sessionId);
91
+ },
92
+ });
93
+ const workspaceOf = (sessionId) => {
94
+ try {
95
+ return workspaceId(catalog.get(sessionId).workspace);
96
+ }
97
+ catch {
98
+ return undefined;
99
+ }
100
+ };
101
+ let approvals;
102
+ let interactions;
103
+ const hub = new SseHub({
104
+ onSubscribersChanged: () => {
105
+ approvals?.subscribersChanged();
106
+ interactions?.subscribersChanged();
107
+ },
108
+ snapshot: (sessionId) => {
109
+ const service = sessions;
110
+ const session = service.get(sessionId);
111
+ const current = inflight.get(sessionId);
112
+ return {
113
+ t: "snapshot",
114
+ sessionId,
115
+ cursor: catalog.lastEventId(sessionId),
116
+ session: service.wire(session),
117
+ messages: catalog.messagesPage(sessionId, { limit: 50 }),
118
+ ...(current ? { inflight: current } : {}),
119
+ pending: {
120
+ approvals: approvals?.pending(sessionId) ?? [],
121
+ interactions: interactions?.pending(sessionId) ?? [],
122
+ },
123
+ };
124
+ },
125
+ messagesAfter: (sessionId, after) => catalog.messagesPage(sessionId, { after, limit: 200 }),
126
+ toolResult: (sessionId, callId) => catalog.callResult(sessionId, callId),
127
+ workspaceOf,
128
+ ...(options.heartbeatMs ? { heartbeatMs: options.heartbeatMs } : {}),
129
+ ...(options.coalesceMs ? { coalesceMs: options.coalesceMs } : {}),
130
+ });
131
+ const workspaces = new WorkspaceHost({
132
+ base,
133
+ catalog,
134
+ wire: () => ({
135
+ onEvent: (event) => {
136
+ inflight.apply(event);
137
+ hub.publish(event);
138
+ },
139
+ ...(approvals
140
+ ? { approve: approvals.handler, approveExternalDirectory: approvals.directoryHandler }
141
+ : {}),
142
+ }),
143
+ onOpen: (entry) => {
144
+ if (interactions)
145
+ entry.app.plugins.setInteractiveUI(interactions.uiFor(entry.id));
146
+ },
147
+ busy: (id) => scheduler.busyWorkspace(id) || hub.watchesWorkspace(id),
148
+ ...(options.maxOpenWorkspaces ? { maxOpen: options.maxOpenWorkspaces } : {}),
149
+ ...(options.idleEvictMs ? { idleEvictMs: options.idleEvictMs } : {}),
150
+ ...(options.defaultWorkspace ? { defaultWorkspace: options.defaultWorkspace } : {}),
151
+ });
152
+ const sweeper = setInterval(() => void workspaces.sweep().catch(() => { }), Math.min(60_000, options.idleEvictMs ?? 60_000));
153
+ sweeper.unref();
154
+ sessions = new SessionService({
155
+ catalog,
156
+ workspaces,
157
+ scheduler,
158
+ base,
159
+ broadcast: (frame) => hub.broadcast(frame),
160
+ awaitingInput: (root) => !!approvals?.awaiting(root) || !!interactions?.awaiting(root),
161
+ });
162
+ const service = sessions;
163
+ approvals = new ApprovalBridge({
164
+ hub,
165
+ rootOf: (id) => service.rootOf(id),
166
+ runOf: (id) => scheduler.job(id)?.runId,
167
+ ...(options.approvalGraceMs !== undefined ? { graceMs: options.approvalGraceMs } : {}),
168
+ ...(options.approvalTimeoutMs !== undefined ? { timeoutMs: options.approvalTimeoutMs } : {}),
169
+ onChange: (root) => service.notify(root),
170
+ });
171
+ interactions = new InteractionBridge({
172
+ hub,
173
+ rootOf: (id) => service.rootOf(id),
174
+ workspaceOf,
175
+ ...(options.approvalGraceMs !== undefined ? { graceMs: options.approvalGraceMs } : {}),
176
+ ...(options.approvalTimeoutMs !== undefined ? { timeoutMs: options.approvalTimeoutMs } : {}),
177
+ onOpen: (id) => service.notify(id),
178
+ onChange: (root) => root && service.notify(root),
179
+ });
180
+ const stats = () => ({
181
+ shuttingDown: closing,
182
+ activeRuns: scheduler.counts().active,
183
+ queuedRuns: scheduler.counts().queued,
184
+ openWorkspaces: workspaces.openCount,
185
+ subscribers: hub.size,
186
+ pendingApprovals: approvals?.size ?? 0,
187
+ sseDropped: hub.dropped,
188
+ });
189
+ registerHealthRoutes(router, {
190
+ version: options.version ?? "dev",
191
+ remote: !isLoopbackHost(host),
192
+ stats,
193
+ });
194
+ registerWorkspaceRoutes(router, { workspaces, catalog });
195
+ registerSessionRoutes(router, { catalog, workspaces, sessions, scheduler });
196
+ registerSessionViewRoutes(router, { catalog, sessions });
197
+ registerFileRoutes(router, { workspaces, catalog, sessions });
198
+ registerBlobRoutes(router, { blobs });
199
+ registerPromptRoutes(router, { sessions, scheduler });
200
+ registerCommandRoutes(router, { sessions, scheduler, workspaces });
201
+ registerApprovalRoutes(router, { approvals, interactions });
202
+ const management = {
203
+ workspaces,
204
+ scheduler,
205
+ base,
206
+ broadcast: (frame) => hub.broadcast(frame),
207
+ };
208
+ recycler = new WorkspaceRecycler(management);
209
+ registerManagementRoutes(router, management, recycler);
210
+ registerProviderRoutes(router, management, recycler);
211
+ registerEventRoutes(router, {
212
+ hub,
213
+ sessions,
214
+ ...(options.maxStreams ? { maxStreams: options.maxStreams } : {}),
215
+ });
216
+ const server = createServer((req, res) => void handle(req, res));
217
+ async function handle(req, res) {
218
+ const started = Date.now();
219
+ const header = req.headers["x-request-id"];
220
+ const correlationId = typeof header === "string" && REQUEST_ID.test(header) ? header : randomUUID();
221
+ res.setHeader("X-Request-Id", correlationId);
222
+ const url = new URL(req.url ?? "/", "http://localhost");
223
+ const api = url.pathname === "/api" || url.pathname.startsWith("/api/");
224
+ securityHeaders(res, assets.csp, api);
225
+ const method = req.method ?? "GET";
226
+ try {
227
+ if (!guard)
228
+ throw new HttpError("shutting_down", "Server is not ready");
229
+ guard.checkHost(req);
230
+ guard.checkOrigin(req);
231
+ if (!api) {
232
+ if (method !== "GET" && method !== "HEAD")
233
+ throw new HttpError("not_found", "Not found");
234
+ const token = url.searchParams.get("token");
235
+ if (url.pathname === "/" && token !== null) {
236
+ if (!guard.validToken(token))
237
+ throw new HttpError("unauthorized", "Invalid token");
238
+ res.writeHead(303, { Location: "/", "Set-Cookie": guard.sessionCookie() });
239
+ res.end();
240
+ return;
241
+ }
242
+ await assets.serve(res, url.pathname, method === "HEAD");
243
+ return;
244
+ }
245
+ const matched = router.match(method, url.pathname);
246
+ if (!matched)
247
+ throw new HttpError("not_found", "Not found");
248
+ const isHealth = url.pathname === "/api/health" && method === "GET";
249
+ if (!isHealth && !guard.authenticated(req))
250
+ throw new HttpError("unauthorized", "Open the launch URL printed by `alisio serve`");
251
+ if (matched === "method")
252
+ throw new HttpError("not_found", "Method not supported");
253
+ // Every write is JSON (it forces a CORS preflight) except the raw image upload, which
254
+ // checks its own image content types (also non-safelisted, so also preflighted).
255
+ const upload = method === "POST" && url.pathname === "/api/blobs";
256
+ if (!["GET", "HEAD", "OPTIONS"].includes(method) && !upload) {
257
+ const type = String(req.headers["content-type"] ?? "")
258
+ .split(";")[0]
259
+ ?.trim();
260
+ if (type !== "application/json")
261
+ throw new HttpError("unsupported_media_type", "Content-Type must be application/json");
262
+ }
263
+ if (closing && !isHealth && url.pathname !== "/api/ready")
264
+ throw new HttpError("shutting_down", "Server is shutting down");
265
+ const ctx = { req, res, url, params: matched.params, correlationId };
266
+ const result = await matched.handler(ctx);
267
+ if (result)
268
+ sendJson(res, result.status ?? 200, result.body);
269
+ }
270
+ catch (error) {
271
+ const { status, body } = toApiError(error, correlationId);
272
+ if (status === 500)
273
+ logger.error("request failed", {
274
+ correlationId,
275
+ route: url.pathname,
276
+ error: error instanceof Error ? error.message : String(error),
277
+ });
278
+ if (!res.headersSent)
279
+ sendJson(res, status, body);
280
+ else
281
+ res.end();
282
+ }
283
+ finally {
284
+ logger.debug("request", {
285
+ correlationId,
286
+ route: `${method} ${url.pathname}`,
287
+ status: res.statusCode,
288
+ ms: Date.now() - started,
289
+ });
290
+ }
291
+ }
292
+ try {
293
+ await new Promise((resolve, reject) => {
294
+ server.once("error", reject);
295
+ server.listen(options.port ?? 4317, host, () => {
296
+ server.off("error", reject);
297
+ resolve();
298
+ });
299
+ });
300
+ }
301
+ catch (error) {
302
+ clearInterval(sweeper);
303
+ catalog.close();
304
+ throw error;
305
+ }
306
+ const port = server.address().port;
307
+ guard = new AuthGuard({ host, port, ...(options.token ? { token: options.token } : {}) });
308
+ const displayHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
309
+ const url = `http://${displayHost.includes(":") ? `[${displayHost}]` : displayHost}:${port}`;
310
+ return {
311
+ host,
312
+ port,
313
+ url,
314
+ token: guard.token,
315
+ launchUrl: `${url}/?token=${guard.token}`,
316
+ webInstalled: assets.installed,
317
+ close() {
318
+ shutdown ??= (async () => {
319
+ // RNF-08: refuse new work (503), abort runs (queued ones are journaled cancelled),
320
+ // withdraw approvals (deny), end the streams, close every app within the core's
321
+ // per-stage teardown caps, then stop listening. Locks are released by the runner.
322
+ const began = Date.now();
323
+ closing = true;
324
+ clearInterval(sweeper);
325
+ const settling = scheduler.shutdown(2_000);
326
+ approvals?.denyAll();
327
+ interactions?.cancelAll();
328
+ await settling;
329
+ hub.closeAll();
330
+ await workspaces.closeAll();
331
+ await new Promise((resolve) => {
332
+ server.closeAllConnections();
333
+ server.close(() => resolve());
334
+ });
335
+ catalog.close();
336
+ logger.info("server stopped", { ms: Date.now() - began });
337
+ })();
338
+ return shutdown;
339
+ },
340
+ };
341
+ }
342
+ /** Writes a JSON response. */
343
+ export function sendJson(res, status, body) {
344
+ const text = JSON.stringify(body);
345
+ res.writeHead(status, {
346
+ "Content-Type": "application/json; charset=utf-8",
347
+ "Content-Length": Buffer.byteLength(text),
348
+ });
349
+ res.end(text);
350
+ }
package/dist/log.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /** Structured JSON-per-line logs to stderr (RNF-13). No OpenTelemetry. */
2
+ export type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
3
+ export interface LogFields {
4
+ correlationId?: string;
5
+ sessionId?: string;
6
+ runId?: string;
7
+ route?: string;
8
+ status?: number;
9
+ ms?: number;
10
+ [key: string]: unknown;
11
+ }
12
+ export interface Logger {
13
+ debug(msg: string, fields?: LogFields): void;
14
+ info(msg: string, fields?: LogFields): void;
15
+ warn(msg: string, fields?: LogFields): void;
16
+ error(msg: string, fields?: LogFields): void;
17
+ }
18
+ /** Parses `ALISIO_LOG_LEVEL`; anything unknown falls back to `info`. */
19
+ export declare function logLevel(value?: string | undefined): LogLevel;
20
+ /** A logger that writes one JSON object per line through `write` (stderr by default). */
21
+ export declare function createLogger(level?: LogLevel, write?: (line: string) => void): Logger;
package/dist/log.js ADDED
@@ -0,0 +1,19 @@
1
+ const ORDER = { debug: 10, info: 20, warn: 30, error: 40, silent: 99 };
2
+ /** Parses `ALISIO_LOG_LEVEL`; anything unknown falls back to `info`. */
3
+ export function logLevel(value = process.env.ALISIO_LOG_LEVEL) {
4
+ return value && value in ORDER ? value : "info";
5
+ }
6
+ /** A logger that writes one JSON object per line through `write` (stderr by default). */
7
+ export function createLogger(level = logLevel(), write = (line) => process.stderr.write(line)) {
8
+ const emit = (at) => (msg, fields = {}) => {
9
+ if (ORDER[at] < ORDER[level])
10
+ return;
11
+ try {
12
+ write(`${JSON.stringify({ ts: new Date().toISOString(), level: at, msg, ...fields })}\n`);
13
+ }
14
+ catch {
15
+ /* Logging never breaks a request. */
16
+ }
17
+ };
18
+ return { debug: emit("debug"), info: emit("info"), warn: emit("warn"), error: emit("error") };
19
+ }
@@ -0,0 +1,8 @@
1
+ import type { ApprovalBridge } from "../bridges/approval-bridge.ts";
2
+ import type { InteractionBridge } from "../bridges/interaction-bridge.ts";
3
+ import type { Router } from "../http/router.ts";
4
+ /** Approvals and plugin interactions answered from the web (first answer wins). */
5
+ export declare function registerApprovalRoutes(router: Router, ctx: {
6
+ approvals: ApprovalBridge;
7
+ interactions: InteractionBridge;
8
+ }): void;
@@ -0,0 +1,33 @@
1
+ import { readJson } from "../http/body.js";
2
+ import { HttpError } from "../http/errors.js";
3
+ import { is, validate } from "../schemas.js";
4
+ /** Approvals and plugin interactions answered from the web (first answer wins). */
5
+ export function registerApprovalRoutes(router, ctx) {
6
+ router.get("/api/approvals", ({ url }) => ({
7
+ body: ctx.approvals.pending(url.searchParams.get("session") ?? undefined),
8
+ }));
9
+ router.post("/api/approvals/:aid", async ({ req, params }) => {
10
+ const { decision } = validate(await readJson(req), {
11
+ decision: { check: is.oneOf(["once", "session", "deny"]), required: true },
12
+ });
13
+ const outcome = ctx.approvals.resolve(params.aid ?? "", decision);
14
+ if (outcome === "not_found")
15
+ throw new HttpError("not_found", "Approval not found");
16
+ if (outcome === "already_resolved")
17
+ throw new HttpError("approval_resolved", "This approval was already answered");
18
+ return { body: { resolved: true } };
19
+ });
20
+ router.post("/api/interactions/:iid", async ({ req, params }) => {
21
+ const { answer } = validate(await readJson(req), {
22
+ answer: { check: is.any(), required: true },
23
+ });
24
+ const outcome = ctx.interactions.resolve(params.iid ?? "", answer);
25
+ if (outcome === "not_found")
26
+ throw new HttpError("not_found", "Interaction not found");
27
+ if (outcome === "already_resolved")
28
+ throw new HttpError("approval_resolved", "This interaction was already answered");
29
+ if (outcome === "invalid")
30
+ throw new HttpError("validation_failed", "Invalid answer", { fields: ["answer"] });
31
+ return { body: { resolved: true } };
32
+ });
33
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Attachment uploads (RF-06, spec §8.2/§11.1): `POST /api/blobs` takes the raw image bytes
3
+ * (≤ 10 MB), trusts only their magic bytes (PNG, JPEG, GIF, WebP), stores them once in the
4
+ * content-addressed `BlobStore` and answers the `BlobRef` a prompt then carries. `GET` serves
5
+ * them back with the stored type, inline and immutable.
6
+ */
7
+ import type { BlobStore } from "@alisio/core";
8
+ import type { Router } from "../http/router.ts";
9
+ /** Per-image upload limit (spec RF-06). */
10
+ export declare const MAX_BLOB_BYTES: number;
11
+ export declare function registerBlobRoutes(router: Router, ctx: {
12
+ blobs: BlobStore;
13
+ }): void;
@@ -0,0 +1,46 @@
1
+ import { readBody } from "../http/body.js";
2
+ import { HttpError } from "../http/errors.js";
3
+ import { sniffImage } from "./images.js";
4
+ /** Per-image upload limit (spec RF-06). */
5
+ export const MAX_BLOB_BYTES = 10 * 1024 * 1024;
6
+ /** Declared types an upload may carry; the stored type always comes from the bytes. */
7
+ const DECLARED = new Set([
8
+ "image/png",
9
+ "image/jpeg",
10
+ "image/gif",
11
+ "image/webp",
12
+ "application/octet-stream",
13
+ ]);
14
+ export function registerBlobRoutes(router, ctx) {
15
+ router.post("/api/blobs", async ({ req }) => {
16
+ const declared = String(req.headers["content-type"] ?? "")
17
+ .split(";")[0]
18
+ ?.trim()
19
+ .toLowerCase();
20
+ if (!declared || !DECLARED.has(declared)) {
21
+ req.resume();
22
+ throw new HttpError("unsupported_media_type", "Upload PNG, JPEG, GIF or WebP images as the raw request body");
23
+ }
24
+ const bytes = await readBody(req, MAX_BLOB_BYTES);
25
+ if (!bytes.length)
26
+ throw new HttpError("validation_failed", "The upload is empty");
27
+ const image = sniffImage(bytes);
28
+ if (!image)
29
+ throw new HttpError("unsupported_media_type", "The file is not a PNG, JPEG, GIF or WebP image");
30
+ const { mimeType, ...dims } = image;
31
+ return { status: 201, body: ctx.blobs.put(bytes, mimeType, dims) };
32
+ });
33
+ router.get("/api/blobs/:hash", ({ params, res }) => {
34
+ const found = ctx.blobs.get(params.hash ?? "");
35
+ if (!found || !found.mimeType.startsWith("image/"))
36
+ throw new HttpError("not_found", "Blob not found");
37
+ res.writeHead(200, {
38
+ "Content-Type": found.mimeType,
39
+ "Content-Length": found.bytes.length,
40
+ "Content-Disposition": "inline",
41
+ "Cache-Control": "private, max-age=31536000, immutable",
42
+ });
43
+ res.end(found.bytes);
44
+ return undefined;
45
+ });
46
+ }
@@ -0,0 +1,21 @@
1
+ import type { RunScheduler } from "../host/run-scheduler.ts";
2
+ import type { SessionService } from "../host/sessions.ts";
3
+ import type { WorkspaceHost } from "../host/workspace-host.ts";
4
+ import type { Router } from "../http/router.ts";
5
+ import { RecentRequests } from "./prompts.ts";
6
+ /**
7
+ * The `/ask` prompt: the TUI sends the same instruction (packages/cli/src/tui/app.ts); both turn
8
+ * the user's question into a multiple-choice `ask_user_question` call.
9
+ */
10
+ export declare const askPrompt: (question: string) => string;
11
+ /**
12
+ * The shared slash-command catalog for the web (RF-07): `GET /api/commands` lists the commands
13
+ * whose surfaces include `web`; `POST /api/sessions/:sid/commands` runs core commands and expands
14
+ * prompt templates, skills and `/ask` into a prompt the client sends itself.
15
+ */
16
+ export declare function registerCommandRoutes(router: Router, ctx: {
17
+ sessions: SessionService;
18
+ scheduler: RunScheduler;
19
+ workspaces: WorkspaceHost;
20
+ recent?: RecentRequests;
21
+ }): void;