@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,326 @@
1
+ /**
2
+ * Management routes (RF-14, §8.2): plugins, skills, MCP servers, agent presets and settings of
3
+ * one workspace application. Responses are path- and credential-safe: MCP commands, arguments
4
+ * and URLs are never sent, skills carry no file paths. Changes that alter the slash-command
5
+ * catalog broadcast `catalog_changed` so every open palette refreshes.
6
+ */
7
+ import { isAbsolute, join } from "node:path";
8
+ import { agentCatalogFromState, CommandCatalog, configFile, configHome, isSettableSettingKey, pluginPrefix, resolveActiveAgent, settableSettings, } from "@alisio/core";
9
+ import { readJson } from "../http/body.js";
10
+ import { HttpError } from "../http/errors.js";
11
+ import { is, validate } from "../schemas.js";
12
+ /** Resolves `?workspace=` / `{workspace}` (an opaque id or an absolute path) to its open app. */
13
+ export async function workspaceApp(workspaces, reference) {
14
+ if (!reference)
15
+ throw new HttpError("validation_failed", "workspace is required", { fields: ["workspace"] });
16
+ const path = isAbsolute(reference)
17
+ ? await workspaces.canonical(reference)
18
+ : await workspaces.pathOf(reference);
19
+ if (!path)
20
+ throw new HttpError("not_found", "Workspace not found");
21
+ return workspaces.openPath(path);
22
+ }
23
+ const UNTRUSTED = "Trust this workspace from the terminal (run alisio in it) to manage its plugins here";
24
+ const message = (error) => (error instanceof Error ? error.message : String(error));
25
+ /**
26
+ * Plugin and skill toggles persist a project override; the running app reflects plugin changes
27
+ * only after a restart. The server recycles the workspace app as soon as it has no runs (now,
28
+ * or when its last run finishes), so the change applies without restarting `alisio serve`.
29
+ */
30
+ export class WorkspaceRecycler {
31
+ ctx;
32
+ pending = new Set();
33
+ constructor(ctx) {
34
+ this.ctx = ctx;
35
+ }
36
+ /** Recycles now when idle; otherwise once the workspace's runs finish. */
37
+ async request(id) {
38
+ if (this.ctx.scheduler.busyWorkspace(id)) {
39
+ this.pending.add(id);
40
+ return undefined;
41
+ }
42
+ this.pending.delete(id);
43
+ const reopened = await this.ctx.workspaces.recycle(id);
44
+ this.announce(id, ["plugins", "commands", "skills", "agents", "mcp"]);
45
+ return reopened;
46
+ }
47
+ /** Called when a run finishes: applies a deferred recycle once nothing runs. */
48
+ idle(id) {
49
+ if (!this.pending.has(id) || this.ctx.scheduler.busyWorkspace(id))
50
+ return;
51
+ void this.request(id).catch(() => { });
52
+ }
53
+ announce(workspaceId, scopes) {
54
+ for (const scope of scopes)
55
+ this.ctx.broadcast({ t: "catalog_changed", workspaceId, scope });
56
+ }
57
+ }
58
+ function pluginInfo(opened, entry) {
59
+ const prefix = pluginPrefix(entry.id);
60
+ const tools = opened.app.plugins
61
+ .toolsOf(entry.id)
62
+ .map((name) => (name.startsWith(`${prefix}_`) ? name.slice(prefix.length + 1) : name))
63
+ .sort();
64
+ const commands = new CommandCatalog(opened.app)
65
+ .list()
66
+ .filter((c) => c.source === "plugin" && c.owner === entry.id)
67
+ .map((c) => c.name)
68
+ .sort();
69
+ const untrusted = !opened.trusted && entry.manageable;
70
+ return {
71
+ id: entry.id,
72
+ name: entry.name,
73
+ description: entry.description,
74
+ ...(entry.version ? { version: entry.version } : {}),
75
+ categories: [...entry.categories],
76
+ builtin: entry.builtin,
77
+ source: entry.source,
78
+ status: entry.status,
79
+ enabled: entry.enabled,
80
+ manageable: entry.manageable && !untrusted,
81
+ ...(untrusted
82
+ ? { diagnostic: UNTRUSTED }
83
+ : entry.diagnostic
84
+ ? { diagnostic: entry.diagnostic }
85
+ : {}),
86
+ tools,
87
+ commands,
88
+ toolPrefix: prefix,
89
+ };
90
+ }
91
+ function skillInfo(skill) {
92
+ return {
93
+ id: skill.id,
94
+ name: skill.name,
95
+ displayId: skill.displayId,
96
+ description: skill.description,
97
+ scope: skill.scope,
98
+ source: skill.source,
99
+ ...(skill.owner ? { owner: { id: skill.owner.id, name: skill.owner.name } } : {}),
100
+ manageable: skill.manageable,
101
+ locked: skill.locked,
102
+ enabled: skill.enabled,
103
+ effective: skill.effective,
104
+ ...(skill.shadowedBy ? { shadowedBy: skill.shadowedBy } : {}),
105
+ approximateTokens: skill.approximateTokens,
106
+ };
107
+ }
108
+ /** MCP server status without the command line, arguments or URL (they may carry tokens). */
109
+ function mcpWire(info) {
110
+ return {
111
+ name: info.name,
112
+ displayName: info.displayName,
113
+ source: info.source.kind,
114
+ status: info.status,
115
+ enabled: info.enabled,
116
+ transport: info.transport,
117
+ capabilities: [...info.capabilities],
118
+ counts: { ...info.counts },
119
+ ...(info.diagnostic ? { diagnostic: info.diagnostic } : {}),
120
+ };
121
+ }
122
+ function mcpOverview(opened) {
123
+ return {
124
+ permission: opened.app.mcpRuntimePermission(),
125
+ persisted: opened.app.mcpAllowPersisted(),
126
+ servers: opened.app.mcp.list().map(mcpWire),
127
+ };
128
+ }
129
+ function settingValue(config, key) {
130
+ const [section, leaf] = key.split(".");
131
+ const value = config[section]?.[leaf];
132
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean"
133
+ ? value
134
+ : undefined;
135
+ }
136
+ export function registerManagementRoutes(router, ctx, recycler) {
137
+ const open = (reference) => workspaceApp(ctx.workspaces, reference);
138
+ // ---- Plugins ------------------------------------------------------------------------------
139
+ router.get("/api/plugins", async ({ url }) => {
140
+ const opened = await open(url.searchParams.get("workspace"));
141
+ return { body: opened.app.pluginCatalog().map((entry) => pluginInfo(opened, entry)) };
142
+ });
143
+ router.patch("/api/plugins/:id", async ({ req, url, params }) => {
144
+ const input = validate(await readJson(req), {
145
+ enabled: { check: is.boolean(), required: true },
146
+ workspace: { check: is.nonEmpty(4096) },
147
+ });
148
+ let opened = await open(input.workspace ?? url.searchParams.get("workspace"));
149
+ const id = params.id ?? "";
150
+ const entry = opened.app.pluginCatalog().find((p) => p.id === id);
151
+ if (!entry)
152
+ throw new HttpError("not_found", "Plugin not found");
153
+ const info = pluginInfo(opened, entry);
154
+ if (!info.manageable)
155
+ throw new HttpError("not_manageable", info.diagnostic ?? "This plugin cannot be managed here");
156
+ let updated;
157
+ try {
158
+ updated = await opened.app.setPluginEnabled(id, input.enabled);
159
+ }
160
+ catch (error) {
161
+ throw new HttpError("validation_failed", message(error), { fields: ["enabled"] });
162
+ }
163
+ if (updated.status === "restart-required") {
164
+ const reopened = await recycler.request(opened.id);
165
+ if (reopened)
166
+ opened = reopened;
167
+ }
168
+ recycler.announce(opened.id, ["plugins", "commands"]);
169
+ const current = opened.app.pluginCatalog().find((p) => p.id === id) ?? updated;
170
+ return { body: pluginInfo(opened, current) };
171
+ });
172
+ // ---- Skills -------------------------------------------------------------------------------
173
+ router.get("/api/skills", async ({ url }) => {
174
+ const opened = await open(url.searchParams.get("workspace"));
175
+ return { body: opened.app.skillCatalog().map(skillInfo) };
176
+ });
177
+ router.patch("/api/skills/:id", async ({ req, url, params }) => {
178
+ const input = validate(await readJson(req), {
179
+ enabled: { check: is.boolean(), required: true },
180
+ workspace: { check: is.nonEmpty(4096) },
181
+ });
182
+ const opened = await open(input.workspace ?? url.searchParams.get("workspace"));
183
+ const id = params.id ?? "";
184
+ const skill = opened.app.skillCatalog().find((s) => s.id === id && s.effective);
185
+ if (!skill)
186
+ throw new HttpError("not_found", "Skill not found");
187
+ if (!skill.manageable || skill.locked)
188
+ throw new HttpError("not_manageable", "This skill is locked by its plugin");
189
+ try {
190
+ await opened.app.setSkillEnabled(id, input.enabled);
191
+ }
192
+ catch (error) {
193
+ const text = message(error);
194
+ if (/trust/i.test(text))
195
+ throw new HttpError("not_manageable", text);
196
+ throw new HttpError("validation_failed", text, { fields: ["enabled"] });
197
+ }
198
+ recycler.announce(opened.id, ["skills", "commands"]);
199
+ const current = opened.app.skillCatalog().find((s) => s.id === id && s.effective) ?? skill;
200
+ return { body: skillInfo(current) };
201
+ });
202
+ // ---- MCP ----------------------------------------------------------------------------------
203
+ router.get("/api/mcp", async ({ url }) => {
204
+ const opened = await open(url.searchParams.get("workspace"));
205
+ return { body: mcpOverview(opened) };
206
+ });
207
+ router.patch("/api/mcp/:name", async ({ req, url, params }) => {
208
+ const input = validate(await readJson(req), {
209
+ enabled: { check: is.boolean(), required: true },
210
+ connect: { check: is.boolean() },
211
+ workspace: { check: is.nonEmpty(4096) },
212
+ });
213
+ const opened = await open(input.workspace ?? url.searchParams.get("workspace"));
214
+ const name = params.name ?? "";
215
+ if (!opened.app.config.mcpSources[name])
216
+ throw new HttpError("not_found", "MCP server not found");
217
+ if (ctx.base.readOnly)
218
+ throw new HttpError("mcp_not_permitted", "MCP changes are unavailable under --read-only");
219
+ const connect = !!input.connect && input.enabled;
220
+ if (connect && opened.app.mcpRuntimePermission() !== "granted")
221
+ throw new HttpError("mcp_not_permitted", "Grant MCP access for this workspace first (MCP servers run with your user privileges)");
222
+ try {
223
+ await opened.app.setMcpEnabled(name, input.enabled, connect);
224
+ }
225
+ catch {
226
+ // A failed connection is reported through the server's status and diagnostic.
227
+ }
228
+ recycler.announce(opened.id, ["mcp"]);
229
+ return { body: mcpWire(opened.app.mcp.info(name)) };
230
+ });
231
+ router.post("/api/mcp/consent", async ({ req }) => {
232
+ const input = validate(await readJson(req), {
233
+ workspace: { check: is.nonEmpty(4096), required: true },
234
+ // Explicit confirmation is required: the UI shows what MCP access means first.
235
+ confirmed: { check: (v) => v === true, required: true },
236
+ remember: { check: is.boolean() },
237
+ });
238
+ const opened = await open(input.workspace);
239
+ if (ctx.base.readOnly)
240
+ throw new HttpError("mcp_not_permitted", "MCP is unavailable under --read-only");
241
+ try {
242
+ if (input.remember)
243
+ await opened.app.rememberGlobalMcpConsent("interactive-web");
244
+ else
245
+ opened.app.grantMcpRuntimePermission({ source: "interactive-web", confirmed: true });
246
+ }
247
+ catch (error) {
248
+ throw new HttpError("mcp_not_permitted", message(error));
249
+ }
250
+ recycler.announce(opened.id, ["mcp"]);
251
+ return { body: mcpOverview(opened) };
252
+ });
253
+ // ---- Agent presets ------------------------------------------------------------------------
254
+ router.get("/api/agents", async ({ url }) => {
255
+ const opened = await open(url.searchParams.get("workspace"));
256
+ let state;
257
+ try {
258
+ state = opened.app.plugins.pluginState("subagents", "mainAgents");
259
+ }
260
+ catch {
261
+ /* contributions are best-effort */
262
+ }
263
+ const agents = agentCatalogFromState(state);
264
+ const fallback = resolveActiveAgent(agents, opened.app.config.agents.active).id;
265
+ return {
266
+ body: agents.map((agent) => ({
267
+ id: agent.id,
268
+ name: agent.name,
269
+ description: agent.description,
270
+ ...(agent.instructions ? { instructions: agent.instructions } : {}),
271
+ ...(agent.model ? { model: agent.model } : {}),
272
+ ...(agent.readOnly ? { readOnly: true } : {}),
273
+ source: agent.source,
274
+ default: agent.id === fallback,
275
+ })),
276
+ };
277
+ });
278
+ // ---- Settings -----------------------------------------------------------------------------
279
+ router.get("/api/settings", async ({ url }) => {
280
+ const opened = await open(url.searchParams.get("workspace"));
281
+ const base = ctx.base;
282
+ const body = {
283
+ configPath: configFile(opened.path, base.config ? { file: base.config } : { trustProject: opened.trusted }),
284
+ settingsPath: join(configHome(), "config.json"),
285
+ providersPath: opened.app.providerSettings.profilesPath,
286
+ trusted: opened.trusted,
287
+ readOnly: !!base.readOnly,
288
+ settings: settableSettings().map((setting) => {
289
+ const value = settingValue(opened.app.config, setting.key);
290
+ return {
291
+ key: setting.key,
292
+ kind: setting.kind,
293
+ ...(setting.options ? { options: setting.options } : {}),
294
+ ...(value !== undefined ? { value } : {}),
295
+ };
296
+ }),
297
+ };
298
+ return { body };
299
+ });
300
+ router.patch("/api/settings", async ({ req }) => {
301
+ const input = validate(await readJson(req), {
302
+ workspace: { check: is.nonEmpty(4096), required: true },
303
+ key: { check: (v) => typeof v === "string" && isSettableSettingKey(v), required: true },
304
+ value: {
305
+ check: (v) => v === null ||
306
+ typeof v === "boolean" ||
307
+ (typeof v === "number" && Number.isFinite(v)) ||
308
+ (typeof v === "string" && v.length <= 1_000),
309
+ required: true,
310
+ },
311
+ });
312
+ const opened = await open(input.workspace);
313
+ if (ctx.base.readOnly)
314
+ throw new HttpError("capability_ceiling", "Settings changes are unavailable under --read-only");
315
+ const key = input.key;
316
+ try {
317
+ await opened.app.updateSetting(key, input.value === null ? undefined : input.value);
318
+ }
319
+ catch (error) {
320
+ throw new HttpError("validation_failed", message(error), { fields: ["value"] });
321
+ }
322
+ if (key.startsWith("agents."))
323
+ recycler.announce(opened.id, ["agents"]);
324
+ return { body: { message: `Saved ${key}` } };
325
+ });
326
+ }
@@ -0,0 +1,22 @@
1
+ import type { RunScheduler } from "../host/run-scheduler.ts";
2
+ import type { SessionService } from "../host/sessions.ts";
3
+ import type { Router } from "../http/router.ts";
4
+ /**
5
+ * In-memory idempotency for requests that do not create a run (enqueued prompts, commands):
6
+ * the last 100 request ids per session for 10 minutes. Lost on restart, like the runner's
7
+ * own enqueue queue.
8
+ */
9
+ export declare class RecentRequests {
10
+ private limit;
11
+ private ttlMs;
12
+ private seen;
13
+ constructor(limit?: number, ttlMs?: number);
14
+ has(sessionId: string, requestId: string, now?: number): boolean;
15
+ add(sessionId: string, requestId: string, now?: number): void;
16
+ }
17
+ /** Prompts (idempotent by `requestId`), cancellation and manual compaction. */
18
+ export declare function registerPromptRoutes(router: Router, ctx: {
19
+ sessions: SessionService;
20
+ scheduler: RunScheduler;
21
+ recent?: RecentRequests;
22
+ }): void;
@@ -0,0 +1,153 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readJson } from "../http/body.js";
3
+ import { HttpError } from "../http/errors.js";
4
+ import { is, validate } from "../schemas.js";
5
+ /**
6
+ * In-memory idempotency for requests that do not create a run (enqueued prompts, commands):
7
+ * the last 100 request ids per session for 10 minutes. Lost on restart, like the runner's
8
+ * own enqueue queue.
9
+ */
10
+ export class RecentRequests {
11
+ limit;
12
+ ttlMs;
13
+ seen = new Map();
14
+ constructor(limit = 100, ttlMs = 10 * 60_000) {
15
+ this.limit = limit;
16
+ this.ttlMs = ttlMs;
17
+ }
18
+ has(sessionId, requestId, now = Date.now()) {
19
+ const expires = this.seen.get(sessionId)?.get(requestId);
20
+ return expires !== undefined && expires > now;
21
+ }
22
+ add(sessionId, requestId, now = Date.now()) {
23
+ const entries = this.seen.get(sessionId) ?? new Map();
24
+ entries.set(requestId, now + this.ttlMs);
25
+ for (const [id, expires] of entries)
26
+ if (expires <= now)
27
+ entries.delete(id);
28
+ while (entries.size > this.limit)
29
+ entries.delete(entries.keys().next().value);
30
+ this.seen.set(sessionId, entries);
31
+ }
32
+ }
33
+ const isBlobRef = (value) => {
34
+ const ref = value;
35
+ return (!!ref &&
36
+ typeof ref === "object" &&
37
+ typeof ref.hash === "string" &&
38
+ /^[0-9a-f]{64}$/.test(ref.hash) &&
39
+ typeof ref.mimeType === "string" &&
40
+ typeof ref.bytes === "number");
41
+ };
42
+ /** Prompts (idempotent by `requestId`), cancellation and manual compaction. */
43
+ export function registerPromptRoutes(router, ctx) {
44
+ const { sessions, scheduler } = ctx;
45
+ const recent = ctx.recent ?? new RecentRequests();
46
+ router.post("/api/sessions/:sid/prompts", async ({ req, params, correlationId }) => {
47
+ const session = sessions.get(params.sid ?? "");
48
+ const input = validate(await readJson(req), {
49
+ requestId: { check: is.requestId(), required: true },
50
+ text: { check: is.string(200_000), required: true },
51
+ attachments: { check: is.array(isBlobRef, 16) },
52
+ display: { check: is.string(1_000) },
53
+ });
54
+ if (session.parentId)
55
+ throw new HttpError("session_busy", "Child sessions run under their parent session");
56
+ const duplicate = () => {
57
+ const run = sessions.runByRequest(session.id, input.requestId);
58
+ if (run)
59
+ return { status: 200, body: { runId: run.id, status: run.status, duplicate: true } };
60
+ if (recent.has(session.id, input.requestId))
61
+ return { status: 202, body: { status: "enqueued", duplicate: true } };
62
+ return undefined;
63
+ };
64
+ const again = duplicate();
65
+ if (again)
66
+ return again;
67
+ if (sessions.lockedBy(session.id))
68
+ throw new HttpError("session_locked", "Another Alisio process (for example the TUI) is using this session");
69
+ const opened = await sessions.app(session);
70
+ // Everything below runs in one tick: idempotency checks and the insert cannot interleave.
71
+ const retried = duplicate();
72
+ if (retried)
73
+ return retried;
74
+ const job = scheduler.job(session.id);
75
+ if (job || scheduler.busy(session.id) || opened.app.runner.isRunning(session.id)) {
76
+ if (job?.status === "queued" || scheduler.compacting(session.id))
77
+ throw new HttpError("session_busy", "The session is waiting for or compacting a run");
78
+ if (input.attachments?.length)
79
+ throw new HttpError("session_busy", "The session is running; only text can be queued (retry attachments when idle)");
80
+ opened.app.runner.enqueue(session.id, input.text);
81
+ recent.add(session.id, input.requestId);
82
+ return { status: 202, body: { status: "enqueued" } };
83
+ }
84
+ const attachments = [];
85
+ for (const ref of input.attachments ?? [])
86
+ try {
87
+ attachments.push(opened.app.blobs.attachment(ref));
88
+ }
89
+ catch {
90
+ throw new HttpError("validation_failed", "Unknown or invalid attachment", {
91
+ fields: ["attachments"],
92
+ });
93
+ }
94
+ const { run, created } = sessions.beginRun({
95
+ id: randomUUID(),
96
+ session: session.id,
97
+ status: "queued",
98
+ requestId: input.requestId,
99
+ correlationId,
100
+ model: session.model,
101
+ });
102
+ if (!created)
103
+ return {
104
+ status: 200,
105
+ body: { runId: run.id, status: run.status, duplicate: true },
106
+ };
107
+ const status = scheduler.submit({
108
+ runId: run.id,
109
+ sessionId: session.id,
110
+ workspaceId: opened.id,
111
+ app: opened.app,
112
+ text: input.text,
113
+ correlationId,
114
+ ...(attachments.length ? { attachments } : {}),
115
+ ...(input.display ? { display: input.display } : {}),
116
+ options: () => sessions.runOptions(session.id),
117
+ });
118
+ return { status: 202, body: { runId: run.id, status } };
119
+ });
120
+ router.post("/api/sessions/:sid/cancel", async ({ req, params }) => {
121
+ const session = sessions.get(params.sid ?? "");
122
+ const { runId } = validate(await readJson(req), {
123
+ runId: { check: is.nonEmpty(100) },
124
+ });
125
+ if (scheduler.cancel(session.id, runId))
126
+ return { body: { cancelled: true } };
127
+ const opened = sessions.openApp(session);
128
+ const aborted = !runId && !!opened?.app.runner.abort(session.id);
129
+ return { body: { cancelled: aborted } };
130
+ });
131
+ router.post("/api/sessions/:sid/compact", async ({ req, params }) => {
132
+ const session = sessions.get(params.sid ?? "");
133
+ const { focus } = validate(await readJson(req), {
134
+ focus: { check: is.string(2_000) },
135
+ });
136
+ if (sessions.lockedBy(session.id))
137
+ throw new HttpError("session_locked", "Another Alisio process is using this session");
138
+ const opened = await sessions.app(session);
139
+ if (scheduler.busy(session.id) || opened.app.runner.isRunning(session.id))
140
+ throw new HttpError("session_busy", "The session is running");
141
+ const controller = new AbortController();
142
+ const work = opened.app.runner.compact(session.id, {
143
+ ...(focus?.trim() ? { focus: focus.trim() } : {}),
144
+ signal: controller.signal,
145
+ });
146
+ scheduler.track(session.id, opened.id, work, {
147
+ abort: () => controller.abort(new Error("Cancelled")),
148
+ onDone: () => sessions.notify(session.id),
149
+ });
150
+ sessions.notify(session.id);
151
+ return { status: 202, body: { status: "started" } };
152
+ });
153
+ }
@@ -0,0 +1,3 @@
1
+ import type { Router } from "../http/router.ts";
2
+ import { type ManagementContext, type WorkspaceRecycler } from "./management.ts";
3
+ export declare function registerProviderRoutes(router: Router, ctx: ManagementContext, recycler: WorkspaceRecycler): void;