libre-webui 0.24.0 → 0.25.0

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 (244) hide show
  1. package/README.md +26 -15
  2. package/backend/dist/index.d.ts.map +1 -1
  3. package/backend/dist/index.js +9 -0
  4. package/backend/dist/index.js.map +1 -1
  5. package/backend/dist/persistence/postgresMigrationRegistry.d.ts.map +1 -1
  6. package/backend/dist/persistence/postgresMigrationRegistry.js +2 -0
  7. package/backend/dist/persistence/postgresMigrationRegistry.js.map +1 -1
  8. package/backend/dist/persistence/postgresPersonalAutomationsMigration.d.ts +8 -0
  9. package/backend/dist/persistence/postgresPersonalAutomationsMigration.d.ts.map +1 -0
  10. package/backend/dist/persistence/postgresPersonalAutomationsMigration.js +98 -0
  11. package/backend/dist/persistence/postgresPersonalAutomationsMigration.js.map +1 -0
  12. package/backend/dist/persistence/postgresResourceRepositories.d.ts.map +1 -1
  13. package/backend/dist/persistence/postgresResourceRepositories.js +274 -0
  14. package/backend/dist/persistence/postgresResourceRepositories.js.map +1 -1
  15. package/backend/dist/persistence/resourceTypes.d.ts +83 -2
  16. package/backend/dist/persistence/resourceTypes.d.ts.map +1 -1
  17. package/backend/dist/persistence/resourceTypes.js.map +1 -1
  18. package/backend/dist/persistence/sqliteMigrations.d.ts +1 -0
  19. package/backend/dist/persistence/sqliteMigrations.d.ts.map +1 -1
  20. package/backend/dist/persistence/sqliteMigrations.js +181 -0
  21. package/backend/dist/persistence/sqliteMigrations.js.map +1 -1
  22. package/backend/dist/persistence/sqliteResourceRepositories.d.ts.map +1 -1
  23. package/backend/dist/persistence/sqliteResourceRepositories.js +233 -0
  24. package/backend/dist/persistence/sqliteResourceRepositories.js.map +1 -1
  25. package/backend/dist/persistence/sqliteToPostgresMigration.d.ts.map +1 -1
  26. package/backend/dist/persistence/sqliteToPostgresMigration.js +41 -0
  27. package/backend/dist/persistence/sqliteToPostgresMigration.js.map +1 -1
  28. package/backend/dist/platform/jobs/domainJobContracts.d.ts +3 -0
  29. package/backend/dist/platform/jobs/domainJobContracts.d.ts.map +1 -1
  30. package/backend/dist/platform/jobs/domainJobContracts.js +3 -0
  31. package/backend/dist/platform/jobs/domainJobContracts.js.map +1 -1
  32. package/backend/dist/platform/jobs/domainJobHandlers.d.ts.map +1 -1
  33. package/backend/dist/platform/jobs/domainJobHandlers.js +101 -1
  34. package/backend/dist/platform/jobs/domainJobHandlers.js.map +1 -1
  35. package/backend/dist/routes/automations.d.ts +3 -0
  36. package/backend/dist/routes/automations.d.ts.map +1 -0
  37. package/backend/dist/routes/automations.js +232 -0
  38. package/backend/dist/routes/automations.js.map +1 -0
  39. package/backend/dist/routes/calendar.d.ts +3 -0
  40. package/backend/dist/routes/calendar.d.ts.map +1 -0
  41. package/backend/dist/routes/calendar.js +206 -0
  42. package/backend/dist/routes/calendar.js.map +1 -0
  43. package/backend/dist/routes/chat.d.ts.map +1 -1
  44. package/backend/dist/routes/chat.js +99 -7
  45. package/backend/dist/routes/chat.js.map +1 -1
  46. package/backend/dist/routes/ollama.d.ts.map +1 -1
  47. package/backend/dist/routes/ollama.js +4 -1
  48. package/backend/dist/routes/ollama.js.map +1 -1
  49. package/backend/dist/routes/search.d.ts.map +1 -1
  50. package/backend/dist/routes/search.js +5 -3
  51. package/backend/dist/routes/search.js.map +1 -1
  52. package/backend/dist/services/automationSchedulerService.d.ts +28 -0
  53. package/backend/dist/services/automationSchedulerService.d.ts.map +1 -0
  54. package/backend/dist/services/automationSchedulerService.js +191 -0
  55. package/backend/dist/services/automationSchedulerService.js.map +1 -0
  56. package/backend/dist/services/automationService.d.ts +53 -0
  57. package/backend/dist/services/automationService.d.ts.map +1 -0
  58. package/backend/dist/services/automationService.js +224 -0
  59. package/backend/dist/services/automationService.js.map +1 -0
  60. package/backend/dist/services/chatGenerationService.d.ts.map +1 -1
  61. package/backend/dist/services/chatGenerationService.js +7 -1
  62. package/backend/dist/services/chatGenerationService.js.map +1 -1
  63. package/backend/dist/services/chatService.d.ts +9 -1
  64. package/backend/dist/services/chatService.d.ts.map +1 -1
  65. package/backend/dist/services/chatService.js +91 -13
  66. package/backend/dist/services/chatService.js.map +1 -1
  67. package/backend/dist/services/contextCompactionService.d.ts +11 -6
  68. package/backend/dist/services/contextCompactionService.d.ts.map +1 -1
  69. package/backend/dist/services/contextCompactionService.js +127 -27
  70. package/backend/dist/services/contextCompactionService.js.map +1 -1
  71. package/backend/dist/services/durableChatGenerationService.d.ts.map +1 -1
  72. package/backend/dist/services/durableChatGenerationService.js +15 -3
  73. package/backend/dist/services/durableChatGenerationService.js.map +1 -1
  74. package/backend/dist/services/ollamaService.d.ts.map +1 -1
  75. package/backend/dist/services/ollamaService.js +9 -2
  76. package/backend/dist/services/ollamaService.js.map +1 -1
  77. package/backend/dist/services/pluginService.d.ts +10 -0
  78. package/backend/dist/services/pluginService.d.ts.map +1 -1
  79. package/backend/dist/services/pluginService.js +42 -7
  80. package/backend/dist/services/pluginService.js.map +1 -1
  81. package/backend/dist/services/preferencesService.d.ts.map +1 -1
  82. package/backend/dist/services/preferencesService.js +3 -0
  83. package/backend/dist/services/preferencesService.js.map +1 -1
  84. package/backend/dist/services/titleGenerationService.d.ts.map +1 -1
  85. package/backend/dist/services/titleGenerationService.js +5 -1
  86. package/backend/dist/services/titleGenerationService.js.map +1 -1
  87. package/backend/dist/services/webSearchPlanService.d.ts +18 -0
  88. package/backend/dist/services/webSearchPlanService.d.ts.map +1 -0
  89. package/backend/dist/services/webSearchPlanService.js +160 -0
  90. package/backend/dist/services/webSearchPlanService.js.map +1 -0
  91. package/backend/dist/services/webSearchService.d.ts +9 -3
  92. package/backend/dist/services/webSearchService.d.ts.map +1 -1
  93. package/backend/dist/services/webSearchService.js +8 -2
  94. package/backend/dist/services/webSearchService.js.map +1 -1
  95. package/backend/dist/storage.d.ts +7 -1
  96. package/backend/dist/storage.d.ts.map +1 -1
  97. package/backend/dist/storage.js +65 -1
  98. package/backend/dist/storage.js.map +1 -1
  99. package/backend/dist/types/index.d.ts +79 -0
  100. package/backend/dist/types/index.d.ts.map +1 -1
  101. package/backend/dist/types/index.js.map +1 -1
  102. package/backend/dist/utils/automationSchedule.d.ts +26 -0
  103. package/backend/dist/utils/automationSchedule.d.ts.map +1 -0
  104. package/backend/dist/utils/automationSchedule.js +231 -0
  105. package/backend/dist/utils/automationSchedule.js.map +1 -0
  106. package/backend/dist/utils/chatContext.d.ts +7 -0
  107. package/backend/dist/utils/chatContext.d.ts.map +1 -1
  108. package/backend/dist/utils/chatContext.js +13 -2
  109. package/backend/dist/utils/chatContext.js.map +1 -1
  110. package/backend/dist/utils/openAIResponsesAdapter.d.ts.map +1 -1
  111. package/backend/dist/utils/openAIResponsesAdapter.js +3 -1
  112. package/backend/dist/utils/openAIResponsesAdapter.js.map +1 -1
  113. package/backend/dist/utils/pluginChatAdapter.d.ts +1 -0
  114. package/backend/dist/utils/pluginChatAdapter.d.ts.map +1 -1
  115. package/backend/dist/utils/pluginChatAdapter.js +82 -19
  116. package/backend/dist/utils/pluginChatAdapter.js.map +1 -1
  117. package/backend/dist/utils/pluginModelCatalog.d.ts +20 -0
  118. package/backend/dist/utils/pluginModelCatalog.d.ts.map +1 -1
  119. package/backend/dist/utils/pluginModelCatalog.js +101 -9
  120. package/backend/dist/utils/pluginModelCatalog.js.map +1 -1
  121. package/backend/dist/utils/resourceLimits.d.ts +7 -0
  122. package/backend/dist/utils/resourceLimits.d.ts.map +1 -1
  123. package/backend/dist/utils/resourceLimits.js +7 -0
  124. package/backend/dist/utils/resourceLimits.js.map +1 -1
  125. package/backend/dist/utils/thinkingOptions.d.ts +23 -3
  126. package/backend/dist/utils/thinkingOptions.d.ts.map +1 -1
  127. package/backend/dist/utils/thinkingOptions.js +35 -9
  128. package/backend/dist/utils/thinkingOptions.js.map +1 -1
  129. package/backend/dist/websocketServer.d.ts.map +1 -1
  130. package/backend/dist/websocketServer.js +9 -3
  131. package/backend/dist/websocketServer.js.map +1 -1
  132. package/backend/package.json +1 -1
  133. package/frontend/dist/artifact-runtime/.build-fingerprint +1 -1
  134. package/frontend/dist/assets/index-6o-Hc2wS.js +41 -0
  135. package/frontend/dist/assets/index-D-pM957b.css +1 -0
  136. package/frontend/dist/index.html +12 -12
  137. package/frontend/dist/js/ArtifactContainer-CpUi6ekF.js +1 -0
  138. package/frontend/dist/js/{ArtifactDemoPage-TONeQvof.js → ArtifactDemoPage-BeNs3gHp.js} +1 -1
  139. package/frontend/dist/js/{ArtifactSandboxFrame-CV2XXu_t.js → ArtifactSandboxFrame-qsj_Y3LV.js} +2 -2
  140. package/frontend/dist/js/ArtifactSlideOutPanel-CSFRluWA.js +1 -0
  141. package/frontend/dist/js/AutomationsPage-KUeWoZNv.js +1 -0
  142. package/frontend/dist/js/CalendarPage-D9I972w8.js +1 -0
  143. package/frontend/dist/js/ChatPage-CmBx4oAL.js +16 -0
  144. package/frontend/dist/js/{FirstTimeSetup-smhj0X6o.js → FirstTimeSetup-Df0YJni3.js} +1 -1
  145. package/frontend/dist/js/GalleryPage-bLCrdJN_.js +1 -0
  146. package/frontend/dist/js/HomePage-Uc6PEg-U.js +1 -0
  147. package/frontend/dist/js/{LibreClawPage-CRKei1MM.js → LibreClawPage-CG63DHg1.js} +1 -1
  148. package/frontend/dist/js/{ModelManagerModals-D0amaOnl.js → ModelManagerModals-DcLGYN3l.js} +1 -1
  149. package/frontend/dist/js/ModelSelector-yxJC64rM.js +1 -0
  150. package/frontend/dist/js/ModelsPage-1R_TxgmJ.js +2 -0
  151. package/frontend/dist/js/NotesPage-L65j5HoW.js +1 -0
  152. package/frontend/dist/js/PersonasPage-CKeBpHIH.js +13 -0
  153. package/frontend/dist/js/PluginUsagePage-BX1SKMbW.js +1 -0
  154. package/frontend/dist/js/{RichMessageContent-Bxi_u0gC.js → RichMessageContent-DiySVYGB.js} +1 -1
  155. package/frontend/dist/js/SettingsModal-68SG1uZ2.js +1 -0
  156. package/frontend/dist/js/SystemPage-CytpZiQV.js +1 -0
  157. package/frontend/dist/js/UserManagementPage-BzJBvZiS.js +1 -0
  158. package/frontend/dist/js/WorkPage-C8gr0QJe.js +6 -0
  159. package/frontend/dist/js/api-BDtqBtqw.js +14 -0
  160. package/frontend/dist/js/{appStore-CJtomipz.js → appStore-0JnCUlEq.js} +2 -2
  161. package/frontend/dist/js/ar-CSpi37Gr.js +2 -0
  162. package/frontend/dist/js/{authStore-BckFDL70.js → authStore-DbMdvNgJ.js} +3 -3
  163. package/frontend/dist/js/{babel-D24oDI9y.js → babel-BsYoxD-7.js} +1 -1
  164. package/frontend/dist/js/bn-CyH1BeDD.js +2 -0
  165. package/frontend/dist/js/calendarDates-Co3o_05Y.js +1 -0
  166. package/frontend/dist/js/chatStore-CHQ2uINS.js +3 -0
  167. package/frontend/dist/js/{config-CNVNpf_q.js → config-DSOYQ9a3.js} +1 -1
  168. package/frontend/dist/js/cs-Cv2NfRK5.js +2 -0
  169. package/frontend/dist/js/da-D6D6IvPK.js +2 -0
  170. package/frontend/dist/js/de-BNoJJTrT.js +2 -0
  171. package/frontend/dist/js/es-kzo9C7q_.js +2 -0
  172. package/frontend/dist/js/{estree-D1TYSBcf.js → estree-BcnBcOAx.js} +7 -7
  173. package/frontend/dist/js/fr-CjHYvJRp.js +2 -0
  174. package/frontend/dist/js/hi-BU98AdCQ.js +2 -0
  175. package/frontend/dist/js/{html-Dmddj0zf.js → html-DkY55Jys.js} +10 -10
  176. package/frontend/dist/js/id-CpdedejX.js +2 -0
  177. package/frontend/dist/js/is-Cp5dDWBH.js +2 -0
  178. package/frontend/dist/js/it-CuH_cK4U.js +2 -0
  179. package/frontend/dist/js/ja-BWXXXiHn.js +2 -0
  180. package/frontend/dist/js/ko-DQgv2M1t.js +2 -0
  181. package/frontend/dist/js/{markdown-Bl_LsHEk.js → markdown-IZSI9DXw.js} +6 -6
  182. package/frontend/dist/js/{messageCodeStyles-CgKxn4tN.js → messageCodeStyles-BFNR9Wv6.js} +2 -2
  183. package/frontend/dist/js/ms-DCcRoIcX.js +2 -0
  184. package/frontend/dist/js/nl-CbKR1_EU.js +2 -0
  185. package/frontend/dist/js/pl-CRyJZccn.js +2 -0
  186. package/frontend/dist/js/{postcss-Bx9CTcIc.js → postcss-BJ0okz7n.js} +1 -1
  187. package/frontend/dist/js/pt-BXYmHFGH.js +2 -0
  188. package/frontend/dist/js/{router-vendor-CbPDks39.js → router-vendor-DGFGM3EP.js} +1 -1
  189. package/frontend/dist/js/ru-M9SyZG4b.js +2 -0
  190. package/frontend/dist/js/{standalone-BWau8hEE.js → standalone-Csw3jfJ_.js} +5 -5
  191. package/frontend/dist/js/sv-DJ486LTU.js +2 -0
  192. package/frontend/dist/js/th-Ch7gKtZv.js +2 -0
  193. package/frontend/dist/js/tr-ClehWhSC.js +2 -0
  194. package/frontend/dist/js/{typescript-DxGn3Yn-.js → typescript-CnF4UVx7.js} +2 -2
  195. package/frontend/dist/js/{ui-jlx9WA5A.js → ui-CC-K19wO.js} +2 -2
  196. package/frontend/dist/js/ui-vendor-B1UgO3vd.js +177 -0
  197. package/frontend/dist/js/uk-BqAd5zHF.js +2 -0
  198. package/frontend/dist/js/{vi-BgRaIrQC.js → vi-D9gjlDVB.js} +2 -2
  199. package/frontend/dist/js/{workApi-DWRpZ_cq.js → workApi-C3uy9EDT.js} +3 -3
  200. package/frontend/dist/js/{yaml-DioTxVLA.js → yaml-DzT5hBSH.js} +5 -5
  201. package/frontend/dist/js/zh-DKTyHkiR.js +2 -0
  202. package/package.json +2 -2
  203. package/frontend/dist/assets/index-BlTbxYPP.js +0 -33
  204. package/frontend/dist/assets/index-CbiWSGOj.css +0 -1
  205. package/frontend/dist/js/ArtifactContainer-C-n1vPsA.js +0 -1
  206. package/frontend/dist/js/ArtifactSlideOutPanel-2exw7YqL.js +0 -1
  207. package/frontend/dist/js/ChatPage-wnyG_TyC.js +0 -23
  208. package/frontend/dist/js/GalleryPage-BZfeWlBg.js +0 -1
  209. package/frontend/dist/js/HomePage-gf4GgI6z.js +0 -1
  210. package/frontend/dist/js/ModelSelector-C5Tn6y0p.js +0 -1
  211. package/frontend/dist/js/ModelsPage-Rbags4Xq.js +0 -2
  212. package/frontend/dist/js/NotesPage-9NR9HZZ8.js +0 -1
  213. package/frontend/dist/js/PersonasPage-DYP67Qiy.js +0 -13
  214. package/frontend/dist/js/PluginUsagePage-QtCgymRu.js +0 -1
  215. package/frontend/dist/js/SettingsModal-B4RelkLP.js +0 -1
  216. package/frontend/dist/js/SystemPage-xFSTYdBS.js +0 -1
  217. package/frontend/dist/js/UserManagementPage-Cp430WCP.js +0 -1
  218. package/frontend/dist/js/WorkPage-DFES41Qm.js +0 -6
  219. package/frontend/dist/js/api-DUUxxx_B.js +0 -14
  220. package/frontend/dist/js/ar-DMH3qbt6.js +0 -2
  221. package/frontend/dist/js/bn-CGDX8khx.js +0 -2
  222. package/frontend/dist/js/chatStore-DK-_wd-j.js +0 -3
  223. package/frontend/dist/js/cs-CJLgUk3m.js +0 -2
  224. package/frontend/dist/js/da-DnZMnCVM.js +0 -2
  225. package/frontend/dist/js/de-CWpguVQ5.js +0 -2
  226. package/frontend/dist/js/es-DVU1vjm0.js +0 -2
  227. package/frontend/dist/js/fr-pWnSnypD.js +0 -2
  228. package/frontend/dist/js/hi-B6xDfxA5.js +0 -2
  229. package/frontend/dist/js/id-BThSTUAG.js +0 -2
  230. package/frontend/dist/js/is-BOohDFie.js +0 -2
  231. package/frontend/dist/js/it-BdRArcCC.js +0 -2
  232. package/frontend/dist/js/ja-5Cq35sMr.js +0 -2
  233. package/frontend/dist/js/ko-BlzNRtpQ.js +0 -2
  234. package/frontend/dist/js/ms-CtGeurkZ.js +0 -2
  235. package/frontend/dist/js/nl-MKPHGjVF.js +0 -2
  236. package/frontend/dist/js/pl-BwBrqCey.js +0 -2
  237. package/frontend/dist/js/pt-CKzoHWBB.js +0 -2
  238. package/frontend/dist/js/ru-BQC4jh0Y.js +0 -2
  239. package/frontend/dist/js/sv-Cm0qXxzM.js +0 -2
  240. package/frontend/dist/js/th-DUPbiRI2.js +0 -2
  241. package/frontend/dist/js/tr-AsFu_Br5.js +0 -2
  242. package/frontend/dist/js/ui-vendor-54SgntkX.js +0 -177
  243. package/frontend/dist/js/uk-DrMMOZ17.js +0 -2
  244. package/frontend/dist/js/zh-B4rcgcAy.js +0 -2
@@ -0,0 +1,232 @@
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
+ import express from 'express';
18
+ import automationService from '../services/automationService.js';
19
+ import automationSchedulerService from '../services/automationSchedulerService.js';
20
+ import { authenticate } from '../middleware/auth.js';
21
+ import { ResourcePolicyError } from '../utils/resourceLimits.js';
22
+ import { InvalidTriggerError, occurrencesBetween, } from '../utils/automationSchedule.js';
23
+ const router = express.Router();
24
+ router.use(authenticate);
25
+ const userIdOf = (req) => req.user?.userId || 'default';
26
+ /** Occurrence projections span at most 62 days per request. */
27
+ const MAX_OCCURRENCE_RANGE_MS = 62 * 24 * 60 * 60 * 1000;
28
+ function sendAutomationError(res, error, fallback) {
29
+ if (error instanceof ResourcePolicyError ||
30
+ error instanceof InvalidTriggerError) {
31
+ const statusCode = error instanceof ResourcePolicyError ? error.statusCode : 400;
32
+ res.status(statusCode).json({ success: false, error: error.message });
33
+ return;
34
+ }
35
+ res.status(500).json({
36
+ success: false,
37
+ error: error instanceof Error ? error.message : fallback,
38
+ });
39
+ }
40
+ function readEpoch(value, field) {
41
+ const parsed = typeof value === 'string' ? Number(value) : value;
42
+ if (typeof parsed !== 'number' ||
43
+ !Number.isSafeInteger(parsed) ||
44
+ parsed < 0) {
45
+ throw new ResourcePolicyError(`${field} must be an epoch-ms integer`, 400);
46
+ }
47
+ return parsed;
48
+ }
49
+ function readAutomationBody(body) {
50
+ const provider = typeof body.provider === 'string' && body.provider
51
+ ? body.provider
52
+ : undefined;
53
+ const model = typeof body.model === 'string' && body.model ? body.model : undefined;
54
+ return {
55
+ name: typeof body.name === 'string' ? body.name : '',
56
+ instructions: typeof body.instructions === 'string' ? body.instructions : '',
57
+ triggers: body.triggers,
58
+ ...(provider ? { provider } : {}),
59
+ ...(model ? { model } : {}),
60
+ notify: body.notify === 'off' ? 'off' : 'app',
61
+ };
62
+ }
63
+ router.get('/', async (req, res) => {
64
+ try {
65
+ res.json({
66
+ success: true,
67
+ data: await automationService.getAutomations(userIdOf(req)),
68
+ });
69
+ }
70
+ catch (error) {
71
+ sendAutomationError(res, error, 'Failed to load automations');
72
+ }
73
+ });
74
+ router.post('/', async (req, res) => {
75
+ try {
76
+ const automation = await automationService.createAutomation(readAutomationBody((req.body ?? {})), userIdOf(req));
77
+ res.json({ success: true, data: automation });
78
+ }
79
+ catch (error) {
80
+ sendAutomationError(res, error, 'Failed to create automation');
81
+ }
82
+ });
83
+ /** Upcoming computed occurrences across the caller's active automations. */
84
+ router.get('/occurrences', async (req, res) => {
85
+ try {
86
+ const from = readEpoch(req.query.from, 'from');
87
+ const to = readEpoch(req.query.to, 'to');
88
+ if (to <= from || to - from > MAX_OCCURRENCE_RANGE_MS) {
89
+ throw new ResourcePolicyError('The requested range must be positive and span at most 62 days', 400);
90
+ }
91
+ const automations = await automationService.getAutomations(userIdOf(req));
92
+ const occurrences = automations
93
+ .filter(automation => automation.status === 'active')
94
+ .flatMap(automation => occurrencesBetween(automation.triggers, from, to, 200).map(at => ({
95
+ automationId: automation.id,
96
+ name: automation.name,
97
+ at,
98
+ })))
99
+ .sort((left, right) => left.at - right.at);
100
+ res.json({ success: true, data: occurrences });
101
+ }
102
+ catch (error) {
103
+ sendAutomationError(res, error, 'Failed to project occurrences');
104
+ }
105
+ });
106
+ router.get('/runs', async (req, res) => {
107
+ try {
108
+ const options = {};
109
+ if (typeof req.query.automationId === 'string') {
110
+ options.automationId = req.query.automationId;
111
+ }
112
+ if (req.query.from !== undefined) {
113
+ options.from = readEpoch(req.query.from, 'from');
114
+ }
115
+ if (req.query.to !== undefined) {
116
+ options.to = readEpoch(req.query.to, 'to');
117
+ }
118
+ res.json({
119
+ success: true,
120
+ data: await automationService.listRuns(userIdOf(req), options),
121
+ });
122
+ }
123
+ catch (error) {
124
+ sendAutomationError(res, error, 'Failed to load automation runs');
125
+ }
126
+ });
127
+ router.get('/runs/summary', async (req, res) => {
128
+ try {
129
+ res.json({
130
+ success: true,
131
+ data: await automationService.runsSummary(userIdOf(req)),
132
+ });
133
+ }
134
+ catch (error) {
135
+ sendAutomationError(res, error, 'Failed to load the run summary');
136
+ }
137
+ });
138
+ router.post('/runs/seen', async (req, res) => {
139
+ try {
140
+ const marked = await automationService.markRunsSeen(userIdOf(req));
141
+ res.json({ success: true, data: { marked } });
142
+ }
143
+ catch (error) {
144
+ sendAutomationError(res, error, 'Failed to mark runs as seen');
145
+ }
146
+ });
147
+ router.get('/:automationId', async (req, res) => {
148
+ try {
149
+ const automation = await automationService.getAutomation(req.params.automationId, userIdOf(req));
150
+ if (!automation) {
151
+ res.status(404).json({
152
+ success: false,
153
+ error: 'Automation not found',
154
+ });
155
+ return;
156
+ }
157
+ res.json({ success: true, data: automation });
158
+ }
159
+ catch (error) {
160
+ sendAutomationError(res, error, 'Failed to load the automation');
161
+ }
162
+ });
163
+ router.put('/:automationId', async (req, res) => {
164
+ try {
165
+ const automation = await automationService.updateAutomation(req.params.automationId, readAutomationBody((req.body ?? {})), userIdOf(req));
166
+ if (!automation) {
167
+ res.status(404).json({
168
+ success: false,
169
+ error: 'Automation not found',
170
+ });
171
+ return;
172
+ }
173
+ res.json({ success: true, data: automation });
174
+ }
175
+ catch (error) {
176
+ sendAutomationError(res, error, 'Failed to update the automation');
177
+ }
178
+ });
179
+ router.delete('/:automationId', async (req, res) => {
180
+ try {
181
+ const deleted = await automationService.deleteAutomation(req.params.automationId, userIdOf(req));
182
+ if (!deleted) {
183
+ res.status(404).json({
184
+ success: false,
185
+ error: 'Automation not found',
186
+ });
187
+ return;
188
+ }
189
+ res.json({ success: true, message: 'Automation deleted' });
190
+ }
191
+ catch (error) {
192
+ sendAutomationError(res, error, 'Failed to delete the automation');
193
+ }
194
+ });
195
+ const setStatus = async (req, res, status) => {
196
+ try {
197
+ const automation = await automationService.setAutomationStatus(req.params.automationId, userIdOf(req), status);
198
+ if (!automation) {
199
+ res.status(404).json({
200
+ success: false,
201
+ error: 'Automation not found',
202
+ });
203
+ return;
204
+ }
205
+ res.json({ success: true, data: automation });
206
+ }
207
+ catch (error) {
208
+ sendAutomationError(res, error, 'Failed to change the automation status');
209
+ }
210
+ };
211
+ router.post('/:automationId/pause', (req, res) => setStatus(req, res, 'paused'));
212
+ router.post('/:automationId/resume', (req, res) => setStatus(req, res, 'active'));
213
+ router.post('/:automationId/run', async (req, res) => {
214
+ try {
215
+ const userId = userIdOf(req);
216
+ const automation = await automationService.getAutomation(req.params.automationId, userId);
217
+ if (!automation) {
218
+ res.status(404).json({
219
+ success: false,
220
+ error: 'Automation not found',
221
+ });
222
+ return;
223
+ }
224
+ const runId = await automationSchedulerService.runNow(automation.id, userId);
225
+ res.status(202).json({ success: true, data: { runId } });
226
+ }
227
+ catch (error) {
228
+ sendAutomationError(res, error, 'Failed to start the automation run');
229
+ }
230
+ });
231
+ export default router;
232
+ //# sourceMappingURL=automations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automations.js","sourceRoot":"","sources":["../../src/routes/automations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
3
+ //# sourceMappingURL=calendar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../src/routes/calendar.ts"],"names":[],"mappings":"AAiCA,QAAA,MAAM,MAAM,4CAAmB,CAAC;eAqPjB,MAAM"}
@@ -0,0 +1,206 @@
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
+ import express from 'express';
18
+ import { v4 as uuidv4 } from 'uuid';
19
+ import storageService from '../storage.js';
20
+ import { authenticate } from '../middleware/auth.js';
21
+ import { MAX_CALENDAR_EVENT_NOTES_LENGTH, MAX_CALENDAR_EVENT_TITLE_LENGTH, ResourcePolicyError, } from '../utils/resourceLimits.js';
22
+ import { InvalidTriggerError, occurrencesBetween, validateTriggers, } from '../utils/automationSchedule.js';
23
+ const router = express.Router();
24
+ router.use(authenticate);
25
+ const userIdOf = (req) => req.user?.userId || 'default';
26
+ /** A calendar range query may span at most 13 months. */
27
+ const MAX_RANGE_MS = 13 * 32 * 24 * 60 * 60 * 1000;
28
+ function sendCalendarError(res, error, fallback) {
29
+ if (error instanceof ResourcePolicyError ||
30
+ error instanceof InvalidTriggerError) {
31
+ const statusCode = error instanceof ResourcePolicyError ? error.statusCode : 400;
32
+ res.status(statusCode).json({ success: false, error: error.message });
33
+ return;
34
+ }
35
+ res.status(500).json({
36
+ success: false,
37
+ error: error instanceof Error ? error.message : fallback,
38
+ });
39
+ }
40
+ function readTextField(value, field, maximum, required) {
41
+ if (value === undefined || value === null || value === '') {
42
+ if (required) {
43
+ throw new ResourcePolicyError(`${field} is required`, 400);
44
+ }
45
+ return undefined;
46
+ }
47
+ if (typeof value !== 'string') {
48
+ throw new ResourcePolicyError(`${field} must be a string`, 400);
49
+ }
50
+ if (value.length > maximum) {
51
+ throw new ResourcePolicyError(`${field} exceeds the maximum length of ${maximum} characters`, 400);
52
+ }
53
+ return value;
54
+ }
55
+ function readEpoch(value, field) {
56
+ const parsed = typeof value === 'string' ? Number(value) : value;
57
+ if (typeof parsed !== 'number' ||
58
+ !Number.isSafeInteger(parsed) ||
59
+ parsed < 0) {
60
+ throw new ResourcePolicyError(`${field} must be an epoch-ms integer`, 400);
61
+ }
62
+ return parsed;
63
+ }
64
+ function readRange(req) {
65
+ const from = readEpoch(req.query.from, 'from');
66
+ const to = readEpoch(req.query.to, 'to');
67
+ if (to <= from || to - from > MAX_RANGE_MS) {
68
+ throw new ResourcePolicyError('The requested range must be positive and span at most 13 months', 400);
69
+ }
70
+ return { from, to };
71
+ }
72
+ function readEventBody(body, existing) {
73
+ const title = readTextField(body.title, 'title', MAX_CALENDAR_EVENT_TITLE_LENGTH, existing === undefined) ??
74
+ existing?.title ??
75
+ '';
76
+ const notes = body.notes !== undefined
77
+ ? readTextField(body.notes, 'notes', MAX_CALENDAR_EVENT_NOTES_LENGTH, false)
78
+ : existing?.notes;
79
+ const startAt = body.startAt !== undefined
80
+ ? readEpoch(body.startAt, 'startAt')
81
+ : existing?.startAt;
82
+ if (startAt === undefined) {
83
+ throw new ResourcePolicyError('startAt is required', 400);
84
+ }
85
+ const endAt = body.endAt !== undefined && body.endAt !== null
86
+ ? readEpoch(body.endAt, 'endAt')
87
+ : body.endAt === null
88
+ ? undefined
89
+ : existing?.endAt;
90
+ if (endAt !== undefined && endAt < startAt) {
91
+ throw new ResourcePolicyError('endAt must not precede startAt', 400);
92
+ }
93
+ const allDay = body.allDay !== undefined
94
+ ? body.allDay === true
95
+ : (existing?.allDay ?? false);
96
+ const recurrence = body.recurrence !== undefined
97
+ ? body.recurrence === null
98
+ ? undefined
99
+ : validateTriggers([body.recurrence], 1)[0]
100
+ : existing?.recurrence;
101
+ return {
102
+ title,
103
+ ...(notes !== undefined ? { notes } : {}),
104
+ startAt,
105
+ ...(endAt !== undefined ? { endAt } : {}),
106
+ allDay,
107
+ ...(recurrence !== undefined ? { recurrence } : {}),
108
+ };
109
+ }
110
+ /** Project a recurring event's occurrences into [from, to). */
111
+ function expandRecurring(event, from, to) {
112
+ if (!event.recurrence)
113
+ return [];
114
+ const duration = event.endAt !== undefined ? event.endAt - event.startAt : 0;
115
+ const effectiveFrom = Math.max(from, event.startAt);
116
+ if (effectiveFrom >= to)
117
+ return [];
118
+ return occurrencesBetween([event.recurrence], effectiveFrom, to)
119
+ .filter(occurrence => occurrence !== event.startAt)
120
+ .map(occurrence => ({
121
+ ...event,
122
+ id: `${event.id}:${occurrence}`,
123
+ baseEventId: event.id,
124
+ startAt: occurrence,
125
+ ...(event.endAt !== undefined ? { endAt: occurrence + duration } : {}),
126
+ }));
127
+ }
128
+ router.get('/events', async (req, res) => {
129
+ try {
130
+ const userId = userIdOf(req);
131
+ const { from, to } = readRange(req);
132
+ const [inRange, recurring] = await Promise.all([
133
+ storageService.getCalendarEventsBetween(from, to, userId),
134
+ storageService.getRecurringCalendarEvents(userId),
135
+ ]);
136
+ const seen = new Set(inRange.map(event => event.id));
137
+ const expanded = recurring.flatMap(event => expandRecurring(event, from, to).filter(occurrence => !seen.has(occurrence.id)));
138
+ const events = [...inRange, ...expanded].sort((left, right) => left.startAt - right.startAt);
139
+ res.json({ success: true, data: events });
140
+ }
141
+ catch (error) {
142
+ sendCalendarError(res, error, 'Failed to load calendar events');
143
+ }
144
+ });
145
+ router.post('/events', async (req, res) => {
146
+ try {
147
+ const now = Date.now();
148
+ const event = {
149
+ id: uuidv4(),
150
+ ...readEventBody((req.body ?? {})),
151
+ createdAt: now,
152
+ updatedAt: now,
153
+ };
154
+ await storageService.saveCalendarEvent(event, userIdOf(req));
155
+ res.json({ success: true, data: event });
156
+ }
157
+ catch (error) {
158
+ sendCalendarError(res, error, 'Failed to create calendar event');
159
+ }
160
+ });
161
+ router.put('/events/:eventId', async (req, res) => {
162
+ try {
163
+ const userId = userIdOf(req);
164
+ const existing = await storageService.getCalendarEvent(req.params.eventId, userId);
165
+ if (!existing) {
166
+ res.status(404).json({
167
+ success: false,
168
+ error: 'Calendar event not found',
169
+ });
170
+ return;
171
+ }
172
+ const event = {
173
+ ...existing,
174
+ ...readEventBody((req.body ?? {}), existing),
175
+ id: existing.id,
176
+ createdAt: existing.createdAt,
177
+ updatedAt: Date.now(),
178
+ };
179
+ await storageService.saveCalendarEvent(event, userId);
180
+ res.json({ success: true, data: event });
181
+ }
182
+ catch (error) {
183
+ sendCalendarError(res, error, 'Failed to update calendar event');
184
+ }
185
+ });
186
+ router.delete('/events/:eventId', async (req, res) => {
187
+ try {
188
+ const deleted = await storageService.deleteCalendarEvent(req.params.eventId, userIdOf(req));
189
+ if (!deleted) {
190
+ res.status(404).json({
191
+ success: false,
192
+ error: 'Calendar event not found',
193
+ });
194
+ return;
195
+ }
196
+ res.json({
197
+ success: true,
198
+ message: 'Calendar event deleted',
199
+ });
200
+ }
201
+ catch (error) {
202
+ sendCalendarError(res, error, 'Failed to delete calendar event');
203
+ }
204
+ });
205
+ export default router;
206
+ //# sourceMappingURL=calendar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar.js","sourceRoot":"","sources":["../../src/routes/calendar.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/routes/chat.ts"],"names":[],"mappings":"AA8IA,QAAA,MAAM,MAAM,4CAAmB,CAAC;eAgtDjB,MAAM"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/routes/chat.ts"],"names":[],"mappings":"AA6IA,QAAA,MAAM,MAAM,4CAAmB,CAAC;eA+zDjB,MAAM"}
@@ -26,7 +26,8 @@ import { authenticate, requireAdmin, } from '../middleware/auth.js';
26
26
  import { getCompactionConfig, setCompactionConfig, } from '../services/contextCompactionService.js';
27
27
  import { extractStatistics } from '../utils/generationUtils.js';
28
28
  import agentCliService from '../services/agentCliService.js';
29
- import { buildWebSearchEnhancedContent, isWebSearchAvailable, userCanUseWebSearch, webSearch as runWebSearch, } from '../services/webSearchService.js';
29
+ import { buildWebSearchEnhancedContent, isWebSearchAvailable, userCanUseWebSearch, } from '../services/webSearchService.js';
30
+ import { runPlannedWebSearch } from '../services/webSearchPlanService.js';
30
31
  import { userModel } from '../models/userModel.js';
31
32
  import chatGenerationService from '../services/chatGenerationService.js';
32
33
  import preferencesService from '../services/preferencesService.js';
@@ -268,6 +269,34 @@ router.put('/sessions/:sessionId/messages/:messageId', async (req, res) => {
268
269
  });
269
270
  }
270
271
  });
272
+ // Truncate a session at a message: drop it and everything after it
273
+ // (edit-and-resend). The metadata PUT deliberately ignores `messages`,
274
+ // so this is the only way a client can shorten the server-side history.
275
+ router.post('/sessions/:sessionId/messages/:messageId/truncate', async (req, res) => {
276
+ try {
277
+ const sessionId = req.params.sessionId;
278
+ const messageId = req.params.messageId;
279
+ const userId = req.user?.userId || 'default';
280
+ const updatedSession = await chatService.truncateMessagesFrom(sessionId, messageId, userId);
281
+ if (!updatedSession) {
282
+ res.status(404).json({
283
+ success: false,
284
+ error: 'Session or message not found',
285
+ });
286
+ return;
287
+ }
288
+ res.json({
289
+ success: true,
290
+ data: updatedSession,
291
+ });
292
+ }
293
+ catch (error) {
294
+ res.status(500).json({
295
+ success: false,
296
+ error: getErrorMessage(error, 'Failed to truncate session'),
297
+ });
298
+ }
299
+ });
271
300
  // Delete a chat session
272
301
  router.delete('/sessions/:sessionId', async (req, res) => {
273
302
  try {
@@ -410,7 +439,12 @@ router.post('/sessions/:sessionId/generate', async (req, res) => {
410
439
  (await isWebSearchAvailable()) &&
411
440
  (await userCanUseWebSearch(await userModel.getUserById(userId)))) {
412
441
  try {
413
- const results = await runWebSearch(message, undefined, signal);
442
+ const { results } = await runPlannedWebSearch({
443
+ message,
444
+ session,
445
+ userId,
446
+ signal,
447
+ });
414
448
  if (results.length > 0) {
415
449
  enhancedContent = buildWebSearchEnhancedContent(enhancedContent, results, message);
416
450
  hasRelevantContext = true;
@@ -429,7 +463,7 @@ router.post('/sessions/:sessionId/generate', async (req, res) => {
429
463
  session,
430
464
  userId,
431
465
  options,
432
- persistedMessages: await chatService.getMessagesForContext(sessionId, userId),
466
+ persistedMessages: await chatService.getMessagesForContext(sessionId, userId, undefined, signal),
433
467
  content: message,
434
468
  hasRelevantContext,
435
469
  enhancedContent,
@@ -857,7 +891,12 @@ router.post('/sessions/:sessionId/generate/stream', async (req, res) => {
857
891
  (await userCanUseWebSearch(await userModel.getUserById(userId)))) {
858
892
  await emitDurable({ type: 'search', status: 'searching' });
859
893
  try {
860
- const results = await runWebSearch(message, undefined, signal);
894
+ const { results } = await runPlannedWebSearch({
895
+ message,
896
+ session,
897
+ userId,
898
+ signal,
899
+ });
861
900
  if (results.length > 0) {
862
901
  searchEnhancedContent = buildWebSearchEnhancedContent(documentContext.enhancedContent, results, message);
863
902
  webSearchSources = results.map(({ title, url }) => ({
@@ -897,7 +936,7 @@ router.post('/sessions/:sessionId/generate/stream', async (req, res) => {
897
936
  session,
898
937
  userId,
899
938
  options,
900
- persistedMessages: await chatService.getMessagesForContext(sessionId, userId),
939
+ persistedMessages: await chatService.getMessagesForContext(sessionId, userId, undefined, signal),
901
940
  content: message,
902
941
  enhancedContent: searchEnhancedContent ?? documentContext.enhancedContent,
903
942
  hasRelevantContext: Boolean(searchEnhancedContent ?? documentContext.hasRelevantContext),
@@ -1423,8 +1462,61 @@ router.post('/sessions/:sessionId/messages/:messageId/branches', authenticate, c
1423
1462
  });
1424
1463
  }
1425
1464
  });
1426
- /** Context compaction settings: readable by any user, set by admins. */
1427
- router.get('/compaction-config', async (_req, res) => {
1465
+ /**
1466
+ * Undo one compaction: the summary knows exactly which messages it replaced,
1467
+ * so restoring is reactivating them and removing the summary.
1468
+ */
1469
+ router.post('/sessions/:sessionId/compaction/:messageId/restore', authenticate, chatRateLimiter, async (req, res) => {
1470
+ try {
1471
+ const sessionId = req.params.sessionId;
1472
+ const messageId = req.params.messageId;
1473
+ const userId = req.user?.userId || 'default';
1474
+ const session = await chatService.restoreCompaction(sessionId, userId, messageId);
1475
+ if (!session) {
1476
+ res.status(404).json({
1477
+ success: false,
1478
+ error: 'No restorable summary with that id in this session',
1479
+ });
1480
+ return;
1481
+ }
1482
+ res.json({ success: true, data: session });
1483
+ }
1484
+ catch (error) {
1485
+ logger.error('Restore compaction error:', error);
1486
+ res.status(500).json({
1487
+ success: false,
1488
+ error: getErrorMessage(error, 'Failed to restore compacted messages'),
1489
+ });
1490
+ }
1491
+ });
1492
+ /**
1493
+ * The rolling window every conversation runs with, for any signed-in user:
1494
+ * the context meter needs the real message count to mirror what is sent.
1495
+ * Deliberately narrow — none of the admin compaction configuration (prompt,
1496
+ * model, thresholds) leaves this endpoint.
1497
+ */
1498
+ router.get('/context-policy', async (_req, res) => {
1499
+ try {
1500
+ const compaction = await getCompactionConfig();
1501
+ res.json({
1502
+ success: true,
1503
+ data: {
1504
+ windowMessages: compaction.enabled
1505
+ ? Math.max(10, compaction.keepRecentMessages)
1506
+ : 10,
1507
+ },
1508
+ });
1509
+ }
1510
+ catch (error) {
1511
+ res.status(500).json({
1512
+ success: false,
1513
+ error: getErrorMessage(error, 'Failed to load context policy'),
1514
+ });
1515
+ }
1516
+ });
1517
+ /** Context compaction settings: admin-only in both directions — the custom
1518
+ * summarizer prompt is administrator configuration, not user data. */
1519
+ router.get('/compaction-config', requireAdmin, async (_req, res) => {
1428
1520
  try {
1429
1521
  res.json({ success: true, data: await getCompactionConfig() });
1430
1522
  }