libre-webui 0.36.0 → 0.37.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 (379) hide show
  1. package/README.md +59 -0
  2. package/backend/dist/cordis/contracts.d.ts +270 -0
  3. package/backend/dist/cordis/contracts.d.ts.map +1 -0
  4. package/backend/dist/cordis/contracts.js +32 -0
  5. package/backend/dist/cordis/contracts.js.map +1 -0
  6. package/backend/dist/cordis/dsh/chat-model.d.ts +16 -0
  7. package/backend/dist/cordis/dsh/chat-model.d.ts.map +1 -0
  8. package/backend/dist/cordis/dsh/chat-model.js +104 -0
  9. package/backend/dist/cordis/dsh/chat-model.js.map +1 -0
  10. package/backend/dist/cordis/dsh/engine-plugin.d.ts +186 -0
  11. package/backend/dist/cordis/dsh/engine-plugin.d.ts.map +1 -0
  12. package/backend/dist/cordis/dsh/engine-plugin.js +1116 -0
  13. package/backend/dist/cordis/dsh/engine-plugin.js.map +1 -0
  14. package/backend/dist/cordis/dsh/librewebui-llm-adapter.d.ts +175 -0
  15. package/backend/dist/cordis/dsh/librewebui-llm-adapter.d.ts.map +1 -0
  16. package/backend/dist/cordis/dsh/librewebui-llm-adapter.js +480 -0
  17. package/backend/dist/cordis/dsh/librewebui-llm-adapter.js.map +1 -0
  18. package/backend/dist/cordis/dsh/model-identity.d.ts +12 -0
  19. package/backend/dist/cordis/dsh/model-identity.d.ts.map +1 -0
  20. package/backend/dist/cordis/dsh/model-identity.js +87 -0
  21. package/backend/dist/cordis/dsh/model-identity.js.map +1 -0
  22. package/backend/dist/cordis/dsh/native-provider-client.d.ts +44 -0
  23. package/backend/dist/cordis/dsh/native-provider-client.d.ts.map +1 -0
  24. package/backend/dist/cordis/dsh/native-provider-client.js +524 -0
  25. package/backend/dist/cordis/dsh/native-provider-client.js.map +1 -0
  26. package/backend/dist/cordis/dsh/native-provider-plugin.d.ts +11 -0
  27. package/backend/dist/cordis/dsh/native-provider-plugin.d.ts.map +1 -0
  28. package/backend/dist/cordis/dsh/native-provider-plugin.js +416 -0
  29. package/backend/dist/cordis/dsh/native-provider-plugin.js.map +1 -0
  30. package/backend/dist/cordis/dsh/native-provider-protocol.d.ts +37 -0
  31. package/backend/dist/cordis/dsh/native-provider-protocol.d.ts.map +1 -0
  32. package/backend/dist/cordis/dsh/native-provider-protocol.js +325 -0
  33. package/backend/dist/cordis/dsh/native-provider-protocol.js.map +1 -0
  34. package/backend/dist/cordis/dsh/provider-handler.d.ts +36 -0
  35. package/backend/dist/cordis/dsh/provider-handler.d.ts.map +1 -0
  36. package/backend/dist/cordis/dsh/provider-handler.js +582 -0
  37. package/backend/dist/cordis/dsh/provider-handler.js.map +1 -0
  38. package/backend/dist/cordis/dsh/session-files.d.ts +9 -0
  39. package/backend/dist/cordis/dsh/session-files.d.ts.map +1 -0
  40. package/backend/dist/cordis/dsh/session-files.js +60 -0
  41. package/backend/dist/cordis/dsh/session-files.js.map +1 -0
  42. package/backend/dist/cordis/dsh/session-settings.d.ts +15 -0
  43. package/backend/dist/cordis/dsh/session-settings.d.ts.map +1 -0
  44. package/backend/dist/cordis/dsh/session-settings.js +51 -0
  45. package/backend/dist/cordis/dsh/session-settings.js.map +1 -0
  46. package/backend/dist/cordis/dsh/work-driver.d.ts +12 -0
  47. package/backend/dist/cordis/dsh/work-driver.d.ts.map +1 -0
  48. package/backend/dist/cordis/dsh/work-driver.js +402 -0
  49. package/backend/dist/cordis/dsh/work-driver.js.map +1 -0
  50. package/backend/dist/cordis/dsh/workspace-policy.d.ts +10 -0
  51. package/backend/dist/cordis/dsh/workspace-policy.d.ts.map +1 -0
  52. package/backend/dist/cordis/dsh/workspace-policy.js +44 -0
  53. package/backend/dist/cordis/dsh/workspace-policy.js.map +1 -0
  54. package/backend/dist/cordis/host/composition.d.ts +73 -0
  55. package/backend/dist/cordis/host/composition.d.ts.map +1 -0
  56. package/backend/dist/cordis/host/composition.js +205 -0
  57. package/backend/dist/cordis/host/composition.js.map +1 -0
  58. package/backend/dist/cordis/host/config.d.ts +159 -0
  59. package/backend/dist/cordis/host/config.d.ts.map +1 -0
  60. package/backend/dist/cordis/host/config.js +184 -0
  61. package/backend/dist/cordis/host/config.js.map +1 -0
  62. package/backend/dist/cordis/host/host.d.ts +74 -0
  63. package/backend/dist/cordis/host/host.d.ts.map +1 -0
  64. package/backend/dist/cordis/host/host.js +420 -0
  65. package/backend/dist/cordis/host/host.js.map +1 -0
  66. package/backend/dist/cordis/host/loader-entry.d.ts +41 -0
  67. package/backend/dist/cordis/host/loader-entry.d.ts.map +1 -0
  68. package/backend/dist/cordis/host/loader-entry.js +52 -0
  69. package/backend/dist/cordis/host/loader-entry.js.map +1 -0
  70. package/backend/dist/cordis/host/model.d.ts +45 -0
  71. package/backend/dist/cordis/host/model.d.ts.map +1 -0
  72. package/backend/dist/cordis/host/model.js +54 -0
  73. package/backend/dist/cordis/host/model.js.map +1 -0
  74. package/backend/dist/cordis/host/privacy.d.ts +8 -0
  75. package/backend/dist/cordis/host/privacy.d.ts.map +1 -0
  76. package/backend/dist/cordis/host/privacy.js +73 -0
  77. package/backend/dist/cordis/host/privacy.js.map +1 -0
  78. package/backend/dist/cordis/index.d.ts +14 -0
  79. package/backend/dist/cordis/index.d.ts.map +1 -0
  80. package/backend/dist/cordis/index.js +30 -0
  81. package/backend/dist/cordis/index.js.map +1 -0
  82. package/backend/dist/cordis/runtime.d.ts +97 -0
  83. package/backend/dist/cordis/runtime.d.ts.map +1 -0
  84. package/backend/dist/cordis/runtime.js +231 -0
  85. package/backend/dist/cordis/runtime.js.map +1 -0
  86. package/backend/dist/cordis/work-model.d.ts +7 -0
  87. package/backend/dist/cordis/work-model.d.ts.map +1 -0
  88. package/backend/dist/cordis/work-model.js +30 -0
  89. package/backend/dist/cordis/work-model.js.map +1 -0
  90. package/backend/dist/db.js +1 -1
  91. package/backend/dist/index.d.ts.map +1 -1
  92. package/backend/dist/index.js +9 -0
  93. package/backend/dist/index.js.map +1 -1
  94. package/backend/dist/main.js +12 -0
  95. package/backend/dist/main.js.map +1 -1
  96. package/backend/dist/persistence/extensionTypes.d.ts +2 -0
  97. package/backend/dist/persistence/extensionTypes.d.ts.map +1 -1
  98. package/backend/dist/persistence/extensionTypes.js.map +1 -1
  99. package/backend/dist/persistence/postgresExtensionRepositories.d.ts.map +1 -1
  100. package/backend/dist/persistence/postgresExtensionRepositories.js +38 -0
  101. package/backend/dist/persistence/postgresExtensionRepositories.js.map +1 -1
  102. package/backend/dist/persistence/postgresResourceRepositories.d.ts.map +1 -1
  103. package/backend/dist/persistence/postgresResourceRepositories.js +6 -1
  104. package/backend/dist/persistence/postgresResourceRepositories.js.map +1 -1
  105. package/backend/dist/persistence/resourceTypes.d.ts +3 -1
  106. package/backend/dist/persistence/resourceTypes.d.ts.map +1 -1
  107. package/backend/dist/persistence/resourceTypes.js.map +1 -1
  108. package/backend/dist/persistence/sqliteExtensionRepositories.d.ts.map +1 -1
  109. package/backend/dist/persistence/sqliteExtensionRepositories.js +40 -0
  110. package/backend/dist/persistence/sqliteExtensionRepositories.js.map +1 -1
  111. package/backend/dist/persistence/sqliteResourceRepositories.d.ts.map +1 -1
  112. package/backend/dist/persistence/sqliteResourceRepositories.js +7 -1
  113. package/backend/dist/persistence/sqliteResourceRepositories.js.map +1 -1
  114. package/backend/dist/routes/agentCli.d.ts.map +1 -1
  115. package/backend/dist/routes/agentCli.js +34 -2
  116. package/backend/dist/routes/agentCli.js.map +1 -1
  117. package/backend/dist/routes/chat.d.ts.map +1 -1
  118. package/backend/dist/routes/chat.js +9 -1
  119. package/backend/dist/routes/chat.js.map +1 -1
  120. package/backend/dist/routes/cordis.d.ts +3 -0
  121. package/backend/dist/routes/cordis.d.ts.map +1 -0
  122. package/backend/dist/routes/cordis.js +440 -0
  123. package/backend/dist/routes/cordis.js.map +1 -0
  124. package/backend/dist/routes/libreClaw.js +1 -1
  125. package/backend/dist/routes/libreClaw.js.map +1 -1
  126. package/backend/dist/routes/work.d.ts.map +1 -1
  127. package/backend/dist/routes/work.js +23 -8
  128. package/backend/dist/routes/work.js.map +1 -1
  129. package/backend/dist/services/agentAccessService.d.ts +4 -0
  130. package/backend/dist/services/agentAccessService.d.ts.map +1 -1
  131. package/backend/dist/services/agentAccessService.js +48 -16
  132. package/backend/dist/services/agentAccessService.js.map +1 -1
  133. package/backend/dist/services/agentCliService.d.ts +42 -1
  134. package/backend/dist/services/agentCliService.d.ts.map +1 -1
  135. package/backend/dist/services/agentCliService.js +206 -7
  136. package/backend/dist/services/agentCliService.js.map +1 -1
  137. package/backend/dist/services/agentCliUsage.d.ts +22 -0
  138. package/backend/dist/services/agentCliUsage.d.ts.map +1 -0
  139. package/backend/dist/services/agentCliUsage.js +192 -0
  140. package/backend/dist/services/agentCliUsage.js.map +1 -0
  141. package/backend/dist/services/authService.d.ts +3 -0
  142. package/backend/dist/services/authService.d.ts.map +1 -1
  143. package/backend/dist/services/authService.js +4 -1
  144. package/backend/dist/services/authService.js.map +1 -1
  145. package/backend/dist/services/chatGenerationService.d.ts +7 -0
  146. package/backend/dist/services/chatGenerationService.d.ts.map +1 -1
  147. package/backend/dist/services/chatGenerationService.js +32 -1
  148. package/backend/dist/services/chatGenerationService.js.map +1 -1
  149. package/backend/dist/services/cordisAccessService.d.ts +56 -0
  150. package/backend/dist/services/cordisAccessService.d.ts.map +1 -0
  151. package/backend/dist/services/cordisAccessService.js +98 -0
  152. package/backend/dist/services/cordisAccessService.js.map +1 -0
  153. package/backend/dist/services/durableChatGenerationService.d.ts.map +1 -1
  154. package/backend/dist/services/durableChatGenerationService.js +9 -1
  155. package/backend/dist/services/durableChatGenerationService.js.map +1 -1
  156. package/backend/dist/services/emailService.d.ts.map +1 -1
  157. package/backend/dist/services/emailService.js +8 -1
  158. package/backend/dist/services/emailService.js.map +1 -1
  159. package/backend/dist/services/pluginService.d.ts.map +1 -1
  160. package/backend/dist/services/pluginService.js +7 -26
  161. package/backend/dist/services/pluginService.js.map +1 -1
  162. package/backend/dist/services/pluginUsageService.d.ts +52 -0
  163. package/backend/dist/services/pluginUsageService.d.ts.map +1 -1
  164. package/backend/dist/services/pluginUsageService.js +89 -1
  165. package/backend/dist/services/pluginUsageService.js.map +1 -1
  166. package/backend/dist/services/systemSettingsService.d.ts +1 -1
  167. package/backend/dist/services/systemSettingsService.d.ts.map +1 -1
  168. package/backend/dist/services/systemSettingsService.js +1 -1
  169. package/backend/dist/services/systemSettingsService.js.map +1 -1
  170. package/backend/dist/services/thinkingSummaryService.d.ts +3 -0
  171. package/backend/dist/services/thinkingSummaryService.d.ts.map +1 -1
  172. package/backend/dist/services/thinkingSummaryService.js +56 -17
  173. package/backend/dist/services/thinkingSummaryService.js.map +1 -1
  174. package/backend/dist/services/titleGenerationService.d.ts +22 -2
  175. package/backend/dist/services/titleGenerationService.d.ts.map +1 -1
  176. package/backend/dist/services/titleGenerationService.js +63 -8
  177. package/backend/dist/services/titleGenerationService.js.map +1 -1
  178. package/backend/dist/services/workAgentService.d.ts +1 -1
  179. package/backend/dist/services/workAgentService.d.ts.map +1 -1
  180. package/backend/dist/services/workAgentService.js +76 -12
  181. package/backend/dist/services/workAgentService.js.map +1 -1
  182. package/backend/dist/services/workModelProviderService.d.ts +5 -0
  183. package/backend/dist/services/workModelProviderService.d.ts.map +1 -1
  184. package/backend/dist/services/workModelProviderService.js +52 -3
  185. package/backend/dist/services/workModelProviderService.js.map +1 -1
  186. package/backend/dist/services/workRuntimeService.d.ts.map +1 -1
  187. package/backend/dist/services/workRuntimeService.js +4 -1
  188. package/backend/dist/services/workRuntimeService.js.map +1 -1
  189. package/backend/dist/services/workTaskService.d.ts.map +1 -1
  190. package/backend/dist/services/workTaskService.js +6 -6
  191. package/backend/dist/services/workTaskService.js.map +1 -1
  192. package/backend/dist/types/work.d.ts +12 -1
  193. package/backend/dist/types/work.d.ts.map +1 -1
  194. package/backend/dist/utils/pluginStreamAdapter.d.ts.map +1 -1
  195. package/backend/dist/utils/pluginStreamAdapter.js +18 -4
  196. package/backend/dist/utils/pluginStreamAdapter.js.map +1 -1
  197. package/backend/dist/utils/pluginStreaming.d.ts +6 -0
  198. package/backend/dist/utils/pluginStreaming.d.ts.map +1 -1
  199. package/backend/dist/utils/pluginStreaming.js +10 -0
  200. package/backend/dist/utils/pluginStreaming.js.map +1 -1
  201. package/backend/dist/utils/smtpClient.d.ts +5 -1
  202. package/backend/dist/utils/smtpClient.d.ts.map +1 -1
  203. package/backend/dist/utils/smtpClient.js +29 -3
  204. package/backend/dist/utils/smtpClient.js.map +1 -1
  205. package/backend/dist/websocketServer.d.ts.map +1 -1
  206. package/backend/dist/websocketServer.js +4 -0
  207. package/backend/dist/websocketServer.js.map +1 -1
  208. package/backend/package.json +37 -2
  209. package/frontend/dist/artifact-runtime/.build-fingerprint +1 -1
  210. package/frontend/dist/artifact-runtime/framer-motion.js +3 -3
  211. package/frontend/dist/artifact-runtime/lucide-react.js +1 -1
  212. package/frontend/dist/artifact-runtime/react.js +5 -5
  213. package/frontend/dist/artifact-runtime/recharts.js +45 -45
  214. package/frontend/dist/assets/index-BrDSO609.js +20 -0
  215. package/frontend/dist/assets/index-C8_AByBq.css +1 -0
  216. package/frontend/dist/index.html +33 -31
  217. package/frontend/dist/js/{ArtifactContainer-B8tyPtWd.js → ArtifactContainer-B1-GVzVf.js} +1 -1
  218. package/frontend/dist/js/{ArtifactDemoPage-BwRfWoSK.js → ArtifactDemoPage-goftRHdk.js} +1 -1
  219. package/frontend/dist/js/{ArtifactSandboxFrame-Ctcwt_l2.js → ArtifactSandboxFrame-_8RA8_JV.js} +1 -1
  220. package/frontend/dist/js/ArtifactSlideOutPanel-DlkOZI5N.js +1 -0
  221. package/frontend/dist/js/{AutomationModal-mTYs9LrJ.js → AutomationModal-BaaxZF1M.js} +1 -1
  222. package/frontend/dist/js/AutomationsPage-DY2x7zVm.js +1 -0
  223. package/frontend/dist/js/{AvatarUpload-wWUAi3e2.js → AvatarUpload-CH_n1Ef0.js} +1 -1
  224. package/frontend/dist/js/{Button-ZQ6BePid.js → Button-CGMYia4g.js} +1 -1
  225. package/frontend/dist/js/{CalendarPage-Ch4racU2.js → CalendarPage-DLo5nm3Q.js} +1 -1
  226. package/frontend/dist/js/ChannelsPage-DnQI7mzq.js +1 -0
  227. package/frontend/dist/js/ChatPage-BaDkJnEC.js +14 -0
  228. package/frontend/dist/js/ConnectModels-B4dGb5yc.js +1 -0
  229. package/frontend/dist/js/CordisPage-CPiYn2Bx.js +1 -0
  230. package/frontend/dist/js/{EvaluationsPage-DHPnNBkW.js → EvaluationsPage-DpdGpd7x.js} +2 -2
  231. package/frontend/dist/js/{FirstTimeSetup-CoF83yY8.js → FirstTimeSetup-By8OE1JS.js} +1 -1
  232. package/frontend/dist/js/GalleryPage-ByZyIhZQ.js +1 -0
  233. package/frontend/dist/js/HomePage-Btc-Mvin.js +1 -0
  234. package/frontend/dist/js/{LanguageSwitcher-CogfczNm.js → LanguageSwitcher-B3CxRW6J.js} +1 -1
  235. package/frontend/dist/js/LibreClawPage-3Sonm1gu.js +1 -0
  236. package/frontend/dist/js/{Logo-C9fsEUaY.js → Logo-HZvdAk2c.js} +1 -1
  237. package/frontend/dist/js/{LogoMark-Cvu5CQOW.js → LogoMark-GJnfMXsk.js} +1 -1
  238. package/frontend/dist/js/{ModelManagerModals-Bc2Woor7.js → ModelManagerModals-Bk6OQzJI.js} +1 -1
  239. package/frontend/dist/js/NotesPage-lFhfjMqJ.js +3 -0
  240. package/frontend/dist/js/{OptimizedSyntaxHighlighter-aKWyHj_c.js → OptimizedSyntaxHighlighter-CzzV6ONG.js} +1 -1
  241. package/frontend/dist/js/OptimizedSyntaxHighlighter-ajqwyBQm.js +1 -0
  242. package/frontend/dist/js/{PasswordStrengthMeter-BJr4eBbo.js → PasswordStrengthMeter-DasfJa4P.js} +1 -1
  243. package/frontend/dist/js/{PersonasPage-ff5opf9B.js → PersonasPage-nOThOpXp.js} +2 -2
  244. package/frontend/dist/js/PluginUsagePage-BRbHeRf7.js +1 -0
  245. package/frontend/dist/js/{RichMessageContent-BNNylVAb.js → RichMessageContent-BZ7KKJKK.js} +2 -2
  246. package/frontend/dist/js/RichMessageContent-oyydJjgc.js +1 -0
  247. package/frontend/dist/js/SettingsModal-VpsAx2Ns.js +19 -0
  248. package/frontend/dist/js/{SettingsToggle-CKLHOUt-.js → SettingsToggle-oxvw7ulA.js} +1 -1
  249. package/frontend/dist/js/{ShareDialog-DykD70uB.js → ShareDialog-CnxPGPb_.js} +1 -1
  250. package/frontend/dist/js/SystemPage-D2ikvhDp.js +1 -0
  251. package/frontend/dist/js/{TurnstileWidget-BO2ceTt8.js → TurnstileWidget-CVepEMEQ.js} +1 -1
  252. package/frontend/dist/js/{UserManagementPage-Ch-PvYQ9.js → UserManagementPage-2yHv89ZA.js} +1 -1
  253. package/frontend/dist/js/UserManagementPanel-vxpiyiTZ.js +1 -0
  254. package/frontend/dist/js/{WhatsNewModal-Bu4H4MEb.js → WhatsNewModal-DAAFzY0d.js} +1 -1
  255. package/frontend/dist/js/WorkPage-BYpYD9mr.js +8 -0
  256. package/frontend/dist/js/api-C0ligMmL.js +1 -0
  257. package/frontend/dist/js/api-CVUaOaq4.js +14 -0
  258. package/frontend/dist/js/{appNavigation-CVsQuDI1.js → appNavigation-CqtrZ9bm.js} +1 -1
  259. package/frontend/dist/js/{appStore-Din-tTD9.js → appStore-Biw63DtJ.js} +2 -2
  260. package/frontend/dist/js/appVersion-D78eXAb-.js +1 -0
  261. package/frontend/dist/js/ar-Cq-DrSYL.js +2 -0
  262. package/frontend/dist/js/authStore-2DsDTcIu.js +1 -0
  263. package/frontend/dist/js/{authStore-Cm8P6rYs.js → authStore-C5qKrmn4.js} +3 -3
  264. package/frontend/dist/js/bn-BqxLh-KL.js +2 -0
  265. package/frontend/dist/js/chatStore-DQhEqb9F.js +3 -0
  266. package/frontend/dist/js/client-gf5bjBrn.js +2 -0
  267. package/frontend/dist/js/composerStyles-C83jssgZ.js +1 -0
  268. package/frontend/dist/js/{config-DHea4b8N.js → config-CNvgxwAm.js} +1 -1
  269. package/frontend/dist/js/cordisApi-BYjA4EOQ.js +2 -0
  270. package/frontend/dist/js/cs-D2DDNZf-.js +2 -0
  271. package/frontend/dist/js/da-BciY-TSG.js +2 -0
  272. package/frontend/dist/js/de-DjU_eXQ-.js +2 -0
  273. package/frontend/dist/js/es-ChIEs80I.js +2 -0
  274. package/frontend/dist/js/{estree-DCT3Jm65.js → estree-DGmLqopC.js} +17 -17
  275. package/frontend/dist/js/evaluationsApi-Bg12ekHo.js +1 -0
  276. package/frontend/dist/js/fr-Bi8BGRW4.js +2 -0
  277. package/frontend/dist/js/hi-BMsCnWg7.js +2 -0
  278. package/frontend/dist/js/{html-CBXMuS97.js → html-BryNQ_yx.js} +6 -6
  279. package/frontend/dist/js/id-CpcN1vhk.js +2 -0
  280. package/frontend/dist/js/is-DkUy2cmP.js +2 -0
  281. package/frontend/dist/js/it-BglK9DRZ.js +2 -0
  282. package/frontend/dist/js/ja-IrprzwLJ.js +2 -0
  283. package/frontend/dist/js/ko--wwEsMPi.js +2 -0
  284. package/frontend/dist/js/{markdown-Bo71X2X2.js → markdown-CZ1tYEW8.js} +17 -17
  285. package/frontend/dist/js/{markdown-core-BFkBYv2v.js → markdown-core-KjS96SYW.js} +1 -1
  286. package/frontend/dist/js/{markdown-html-CwjnwJbl.js → markdown-html-DQEwrWcf.js} +1 -1
  287. package/frontend/dist/js/{markdown-math-CJvHdD1l.js → markdown-math-CKi32S5d.js} +1 -1
  288. package/frontend/dist/js/{markdownHtml-egpK3_YY.js → markdownHtml-lrXhxBhK.js} +1 -1
  289. package/frontend/dist/js/markdownMath-B0BpyFBS.js +1 -0
  290. package/frontend/dist/js/{messageCodeStyles-euWoxwoo.js → messageCodeStyles-DJZ4QMVq.js} +2 -2
  291. package/frontend/dist/js/ms-BWj59y7-.js +2 -0
  292. package/frontend/dist/js/nl-D-QndW_A.js +2 -0
  293. package/frontend/dist/js/pl-Dw7d7iDV.js +2 -0
  294. package/frontend/dist/js/pt-BI53uHJw.js +2 -0
  295. package/frontend/dist/js/react-vendor-wiHys0m2.js +9 -0
  296. package/frontend/dist/js/router-vendor-HZm4FR0A.js +1 -0
  297. package/frontend/dist/js/ru-JKyTgPg9.js +2 -0
  298. package/frontend/dist/js/{standalone-6z5x8CWe.js → standalone-DbyFjvTU.js} +1 -1
  299. package/frontend/dist/js/sv-BIkBLPSQ.js +2 -0
  300. package/frontend/dist/js/{syntax-highlight-7t8DGG2n.js → syntax-highlight-DNc3FFCa.js} +1 -1
  301. package/frontend/dist/js/{teamEventStream-DGgjrVqw.js → teamEventStream-PXiwR5Kr.js} +1 -1
  302. package/frontend/dist/js/th-CZ_LcCU0.js +2 -0
  303. package/frontend/dist/js/tr-B265byx1.js +2 -0
  304. package/frontend/dist/js/{typescript-CslHIgIJ.js → typescript-DUM5niRM.js} +1 -1
  305. package/frontend/dist/js/{ui-CNcEDQ95.js → ui-DDMjO5dd.js} +2 -2
  306. package/frontend/dist/js/ui-vendor-BhHHyHk3.js +1 -0
  307. package/frontend/dist/js/uk-VTAlChwf.js +2 -0
  308. package/frontend/dist/js/{useChat-YRODF1y1.js → useChat-DaIZ2Dbu.js} +1 -1
  309. package/frontend/dist/js/{useDialogFocus-BQLdAGYH.js → useDialogFocus-BPTG_Zmy.js} +1 -1
  310. package/frontend/dist/js/useDictation-BXyX1U8r.js +1 -0
  311. package/frontend/dist/js/useQuery-hWmQjdLu.js +1 -0
  312. package/frontend/dist/js/{useTranslation-Kn7_iLGj.js → useTranslation-B9kflK9S.js} +1 -1
  313. package/frontend/dist/js/{utils-Dq1w7K0c.js → utils-p8Gt72YP.js} +1 -1
  314. package/frontend/dist/js/{utils-vendor-dx9vGAt3.js → utils-vendor-DISWt2y9.js} +1 -1
  315. package/frontend/dist/js/vi-D8rXh-53.js +2 -0
  316. package/frontend/dist/js/{workStatus-oU04ZrUq.js → work-DmJpVPPO.js} +1 -1
  317. package/frontend/dist/js/workApi-BIS9RxRi.js +1 -0
  318. package/frontend/dist/js/workApi-C3Td_lFD.js +5 -0
  319. package/frontend/dist/js/workModels-MzU2Ab7l.js +1 -0
  320. package/frontend/dist/js/zh-BEGd9iBD.js +2 -0
  321. package/package.json +43 -8
  322. package/scripts/prepare-dsh-provider.mjs +124 -0
  323. package/frontend/dist/assets/index-BXTzjRcS.js +0 -92
  324. package/frontend/dist/assets/index-uzzNXp5R.css +0 -1
  325. package/frontend/dist/js/ArtifactSlideOutPanel-DPWGjHhp.js +0 -1
  326. package/frontend/dist/js/AutomationsPage-DvsLZWtg.js +0 -1
  327. package/frontend/dist/js/ChannelsPage-D252gXwf.js +0 -1
  328. package/frontend/dist/js/ChatPage-D_CzOiHw.js +0 -14
  329. package/frontend/dist/js/ConnectModels-DYOtIOEY.js +0 -1
  330. package/frontend/dist/js/GalleryPage-BpK-vrvd.js +0 -1
  331. package/frontend/dist/js/HomePage-zJkkDNJl.js +0 -1
  332. package/frontend/dist/js/LibreClawPage-CAgNj9GM.js +0 -1
  333. package/frontend/dist/js/NotesPage-CNTqlwNc.js +0 -3
  334. package/frontend/dist/js/OptimizedSyntaxHighlighter-BFjOBavF.js +0 -1
  335. package/frontend/dist/js/PluginUsagePage-Cp4j4-VZ.js +0 -1
  336. package/frontend/dist/js/RichMessageContent-BOY5jqj-.js +0 -1
  337. package/frontend/dist/js/SettingsModal-WAdVG4-g.js +0 -19
  338. package/frontend/dist/js/SystemPage-Dwi8leEQ.js +0 -1
  339. package/frontend/dist/js/UserManagementPanel-CfG67c_k.js +0 -1
  340. package/frontend/dist/js/WorkPage-D_cL9wp4.js +0 -8
  341. package/frontend/dist/js/api-29NO19CP.js +0 -14
  342. package/frontend/dist/js/api-DhafOTdF.js +0 -1
  343. package/frontend/dist/js/appVersion-BNuquyI6.js +0 -1
  344. package/frontend/dist/js/ar-WHne6B0o.js +0 -2
  345. package/frontend/dist/js/authStore-snvjjOuX.js +0 -1
  346. package/frontend/dist/js/bn-BZvCjqwO.js +0 -2
  347. package/frontend/dist/js/chatStore-BPpcL4I5.js +0 -3
  348. package/frontend/dist/js/composerStyles-bNlrIugf.js +0 -1
  349. package/frontend/dist/js/cs-DfSqWQ9V.js +0 -2
  350. package/frontend/dist/js/da-BmMLW4Dl.js +0 -2
  351. package/frontend/dist/js/de-BQMiQwQT.js +0 -2
  352. package/frontend/dist/js/es-DXf6jt4Z.js +0 -2
  353. package/frontend/dist/js/evaluationsApi-Btc9Mg9z.js +0 -1
  354. package/frontend/dist/js/fr-BD6nT1oc.js +0 -2
  355. package/frontend/dist/js/hi-BSuNic-8.js +0 -2
  356. package/frontend/dist/js/id-wodbwt3B.js +0 -2
  357. package/frontend/dist/js/is-CEjw-OJE.js +0 -2
  358. package/frontend/dist/js/it-X_pq8C7q.js +0 -2
  359. package/frontend/dist/js/ja-DWM2HZZd.js +0 -2
  360. package/frontend/dist/js/ko-DFi2khBB.js +0 -2
  361. package/frontend/dist/js/markdownMath-mesq3ZcY.js +0 -1
  362. package/frontend/dist/js/ms-DNQateTR.js +0 -2
  363. package/frontend/dist/js/nl-DSWhi8W8.js +0 -2
  364. package/frontend/dist/js/pl-W9WEDpZ3.js +0 -2
  365. package/frontend/dist/js/pt-DCCGvdNt.js +0 -2
  366. package/frontend/dist/js/react-vendor-D0Ec-185.js +0 -9
  367. package/frontend/dist/js/router-vendor-CM0PhOMl.js +0 -1
  368. package/frontend/dist/js/ru-D-Ww6dXe.js +0 -2
  369. package/frontend/dist/js/sv-B0Iry-H3.js +0 -2
  370. package/frontend/dist/js/th-CEZS0WeE.js +0 -2
  371. package/frontend/dist/js/tr-BFJ8TeYb.js +0 -2
  372. package/frontend/dist/js/ui-vendor-LatG6rFF.js +0 -177
  373. package/frontend/dist/js/uk-BmW3yDel.js +0 -2
  374. package/frontend/dist/js/useQuery-C3O4CZ54.js +0 -1
  375. package/frontend/dist/js/vi-Cu25FXnp.js +0 -2
  376. package/frontend/dist/js/work-DVgm81bJ.js +0 -1
  377. package/frontend/dist/js/workApi-BdUNugC3.js +0 -1
  378. package/frontend/dist/js/workApi-OieM8qEL.js +0 -6
  379. package/frontend/dist/js/zh-CuCAx3oH.js +0 -2
@@ -0,0 +1,1116 @@
1
+ /*
2
+ * Libre WebUI
3
+ * Copyright (C) 2025 Kroonen AI, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at:
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * The DSH engine bridge: a Cordis plugin that turns a mounted DSH composition
19
+ * into the `libreDshEngine` contract.
20
+ *
21
+ * This is the only Libre WebUI module that names concrete `@deepseek-ai/dsh-*`
22
+ * packages, and it does so for types alone. Everything it publishes is defined
23
+ * in `cordis/contracts.ts`, so routes, the WebSocket bridge, and the frontend
24
+ * client never learn that DSH exists.
25
+ *
26
+ * The plugin is mounted as a Loader row, which means Cordis owns it. Its
27
+ * dependency injector, its `session/event` subscription, every agent handle it
28
+ * creates, and the service it publishes all belong to that row's fiber.
29
+ * Removing the row disposes the fiber, and all of it goes away together — that
30
+ * single ownership edge is the rollback guarantee.
31
+ *
32
+ * @module cordis/dsh/engine-plugin
33
+ */
34
+ import { randomUUID } from 'node:crypto';
35
+ import path from 'node:path';
36
+ import { mkdir, realpath } from 'node:fs/promises';
37
+ import { Session } from '@deepseek-ai/dsh-session';
38
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
39
+ import { deleteJsonlSession } from './session-files.js';
40
+ import { installWorkspaceToolPolicy } from './workspace-policy.js';
41
+ import { appendSessionSettings, sessionSettings } from './session-settings.js';
42
+ import { isProviderModelIdentity } from './model-identity.js';
43
+ import { installModelSelection, } from '@deepseek-ai/dsh-agent';
44
+ import { Service } from '@deepseek-ai/cordis';
45
+ import { DSH_ENGINE_SERVICE } from '../contracts.js';
46
+ import { createLogger } from '../../utils/logger.js';
47
+ import { resolveDataDirectory } from '../../utils/dataDirectory.js';
48
+ const logger = createLogger('cordis-dsh-engine');
49
+ /** Plugin name reported to Cordis diagnostics. */
50
+ export const name = 'libre-webui-dsh-engine';
51
+ /**
52
+ * Services this plugin waits for.
53
+ *
54
+ * Declaring them is what makes the bridge start only once the engine is
55
+ * usable, and — just as importantly — refuse to start at all when the engine
56
+ * is absent. A bridge that published itself against a missing engine would
57
+ * turn a configuration mistake into silently empty session lists.
58
+ */
59
+ export const inject = ['sessions', 'agents', 'tools', 'llm'];
60
+ /**
61
+ * One in-flight response for a session.
62
+ *
63
+ * Chunks are buffered until a subscriber attaches, because the HTTP handler
64
+ * must await this handle before it can write response headers — a model that
65
+ * answers faster than the round trip would otherwise lose its opening tokens.
66
+ */
67
+ export class ResponseStream {
68
+ constructor(sessionId, onClose, onDone) {
69
+ this.sessionId = sessionId;
70
+ this.onClose = onClose;
71
+ this.onDone = onDone;
72
+ this.buffer = [];
73
+ this.listeners = new Set();
74
+ this.closed = false;
75
+ this.terminated = false;
76
+ this.errorReported = false;
77
+ this.bufferBytes = 0;
78
+ }
79
+ /** Deliver a chunk to the buffer, or straight to attached listeners. */
80
+ push(chunk) {
81
+ if (this.terminated)
82
+ return;
83
+ const bytes = Buffer.byteLength(JSON.stringify(chunk));
84
+ if (chunk.type !== 'error' &&
85
+ chunk.type !== 'done' &&
86
+ (this.bufferBytes + bytes > ResponseStream.MAX_BUFFER_BYTES ||
87
+ this.buffer.length >= ResponseStream.MAX_BUFFER_CHUNKS)) {
88
+ this.onClose?.();
89
+ this.buffer.length = 0;
90
+ this.bufferBytes = 0;
91
+ this.push({
92
+ type: 'error',
93
+ code: 'CORDIS_STREAM_LIMIT',
94
+ message: 'The engine response exceeded the stream buffer limit.',
95
+ });
96
+ this.push({ type: 'done', reason: 'error' });
97
+ return;
98
+ }
99
+ if (chunk.type === 'error') {
100
+ if (this.errorReported)
101
+ return;
102
+ this.errorReported = true;
103
+ }
104
+ if (chunk.type === 'done') {
105
+ this.terminated = true;
106
+ this.onDone?.();
107
+ }
108
+ if (this.closed)
109
+ return;
110
+ // Recorded here rather than read back from the buffer: a chunk delivered to
111
+ // a live listener is never buffered, so the terminal chunk would be
112
+ // invisible to a later check and the session would look busy forever.
113
+ this.buffer.push(chunk);
114
+ this.bufferBytes += bytes;
115
+ for (const listener of this.listeners) {
116
+ try {
117
+ listener(chunk);
118
+ }
119
+ catch (error) {
120
+ // A misbehaving consumer must not abort the model stream for other
121
+ // consumers, nor for the engine that is publishing it.
122
+ logger.warn('stream listener threw', {
123
+ error: error instanceof Error ? error.message : String(error),
124
+ });
125
+ }
126
+ }
127
+ if (this.terminated)
128
+ this.listeners.clear();
129
+ }
130
+ /**
131
+ * Mark the response finished and release every listener.
132
+ *
133
+ * Also called when a client disconnects, so an abandoned response stops
134
+ * buffering for a reader that is gone.
135
+ */
136
+ close() {
137
+ if (this.closed)
138
+ return;
139
+ this.closed = true;
140
+ this.listeners.clear();
141
+ if (!this.terminated) {
142
+ this.buffer.length = 0;
143
+ this.bufferBytes = 0;
144
+ this.onClose?.();
145
+ }
146
+ }
147
+ /** Whether the response has already terminated. */
148
+ get isClosed() {
149
+ return this.closed;
150
+ }
151
+ /** Chunks buffered but not yet delivered. */
152
+ get pending() {
153
+ return this.buffer.length;
154
+ }
155
+ subscribe(listener) {
156
+ // Replay first, so a subscriber attaching after a fast model answer still
157
+ // sees the complete response. The buffer is retained rather than drained:
158
+ // a turn can reach `done` before the HTTP handler attaches, and discarding
159
+ // the chunks would deliver an empty response for a turn that succeeded.
160
+ for (const chunk of this.buffer)
161
+ listener(chunk);
162
+ if (this.closed || this.terminated)
163
+ return { unsubscribe: () => undefined };
164
+ this.listeners.add(listener);
165
+ return {
166
+ unsubscribe: () => {
167
+ this.listeners.delete(listener);
168
+ },
169
+ };
170
+ }
171
+ /** Whether this response has produced its terminal chunk. */
172
+ get isTerminated() {
173
+ return this.terminated;
174
+ }
175
+ }
176
+ ResponseStream.MAX_BUFFER_BYTES = 2 * 1024 * 1024;
177
+ ResponseStream.MAX_BUFFER_CHUNKS = 10000;
178
+ /** Extract readable text from one DSH message content value. */
179
+ export function extractText(content) {
180
+ if (typeof content === 'string')
181
+ return content;
182
+ if (!Array.isArray(content))
183
+ return '';
184
+ const parts = [];
185
+ for (const block of content) {
186
+ if (typeof block === 'string') {
187
+ parts.push(block);
188
+ continue;
189
+ }
190
+ if (block === null || typeof block !== 'object')
191
+ continue;
192
+ const record = block;
193
+ // Content blocks are a union of text, image, and tool shapes. Only text is
194
+ // projectable into a chat transcript; the rest is deliberately dropped
195
+ // rather than stringified into something misleading.
196
+ if (record.type === 'text' && typeof record.text === 'string') {
197
+ parts.push(record.text);
198
+ }
199
+ else if (record.type === 'tool-result') {
200
+ parts.push(extractText(record.content));
201
+ }
202
+ }
203
+ return parts.join('');
204
+ }
205
+ /** Normalize a DSH message role into the contract's role vocabulary. */
206
+ export function normalizeRole(role) {
207
+ if (role === 'user' ||
208
+ role === 'assistant' ||
209
+ role === 'system' ||
210
+ role === 'tool') {
211
+ return role;
212
+ }
213
+ return 'unknown';
214
+ }
215
+ /** Project one DSH message into the contract's message shape. */
216
+ export function projectMessage(raw, index) {
217
+ if (raw === null || typeof raw !== 'object') {
218
+ return { id: `message-${index}`, role: 'unknown', text: '' };
219
+ }
220
+ const record = raw;
221
+ const id = typeof record.id === 'string' ? record.id : `message-${index}`;
222
+ const source = record.source && typeof record.source === 'object'
223
+ ? record.source
224
+ : undefined;
225
+ const reasoning = Array.isArray(record.content)
226
+ ? record.content
227
+ .filter((block) => block !== null &&
228
+ typeof block === 'object' &&
229
+ block.type === 'reasoning')
230
+ .map((block) => block.text ?? '')
231
+ .join('')
232
+ : '';
233
+ const blocks = Array.isArray(record.content)
234
+ ? record.content.filter((block) => !!block && typeof block === 'object')
235
+ : [];
236
+ const toolCalls = blocks
237
+ .filter(block => block.type === 'tool-call')
238
+ .map(block => ({
239
+ callId: typeof block.id === 'string' ? block.id : '',
240
+ name: typeof block.name === 'string' ? block.name : '',
241
+ arguments: typeof block.arguments === 'string'
242
+ ? block.arguments
243
+ : JSON.stringify(block.arguments ?? {}),
244
+ }));
245
+ const toolResults = blocks
246
+ .filter(block => block.type === 'tool-result')
247
+ .map(block => ({
248
+ callId: typeof block.toolCallId === 'string' ? block.toolCallId : '',
249
+ output: extractText(block.content),
250
+ isError: block.isError === true,
251
+ }));
252
+ const messageSource = record.role === 'system'
253
+ ? 'system'
254
+ : source?.kind === 'plugin'
255
+ ? 'context'
256
+ : source?.kind === 'tool'
257
+ ? 'tool'
258
+ : source?.kind === 'model' || record.role === 'assistant'
259
+ ? 'model'
260
+ : source?.kind === 'user' || record.role === 'user'
261
+ ? 'user'
262
+ : undefined;
263
+ const seq = typeof record.seq === 'number' ? record.seq : undefined;
264
+ return {
265
+ id,
266
+ role: source?.kind === 'tool' ? 'tool' : normalizeRole(record.role),
267
+ text: extractText(record.content),
268
+ ...(reasoning ? { reasoning } : {}),
269
+ ...(messageSource ? { source: messageSource } : {}),
270
+ ...(toolCalls.length ? { toolCalls } : {}),
271
+ ...(toolResults.length ? { toolResults } : {}),
272
+ ...(seq === undefined ? {} : { seq }),
273
+ };
274
+ }
275
+ /**
276
+ * Map one DSH session event onto a contract stream chunk.
277
+ *
278
+ * A durable session event is an envelope: `type` and `seq` sit beside a `data`
279
+ * object that carries the payload for that type. Reading the payload off the
280
+ * envelope instead of `data` yields no chunk at all rather than an error, so
281
+ * each branch unwraps `data` explicitly.
282
+ * @param event - the session event envelope.
283
+ * @returns the projected chunk, or undefined for an event the transcript omits.
284
+ */
285
+ export function projectStreamEvent(event) {
286
+ const type = event.type;
287
+ const data = event.data !== null &&
288
+ typeof event.data === 'object' &&
289
+ !Array.isArray(event.data)
290
+ ? event.data
291
+ : {};
292
+ if (type === 'assistant/message') {
293
+ const message = data.message;
294
+ const text = message !== null && typeof message === 'object'
295
+ ? extractText(message.content)
296
+ : '';
297
+ return text.length > 0 ? { type: 'text', text } : undefined;
298
+ }
299
+ if (type === 'tool/call') {
300
+ return {
301
+ type: 'tool-call',
302
+ callId: typeof data.callId === 'string' ? data.callId : '',
303
+ name: typeof data.name === 'string' ? data.name : '',
304
+ ...(typeof data.arguments === 'string'
305
+ ? { arguments: data.arguments }
306
+ : {}),
307
+ };
308
+ }
309
+ if (type === 'tool/result') {
310
+ // Current DSH stores correlation and error state in the tool-result
311
+ // content block; retain legacy message fields for already-recorded logs.
312
+ const message = data.message !== null &&
313
+ typeof data.message === 'object' &&
314
+ !Array.isArray(data.message)
315
+ ? data.message
316
+ : {};
317
+ const error = data.error !== null &&
318
+ typeof data.error === 'object' &&
319
+ !Array.isArray(data.error)
320
+ ? data.error
321
+ : undefined;
322
+ const block = Array.isArray(message.content)
323
+ ? message.content.find((value) => value !== null &&
324
+ typeof value === 'object' &&
325
+ value.type === 'tool-result')
326
+ : undefined;
327
+ const source = message.source && typeof message.source === 'object'
328
+ ? message.source
329
+ : undefined;
330
+ const callId = block?.toolCallId ?? source?.callId ?? message.toolCallId;
331
+ return {
332
+ type: 'tool-result',
333
+ callId: typeof callId === 'string' ? callId : '',
334
+ // A tool result carries no tool name; the failure identity is the closest
335
+ // thing, and an empty name is honest when the call succeeded.
336
+ name: typeof error?.name === 'string' ? error.name : '',
337
+ isError: block?.isError === true || message.isError === true,
338
+ ...(block ? { output: extractText(block.content) } : {}),
339
+ };
340
+ }
341
+ if (type === 'turn/end') {
342
+ // The reason is a structured value, not a label: a failed turn carries the
343
+ // provider's own error there. Reporting a bare status instead is how a
344
+ // precise failure ("this route lists no models") reached the client as
345
+ // "the turn ended".
346
+ const reason = data.reason;
347
+ const kind = reason !== null && typeof reason === 'object' && !Array.isArray(reason)
348
+ ? reason.kind
349
+ : undefined;
350
+ return {
351
+ type: 'done',
352
+ reason: typeof kind === 'string' ? kind : 'unknown',
353
+ ...(data.interrupted === true ||
354
+ kind === 'aborted' ||
355
+ kind === 'interrupted'
356
+ ? { interrupted: true }
357
+ : {}),
358
+ };
359
+ }
360
+ return undefined;
361
+ }
362
+ /**
363
+ * Project the failure a finished turn recorded, if it recorded one.
364
+ *
365
+ * @param event - the `turn/end` envelope.
366
+ * @returns the error chunk, or undefined when the turn ended normally.
367
+ */
368
+ export function projectTurnFailure(event) {
369
+ const data = event.data !== null &&
370
+ typeof event.data === 'object' &&
371
+ !Array.isArray(event.data)
372
+ ? event.data
373
+ : {};
374
+ const reason = data.reason;
375
+ if (reason === null || typeof reason !== 'object' || Array.isArray(reason)) {
376
+ return undefined;
377
+ }
378
+ const record = reason;
379
+ if (record.kind !== 'error')
380
+ return undefined;
381
+ const error = record.error;
382
+ if (error === null || typeof error !== 'object') {
383
+ return { type: 'error', message: 'The engine reported a failed turn.' };
384
+ }
385
+ const detail = error;
386
+ const message = typeof detail.message === 'string' && detail.message.trim() !== ''
387
+ ? detail.message
388
+ : 'The engine reported a failed turn.';
389
+ return {
390
+ type: 'error',
391
+ message,
392
+ ...(typeof detail.code === 'string' ? { code: detail.code } : {}),
393
+ };
394
+ }
395
+ /**
396
+ * Build the session-id minter for one engine instance.
397
+ *
398
+ * A shared counter is not enough. `dsh-session-persistence-jsonl` indexes live
399
+ * sessions process-wide, so two engine instances in one process that both mint
400
+ * `session-1` collide, and the second fails with `SessionAlreadyExistsError`
401
+ * even though the two hosts use different store directories. Prefixing with a
402
+ * per-instance random component keeps ids unique across every host in the
403
+ * process while staying readable in a session list.
404
+ *
405
+ * @returns a function minting an id no other engine instance will produce.
406
+ */
407
+ function createSessionIdMinter() {
408
+ const prefix = randomUUID().slice(0, 8);
409
+ let counter = 0;
410
+ return () => {
411
+ counter += 1;
412
+ return `session-${prefix}-${counter}`;
413
+ };
414
+ }
415
+ /** Read a session's event count without depending on a concrete class. */
416
+ function eventCountOf(session) {
417
+ return typeof session.seq === 'number' && Number.isFinite(session.seq)
418
+ ? session.seq
419
+ : 0;
420
+ }
421
+ /** Project a session into its summary shape. */
422
+ export function projectSummary(session) {
423
+ const createdAt = session.header?.createdAt;
424
+ const firstInput = session
425
+ .deriveMessages()
426
+ .map(projectMessage)
427
+ .find(message => message.source === 'user' && message.text.trim());
428
+ const title = firstInput?.text.replace(/\s+/g, ' ').trim().slice(0, 80);
429
+ return {
430
+ id: session.id,
431
+ eventCount: eventCountOf(session),
432
+ ...(title ? { title } : {}),
433
+ ...(session.header?.cwd ? { workspacePath: session.header.cwd } : {}),
434
+ ...(typeof createdAt === 'number' ? { createdAt } : {}),
435
+ };
436
+ }
437
+ /**
438
+ * The engine contract, published as the `libreDshEngine` Cordis service.
439
+ *
440
+ * It is a `Service` rather than a plain object so that Cordis owns its
441
+ * lifecycle: the name is declared once, `ctx.get('libreDshEngine')` resolves it
442
+ * for every consumer, and removing the bridge row withdraws it automatically.
443
+ */
444
+ export class LibreDshEngineService extends Service {
445
+ constructor(ctx, config = {}) {
446
+ super(ctx, DSH_ENGINE_SERVICE);
447
+ this.streams = new Map();
448
+ this.handles = new Map();
449
+ /**
450
+ * Sessions created by the UI that no agent owns yet.
451
+ *
452
+ * They are deliberately absent from the DSH store until the first message,
453
+ * because the agent that will own one must be the component that enters it.
454
+ */
455
+ this.pending = new Map();
456
+ /** Working directory requested for each not-yet-owned session. */
457
+ this.pendingCwd = new Map();
458
+ this.pendingNative = new Map();
459
+ this.selections = new Map();
460
+ this.settingsUpdates = new Map();
461
+ this.approvals = new Map();
462
+ /** Caller identity exists only while a model turn is in flight. */
463
+ this.requestUsers = new Map();
464
+ this.agentUsers = new Map();
465
+ this.streamedText = new Set();
466
+ this.starting = new Map();
467
+ this.controllers = new Map();
468
+ this.stopping = new Map();
469
+ this.deleting = new Map();
470
+ this.disposed = false;
471
+ /** Mints session ids unique across every engine in this process. */
472
+ this.mintSessionId = createSessionIdMinter();
473
+ this.workspacePath = path.resolve(config.workspacePath?.trim()
474
+ ? config.workspacePath
475
+ : path.join(resolveDataDirectory(), 'cordis-workspace'));
476
+ installWorkspaceToolPolicy(ctx, this.workspacePath);
477
+ this.allowStreaming = config.streaming !== false;
478
+ this.allowTools = config.tools !== false;
479
+ if (!this.allowTools)
480
+ ctx.tools.guard(() => 'Cordis tools are disabled by configuration.');
481
+ this.sessionStorePath = config.sessionStorePath;
482
+ this.defaultProvider = config.defaultProvider;
483
+ this.defaultModel = config.defaultModel;
484
+ ctx.tools.guard(exec => {
485
+ const args = exec.arguments;
486
+ if (args?.sandbox_permissions === 'danger-full-access')
487
+ return 'Unrestricted filesystem access is not available in the Cordis bridge.';
488
+ const policy = this.ctx.get('sandboxPolicy');
489
+ if ((exec.name === 'write' || exec.name === 'edit') && policy) {
490
+ const mode = policy.resolve({
491
+ ...(exec.agent ? { session: exec.agent.session } : {}),
492
+ }).mode;
493
+ if (mode === 'danger-full-access')
494
+ return 'This session must select read-only or workspace-write permissions.';
495
+ if (mode === 'read-only' &&
496
+ args?.sandbox_permissions !== 'workspace-write')
497
+ return 'File changes require workspace-write mode or explicit approval for this operation.';
498
+ }
499
+ return undefined;
500
+ });
501
+ ctx.on('approval/request', async (request, next) => {
502
+ const sessionId = request.agent.session.id;
503
+ if (/-transient$/.test(sessionId))
504
+ return 'unavailable';
505
+ if (!this.streams.has(sessionId))
506
+ return next();
507
+ const pending = [...this.approvals.values()].find(value => value.approval.sessionId === sessionId &&
508
+ value.approval.callId === request.callId &&
509
+ value.approval.toolName === request.toolName);
510
+ if (!pending)
511
+ return 'unavailable';
512
+ const abort = () => {
513
+ pending.decided = true;
514
+ pending.resolve('cancelled');
515
+ };
516
+ request.signal?.addEventListener('abort', abort, { once: true });
517
+ if (request.signal?.aborted)
518
+ abort();
519
+ try {
520
+ return await pending.promise;
521
+ }
522
+ finally {
523
+ request.signal?.removeEventListener('abort', abort);
524
+ }
525
+ });
526
+ // Each live frame carries its exact agent and session. Finished session
527
+ // events still provide terminal/tool state and the durable transcript.
528
+ // Live assistant frames are the token-level channel. Durable session events
529
+ // carry a turn's finished message, so a client reading only those receives
530
+ // a whole reply at once; these frames arrive per delta, which is what makes
531
+ // a reply appear as it is written.
532
+ ctx.on('agent/assistant-stream', ((payload) => {
533
+ const { agent, frame } = payload;
534
+ const sessionId = agent?.session?.id ?? agent?.id;
535
+ if (!sessionId || !frame)
536
+ return;
537
+ const stream = this.streams.get(sessionId);
538
+ if (!stream || stream.isClosed || stream.isTerminated)
539
+ return;
540
+ if (frame.type === 'start') {
541
+ this.streamedText.delete(sessionId);
542
+ return;
543
+ }
544
+ if (frame.type !== 'chunk' ||
545
+ !frame.chunk ||
546
+ typeof frame.chunk !== 'object')
547
+ return;
548
+ const chunk = frame.chunk;
549
+ if (chunk.type === 'text-delta' && typeof chunk.text === 'string') {
550
+ this.streamedText.add(sessionId);
551
+ stream.push({ type: 'text', text: chunk.text });
552
+ }
553
+ else if (chunk.type === 'reasoning-delta' &&
554
+ typeof chunk.text === 'string') {
555
+ stream.push({ type: 'reasoning', text: chunk.text });
556
+ }
557
+ }));
558
+ ctx.on('agent/error', ((payload) => {
559
+ const sessionId = payload.agent?.session?.id ?? payload.agent?.id;
560
+ const stream = sessionId ? this.streams.get(sessionId) : undefined;
561
+ if (!stream || stream.isTerminated)
562
+ return;
563
+ stream.push({
564
+ type: 'error',
565
+ message: payload.error instanceof Error
566
+ ? payload.error.message
567
+ : String(payload.error),
568
+ });
569
+ // Disposal settles errors that happened before a durable turn boundary.
570
+ if (sessionId)
571
+ void this.cancel(sessionId).catch(error => logger.warn('failed agent cleanup', { error: String(error) }));
572
+ }));
573
+ ctx.on('session/event', ((session, event) => {
574
+ if (event === null || typeof event !== 'object')
575
+ return;
576
+ const sessionId = session?.id;
577
+ if (typeof sessionId !== 'string')
578
+ return;
579
+ const stream = this.streams.get(sessionId);
580
+ if (!stream || stream.isTerminated)
581
+ return;
582
+ const envelope = event;
583
+ const data = envelope.data;
584
+ if (envelope.type === 'approval/asked' &&
585
+ typeof data?.id === 'string' &&
586
+ typeof data.toolName === 'string') {
587
+ if (/-transient$/.test(sessionId))
588
+ return;
589
+ const approval = {
590
+ id: data.id,
591
+ sessionId,
592
+ toolName: data.toolName,
593
+ ...(typeof data.callId === 'string' ? { callId: data.callId } : {}),
594
+ ...(typeof data.reason === 'string' ? { reason: data.reason } : {}),
595
+ };
596
+ let answer;
597
+ const promise = new Promise(resolve => {
598
+ answer = resolve;
599
+ });
600
+ this.approvals.set(approval.id, {
601
+ approval,
602
+ promise,
603
+ resolve: answer,
604
+ decided: false,
605
+ });
606
+ stream.push({ type: 'approval-request', approval });
607
+ return;
608
+ }
609
+ if (envelope.type === 'approval/decided' &&
610
+ typeof data?.id === 'string') {
611
+ this.approvals.delete(data.id);
612
+ stream.push({
613
+ type: 'approval-decision',
614
+ approvalId: data.id,
615
+ outcome: data.outcome,
616
+ });
617
+ return;
618
+ }
619
+ // A failed turn is reported before its terminal chunk, so a consumer
620
+ // that stops reading at `done` has already seen the reason.
621
+ const failure = projectTurnFailure(envelope);
622
+ if (failure)
623
+ stream.push(failure);
624
+ if (envelope.type === 'assistant/message' &&
625
+ this.streamedText.has(sessionId))
626
+ return;
627
+ const projected = projectStreamEvent(envelope);
628
+ if (!projected)
629
+ return;
630
+ stream.push(projected);
631
+ }));
632
+ // Every in-flight response and agent handle is owned by this fiber.
633
+ // Disposing the bridge row releases all of them, so removing the plugin
634
+ // cannot leave a model stream or a live agent behind.
635
+ ctx.effect(() => async () => {
636
+ this.disposed = true;
637
+ await Promise.allSettled([...this.streams.keys()].map(id => this.cancel(id)));
638
+ await Promise.allSettled([...this.handles.values()].map(handle => handle.dispose()));
639
+ this.handles.clear();
640
+ this.pending.clear();
641
+ this.pendingCwd.clear();
642
+ this.pendingNative.clear();
643
+ for (const pending of this.approvals.values())
644
+ pending.resolve('cancelled');
645
+ this.approvals.clear();
646
+ logger.info('libreDshEngine service withdrawn');
647
+ }, 'libre-webui-dsh-engine.teardown');
648
+ }
649
+ /** The DSH session store this bridge reads through. */
650
+ get sessions() {
651
+ return this.ctx.get('sessions');
652
+ }
653
+ /** The DSH agent registry this bridge drives. */
654
+ get agents() {
655
+ return this.ctx.get('agents');
656
+ }
657
+ /** The DSH tool runtime this bridge reads through. */
658
+ get tools() {
659
+ return this.ctx.get('tools');
660
+ }
661
+ modelConfiguration() {
662
+ return { provider: this.defaultProvider, model: this.defaultModel };
663
+ }
664
+ status() {
665
+ const names = [
666
+ 'llm',
667
+ 'systemPrompt',
668
+ 'sessions',
669
+ 'tools',
670
+ 'agents',
671
+ ];
672
+ return names.map(serviceName => ({
673
+ name: serviceName,
674
+ state: this.ctx.get(serviceName) === undefined ? 'pending' : 'ready',
675
+ }));
676
+ }
677
+ get persistence() {
678
+ return this.ctx.get('sessionPersistence');
679
+ }
680
+ async listSessions() {
681
+ const summaries = new Map();
682
+ for (const stored of (await this.persistence?.list()) ?? []) {
683
+ const summary = {
684
+ id: stored.header.id,
685
+ createdAt: stored.header.createdAt,
686
+ eventCount: stored.eventCount ?? 0,
687
+ };
688
+ try {
689
+ const session = await this.nativeSession(stored.header.id);
690
+ summaries.set(stored.header.id, session ? projectSummary(session) : summary);
691
+ }
692
+ catch (error) {
693
+ if (!(error instanceof Error) ||
694
+ error.name !== 'SessionPersistenceCorruptionError')
695
+ throw error;
696
+ summaries.set(stored.header.id, summary);
697
+ }
698
+ }
699
+ for (const session of this.sessions.list())
700
+ summaries.set(session.id, projectSummary(session));
701
+ for (const session of this.pending.values()) {
702
+ if (!summaries.has(session.id))
703
+ summaries.set(session.id, session);
704
+ }
705
+ return [...summaries.values()]
706
+ .filter(session => !/-transient$/.test(session.id))
707
+ .sort((left, right) => (right.createdAt ?? 0) - (left.createdAt ?? 0));
708
+ }
709
+ async nativeSession(sessionId) {
710
+ const live = this.sessions.get(sessionId);
711
+ if (live)
712
+ return live;
713
+ const pending = this.pendingNative.get(sessionId);
714
+ if (pending)
715
+ return pending;
716
+ if (this.persistence &&
717
+ (await this.persistence.stat(sessionId))) {
718
+ const handle = await this.persistence.open(sessionId, 'read');
719
+ try {
720
+ const read = await handle.read();
721
+ return Session.fromRestore(handle.id, read.events, handle.header, handle.inheritedEventCount, read.eventState, this.sessions.messageProjections);
722
+ }
723
+ finally {
724
+ await handle.close();
725
+ }
726
+ }
727
+ return this.pendingNative.get(sessionId);
728
+ }
729
+ async getSession(sessionId) {
730
+ const session = await this.nativeSession(sessionId);
731
+ if (!session)
732
+ return undefined;
733
+ return {
734
+ ...projectSummary(session),
735
+ ...(this.pending.get(sessionId)?.title
736
+ ? { title: this.pending.get(sessionId).title }
737
+ : {}),
738
+ active: this.streams.has(sessionId) ||
739
+ this.starting.has(sessionId) ||
740
+ this.stopping.has(sessionId),
741
+ settings: sessionSettings(session),
742
+ capabilities: {
743
+ permissions: !!this.ctx.get('sandboxPolicy'),
744
+ approvals: !!this.ctx.get('approval') && !/-transient$/.test(sessionId),
745
+ },
746
+ approvals: [...this.approvals.values()]
747
+ .filter(value => value.approval.sessionId === sessionId && !value.decided)
748
+ .map(value => value.approval),
749
+ messages: session.deriveMessages().map(projectMessage),
750
+ };
751
+ }
752
+ async validateSettings(settings, userId) {
753
+ if (settings.permissionMode !== undefined) {
754
+ if (settings.permissionMode !== 'read-only' &&
755
+ settings.permissionMode !== 'workspace-write')
756
+ throw new Error('Choose read-only or workspace-write permissions.');
757
+ if (!this.ctx.get('sandboxPolicy'))
758
+ throw new Error('This composition does not provide filesystem permission controls.');
759
+ }
760
+ if (settings.model !== undefined) {
761
+ if (!isProviderModelIdentity(settings.model))
762
+ throw new Error('Choose an available provider model.');
763
+ const provider = this.ctx.get('libreCordisProvider');
764
+ if (this.defaultProvider === 'libre-webui' &&
765
+ !(await provider?.resolveModel?.(settings.model, userId)))
766
+ throw new Error('The selected provider model is not available for this account.');
767
+ }
768
+ }
769
+ async updateSessionSettings(sessionId, settings, options) {
770
+ if (this.streams.has(sessionId) ||
771
+ this.starting.has(sessionId) ||
772
+ this.stopping.has(sessionId) ||
773
+ this.deleting.has(sessionId) ||
774
+ this.settingsUpdates.has(sessionId))
775
+ throw Object.assign(new Error('Session settings cannot change while a turn or another operation is active.'), { code: 'CORDIS_SESSION_BUSY' });
776
+ const update = (async () => {
777
+ await this.validateSettings(settings, options?.userId);
778
+ const owned = this.handles.get(sessionId);
779
+ if (owned) {
780
+ await owned.agent.whenIdle?.();
781
+ if (!this.persistence && owned.agent.session) {
782
+ appendSessionSettings(owned.agent.session, settings, this.defaultProvider ?? 'libre-webui');
783
+ if (settings.model && this.selections.has(sessionId))
784
+ this.selections.get(sessionId).current = {
785
+ provider: this.defaultProvider ?? 'libre-webui',
786
+ model: settings.model,
787
+ };
788
+ return (await this.getSession(sessionId));
789
+ }
790
+ await owned.dispose();
791
+ this.handles.delete(sessionId);
792
+ }
793
+ const stored = await this.persistence?.stat(sessionId);
794
+ if (stored && this.persistence) {
795
+ const handle = await this.persistence.open(sessionId, 'write');
796
+ try {
797
+ const read = await handle.read();
798
+ const session = Session.fromRestore(handle.id, read.events, handle.header, handle.inheritedEventCount, read.eventState, this.sessions.messageProjections);
799
+ // fromRestore may add a native end-seed marker before our settings.
800
+ // Persist that marker too so the appended suffix stays contiguous.
801
+ const start = read.events.length;
802
+ appendSessionSettings(session, settings, this.defaultProvider ?? 'libre-webui');
803
+ await handle.append(session.snapshotEvents().slice(start));
804
+ await handle.flush();
805
+ this.pendingNative.delete(sessionId);
806
+ }
807
+ finally {
808
+ await handle.close();
809
+ }
810
+ }
811
+ else {
812
+ const session = this.pendingNative.get(sessionId) ??
813
+ this.sessions.get(sessionId);
814
+ if (!session)
815
+ throw new Error('Session not found.');
816
+ appendSessionSettings(session, settings, this.defaultProvider ?? 'libre-webui');
817
+ }
818
+ const result = await this.getSession(sessionId);
819
+ if (!result)
820
+ throw new Error('Session not found.');
821
+ return result;
822
+ })();
823
+ this.settingsUpdates.set(sessionId, update);
824
+ try {
825
+ return await update;
826
+ }
827
+ finally {
828
+ this.settingsUpdates.delete(sessionId);
829
+ }
830
+ }
831
+ async decideApproval(sessionId, approvalId, outcome) {
832
+ if (outcome !== 'allowed-once' && outcome !== 'rejected')
833
+ throw new Error('Invalid approval outcome.');
834
+ const pending = this.approvals.get(approvalId);
835
+ if (!pending || pending.decided || pending.approval.sessionId !== sessionId)
836
+ return false;
837
+ pending.decided = true;
838
+ pending.resolve(outcome);
839
+ return true;
840
+ }
841
+ async resolveWorkspace(cwd) {
842
+ await mkdir(this.workspacePath, { recursive: true });
843
+ const root = await realpath(this.workspacePath);
844
+ const requested = await realpath(cwd || root);
845
+ const relative = path.relative(root, requested);
846
+ if (relative === '..' ||
847
+ relative.startsWith(`..${path.sep}`) ||
848
+ path.isAbsolute(relative)) {
849
+ throw new Error('The session workspace must be inside the configured Cordis workspace.');
850
+ }
851
+ return requested;
852
+ }
853
+ async createSession(options) {
854
+ if (this.disposed)
855
+ throw new Error('The Cordis engine is stopped.');
856
+ await this.validateSettings(options, options.userId);
857
+ const cwd = await this.resolveWorkspace(options.cwd);
858
+ const id = this.mintSessionId() + (options.transient ? '-transient' : '');
859
+ const summary = {
860
+ id,
861
+ eventCount: 0,
862
+ createdAt: Date.now(),
863
+ ...(options.title === undefined ? {} : { title: options.title }),
864
+ };
865
+ const prepared = this.sessions.prepare(id, { meta: { cwd } });
866
+ appendSessionSettings(prepared, {
867
+ ...options,
868
+ ...(this.ctx.get('sandboxPolicy')
869
+ ? { permissionMode: options.permissionMode ?? 'read-only' }
870
+ : {}),
871
+ }, this.defaultProvider ?? 'libre-webui');
872
+ if (this.persistence) {
873
+ const handle = await this.persistence.create(prepared.header);
874
+ try {
875
+ if (prepared.seq)
876
+ await handle.append(prepared.snapshotEvents());
877
+ await handle.flush();
878
+ }
879
+ finally {
880
+ await handle.close();
881
+ }
882
+ }
883
+ this.pendingNative.set(id, prepared);
884
+ this.pending.set(id, summary);
885
+ this.pendingCwd.set(id, cwd);
886
+ return (await this.getSession(id));
887
+ }
888
+ async deleteSession(sessionId) {
889
+ const existing = this.deleting.get(sessionId);
890
+ if (existing)
891
+ return existing;
892
+ const remove = (async () => {
893
+ // Removing an invalid transcript must not require replaying its body.
894
+ // The persistence catalog still validates its identity and header; the
895
+ // deletion adapter separately validates the artifact's canonical path.
896
+ await this.settingsUpdates.get(sessionId)?.catch(() => undefined);
897
+ const exists = this.pending.has(sessionId) ||
898
+ this.sessions.get(sessionId) ||
899
+ (await this.persistence?.stat(sessionId));
900
+ if (!exists)
901
+ return false;
902
+ await this.cancel(sessionId);
903
+ const handle = this.handles.get(sessionId);
904
+ if (handle) {
905
+ await handle.dispose();
906
+ this.handles.delete(sessionId);
907
+ }
908
+ const stored = await this.persistence?.stat(sessionId);
909
+ if (stored) {
910
+ if (!this.sessionStorePath)
911
+ throw new Error('The configured persistence backend does not support session deletion.');
912
+ await deleteJsonlSession(this.sessionStorePath, stored.header);
913
+ }
914
+ this.pending.delete(sessionId);
915
+ this.pendingCwd.delete(sessionId);
916
+ this.pendingNative.delete(sessionId);
917
+ this.agentUsers.delete(sessionId);
918
+ return true;
919
+ })();
920
+ this.deleting.set(sessionId, remove);
921
+ try {
922
+ return await remove;
923
+ }
924
+ finally {
925
+ this.deleting.delete(sessionId);
926
+ }
927
+ }
928
+ async listAgents() {
929
+ const roots = new Set(this.agents.roots().map(agent => agent.id));
930
+ return this.agents
931
+ .list()
932
+ .map(agent => ({ id: agent.id, root: roots.has(agent.id) }));
933
+ }
934
+ async listTools() {
935
+ if (!this.allowTools)
936
+ return [];
937
+ // No scope yields the deployment-wide registry; per-agent restrictions are
938
+ // applied by the tool runtime at dispatch time, not at listing time.
939
+ return this.tools
940
+ .schemas()
941
+ .filter((schema) => typeof schema.name === 'string')
942
+ .map(schema => ({
943
+ name: schema.name,
944
+ description: typeof schema.description === 'string' ? schema.description : '',
945
+ }))
946
+ .sort((left, right) => left.name.localeCompare(right.name));
947
+ }
948
+ async sendMessage(sessionId, text, options) {
949
+ if (this.disposed)
950
+ throw new Error('The Cordis engine is stopped.');
951
+ if (!this.allowStreaming)
952
+ throw new Error('cordis dsh engine: streaming is disabled by configuration');
953
+ if (this.streams.has(sessionId) ||
954
+ this.stopping.has(sessionId) ||
955
+ this.deleting.has(sessionId) ||
956
+ this.settingsUpdates.has(sessionId)) {
957
+ throw new Error(`cordis dsh engine: session "${sessionId}" already has a response in flight`);
958
+ }
959
+ if (options?.userId)
960
+ this.requestUsers.set(sessionId, options.userId);
961
+ const controller = new AbortController();
962
+ this.controllers.set(sessionId, controller);
963
+ const stream = new ResponseStream(sessionId, () => {
964
+ void this.cancel(sessionId).catch(error => logger.warn('stream cancellation failed', { error: String(error) }));
965
+ }, () => {
966
+ if (this.streams.get(sessionId) === stream)
967
+ this.streams.delete(sessionId);
968
+ this.streamedText.delete(sessionId);
969
+ this.controllers.delete(sessionId);
970
+ this.requestUsers.delete(sessionId);
971
+ });
972
+ this.streams.set(sessionId, stream);
973
+ const start = (async () => {
974
+ const details = await this.getSession(sessionId);
975
+ if (!details)
976
+ throw new Error(`cordis dsh engine: session "${sessionId}" does not exist; create it first`);
977
+ let handle = this.handles.get(sessionId);
978
+ if (handle &&
979
+ (this.agentUsers.get(sessionId) !== options?.userId ||
980
+ details.settings.model === undefined ||
981
+ !isProviderModelIdentity(this.selections.get(sessionId)?.current?.model))) {
982
+ await handle.dispose();
983
+ this.handles.delete(sessionId);
984
+ handle = undefined;
985
+ }
986
+ if (!handle) {
987
+ const userId = options?.userId;
988
+ const provider = this.ctx.get('libreCordisProvider');
989
+ const selectedModel = details.settings.model;
990
+ const model = isProviderModelIdentity(selectedModel)
991
+ ? selectedModel
992
+ : isProviderModelIdentity(this.defaultModel)
993
+ ? this.defaultModel
994
+ : await provider?.defaultModel?.(userId);
995
+ const agentOptions = {};
996
+ if (this.defaultProvider)
997
+ agentOptions.provider = this.defaultProvider;
998
+ if (model)
999
+ agentOptions.model = model;
1000
+ const setup = (agentCtx, agent) => {
1001
+ if (!this.allowTools)
1002
+ agentCtx.tools.restrict({ allow: [] });
1003
+ if (agent.session && this.ctx.get('sandboxPolicy')) {
1004
+ // Even an operator's broader default cannot widen this bridge's
1005
+ // two-mode contract. Persist the actual mode enforced on resume.
1006
+ appendSessionSettings(agent.session, { permissionMode: details.settings.permissionMode }, this.defaultProvider ?? 'libre-webui');
1007
+ }
1008
+ if (model && this.defaultProvider) {
1009
+ const selection = {
1010
+ current: { provider: this.defaultProvider, model },
1011
+ assembled: undefined,
1012
+ };
1013
+ this.selections.set(sessionId, selection);
1014
+ installModelSelection(agentCtx, selection);
1015
+ }
1016
+ };
1017
+ const stored = await this.persistence?.stat(sessionId);
1018
+ if (stored) {
1019
+ await this.resolveWorkspace(stored.header.cwd ?? '');
1020
+ handle = await this.agents.resume({
1021
+ resumeSessionId: sessionId,
1022
+ agentOptions,
1023
+ signal: controller.signal,
1024
+ setup,
1025
+ });
1026
+ }
1027
+ else {
1028
+ const cwd = await this.resolveWorkspace(options?.cwd ?? this.pendingCwd.get(sessionId) ?? '');
1029
+ handle = await this.agents.create({
1030
+ sessionId,
1031
+ meta: { cwd },
1032
+ agentOptions,
1033
+ signal: controller.signal,
1034
+ setup,
1035
+ });
1036
+ }
1037
+ this.handles.set(sessionId, handle);
1038
+ this.agentUsers.set(sessionId, userId);
1039
+ this.pending.delete(sessionId);
1040
+ this.pendingCwd.delete(sessionId);
1041
+ this.pendingNative.delete(sessionId);
1042
+ }
1043
+ controller.signal.throwIfAborted();
1044
+ if (typeof handle.agent.followup !== 'function')
1045
+ throw new Error('cordis dsh engine: the mounted engine has no agent driver that accepts messages');
1046
+ handle.agent.followup(createUserMessage({
1047
+ source: { kind: 'user' },
1048
+ content: [{ type: 'text', text }],
1049
+ }));
1050
+ })();
1051
+ this.starting.set(sessionId, start);
1052
+ try {
1053
+ await start;
1054
+ return stream;
1055
+ }
1056
+ catch (error) {
1057
+ stream.push({ type: 'done', reason: 'error' });
1058
+ stream.close();
1059
+ throw error;
1060
+ }
1061
+ finally {
1062
+ this.starting.delete(sessionId);
1063
+ }
1064
+ }
1065
+ requestUserId(sessionId) {
1066
+ return this.requestUsers.get(sessionId);
1067
+ }
1068
+ async cancel(sessionId) {
1069
+ const inProgress = this.stopping.get(sessionId);
1070
+ if (inProgress) {
1071
+ await inProgress;
1072
+ return true;
1073
+ }
1074
+ const stream = this.streams.get(sessionId);
1075
+ const handle = this.handles.get(sessionId);
1076
+ if (!stream && !handle && !this.starting.has(sessionId))
1077
+ return false;
1078
+ this.controllers.get(sessionId)?.abort();
1079
+ const stop = (async () => {
1080
+ await this.starting.get(sessionId)?.catch(() => undefined);
1081
+ const owned = this.handles.get(sessionId);
1082
+ if (owned) {
1083
+ owned.agent.cancel?.({ kind: 'user' });
1084
+ if (!this.persistence && owned.agent.whenIdle) {
1085
+ // Without a persistence backend, the live agent owns the only copy
1086
+ // of the transcript. Drain cancellation without deleting that copy.
1087
+ await owned.agent.whenIdle();
1088
+ }
1089
+ else {
1090
+ await owned.dispose();
1091
+ this.handles.delete(sessionId);
1092
+ }
1093
+ }
1094
+ stream?.push({ type: 'done', reason: 'cancelled', interrupted: true });
1095
+ })();
1096
+ this.stopping.set(sessionId, stop);
1097
+ try {
1098
+ await stop;
1099
+ }
1100
+ finally {
1101
+ this.stopping.delete(sessionId);
1102
+ }
1103
+ return true;
1104
+ }
1105
+ }
1106
+ /** Contract name under which this service is registered. */
1107
+ LibreDshEngineService.provide = DSH_ENGINE_SERVICE;
1108
+ /**
1109
+ * Mount the bridge on a context that already provides the engine services.
1110
+ * @param ctx - context carrying `sessions`, `agents`, and `tools`.
1111
+ * @param config - bridge configuration from the composition row.
1112
+ */
1113
+ export function apply(ctx, config = {}) {
1114
+ ctx.plugin(LibreDshEngineService, config);
1115
+ }
1116
+ //# sourceMappingURL=engine-plugin.js.map