libre-webui 0.25.0 → 0.26.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 (510) hide show
  1. package/README.md +32 -22
  2. package/backend/dist/index.d.ts.map +1 -1
  3. package/backend/dist/index.js +26 -4
  4. package/backend/dist/index.js.map +1 -1
  5. package/backend/dist/middleware/auth.d.ts +7 -0
  6. package/backend/dist/middleware/auth.d.ts.map +1 -1
  7. package/backend/dist/middleware/auth.js +26 -0
  8. package/backend/dist/middleware/auth.js.map +1 -1
  9. package/backend/dist/middleware/index.d.ts +20 -0
  10. package/backend/dist/middleware/index.d.ts.map +1 -1
  11. package/backend/dist/middleware/index.js +105 -2
  12. package/backend/dist/middleware/index.js.map +1 -1
  13. package/backend/dist/models/personaModel.d.ts.map +1 -1
  14. package/backend/dist/models/personaModel.js +2 -0
  15. package/backend/dist/models/personaModel.js.map +1 -1
  16. package/backend/dist/observability/otel.d.ts +40 -0
  17. package/backend/dist/observability/otel.d.ts.map +1 -0
  18. package/backend/dist/observability/otel.js +337 -0
  19. package/backend/dist/observability/otel.js.map +1 -0
  20. package/backend/dist/observability/requestContext.d.ts +14 -0
  21. package/backend/dist/observability/requestContext.d.ts.map +1 -0
  22. package/backend/dist/observability/requestContext.js +21 -0
  23. package/backend/dist/observability/requestContext.js.map +1 -0
  24. package/backend/dist/persistence/chatGenerationTypes.d.ts +9 -0
  25. package/backend/dist/persistence/chatGenerationTypes.d.ts.map +1 -1
  26. package/backend/dist/persistence/extensionTypes.d.ts +13 -0
  27. package/backend/dist/persistence/extensionTypes.d.ts.map +1 -1
  28. package/backend/dist/persistence/extensionTypes.js.map +1 -1
  29. package/backend/dist/persistence/index.d.ts +1 -1
  30. package/backend/dist/persistence/index.d.ts.map +1 -1
  31. package/backend/dist/persistence/index.js.map +1 -1
  32. package/backend/dist/persistence/postgresAgentFoundationMigration.d.ts +9 -0
  33. package/backend/dist/persistence/postgresAgentFoundationMigration.d.ts.map +1 -0
  34. package/backend/dist/persistence/postgresAgentFoundationMigration.js +164 -0
  35. package/backend/dist/persistence/postgresAgentFoundationMigration.js.map +1 -0
  36. package/backend/dist/persistence/postgresExtensionRepositories.d.ts.map +1 -1
  37. package/backend/dist/persistence/postgresExtensionRepositories.js +46 -2
  38. package/backend/dist/persistence/postgresExtensionRepositories.js.map +1 -1
  39. package/backend/dist/persistence/postgresMediaEnterpriseOpsMigration.d.ts +10 -0
  40. package/backend/dist/persistence/postgresMediaEnterpriseOpsMigration.d.ts.map +1 -0
  41. package/backend/dist/persistence/postgresMediaEnterpriseOpsMigration.js +148 -0
  42. package/backend/dist/persistence/postgresMediaEnterpriseOpsMigration.js.map +1 -0
  43. package/backend/dist/persistence/postgresMigrationRegistry.d.ts.map +1 -1
  44. package/backend/dist/persistence/postgresMigrationRegistry.js +10 -0
  45. package/backend/dist/persistence/postgresMigrationRegistry.js.map +1 -1
  46. package/backend/dist/persistence/postgresNotesV2Migration.d.ts +4 -0
  47. package/backend/dist/persistence/postgresNotesV2Migration.d.ts.map +1 -0
  48. package/backend/dist/persistence/postgresNotesV2Migration.js +57 -0
  49. package/backend/dist/persistence/postgresNotesV2Migration.js.map +1 -0
  50. package/backend/dist/persistence/postgresResourceRepositories.d.ts.map +1 -1
  51. package/backend/dist/persistence/postgresResourceRepositories.js +1646 -11
  52. package/backend/dist/persistence/postgresResourceRepositories.js.map +1 -1
  53. package/backend/dist/persistence/postgresSkillFilesMigration.d.ts +9 -0
  54. package/backend/dist/persistence/postgresSkillFilesMigration.d.ts.map +1 -0
  55. package/backend/dist/persistence/postgresSkillFilesMigration.js +50 -0
  56. package/backend/dist/persistence/postgresSkillFilesMigration.js.map +1 -0
  57. package/backend/dist/persistence/postgresTeamCollaborationMigration.d.ts +10 -0
  58. package/backend/dist/persistence/postgresTeamCollaborationMigration.d.ts.map +1 -0
  59. package/backend/dist/persistence/postgresTeamCollaborationMigration.js +161 -0
  60. package/backend/dist/persistence/postgresTeamCollaborationMigration.js.map +1 -0
  61. package/backend/dist/persistence/resourceTypes.d.ts +541 -3
  62. package/backend/dist/persistence/resourceTypes.d.ts.map +1 -1
  63. package/backend/dist/persistence/resourceTypes.js.map +1 -1
  64. package/backend/dist/persistence/sqliteExtensionRepositories.d.ts.map +1 -1
  65. package/backend/dist/persistence/sqliteExtensionRepositories.js +26 -4
  66. package/backend/dist/persistence/sqliteExtensionRepositories.js.map +1 -1
  67. package/backend/dist/persistence/sqliteMigrations.d.ts +9 -0
  68. package/backend/dist/persistence/sqliteMigrations.d.ts.map +1 -1
  69. package/backend/dist/persistence/sqliteMigrations.js +1196 -29
  70. package/backend/dist/persistence/sqliteMigrations.js.map +1 -1
  71. package/backend/dist/persistence/sqliteResourceRepositories.d.ts.map +1 -1
  72. package/backend/dist/persistence/sqliteResourceRepositories.js +1523 -134
  73. package/backend/dist/persistence/sqliteResourceRepositories.js.map +1 -1
  74. package/backend/dist/persistence/sqliteToPostgresMigration.d.ts.map +1 -1
  75. package/backend/dist/persistence/sqliteToPostgresMigration.js +257 -3
  76. package/backend/dist/persistence/sqliteToPostgresMigration.js.map +1 -1
  77. package/backend/dist/platform/jobs/domainJobContracts.d.ts +8 -0
  78. package/backend/dist/platform/jobs/domainJobContracts.d.ts.map +1 -1
  79. package/backend/dist/platform/jobs/domainJobContracts.js +8 -0
  80. package/backend/dist/platform/jobs/domainJobContracts.js.map +1 -1
  81. package/backend/dist/platform/jobs/domainJobHandlers.d.ts.map +1 -1
  82. package/backend/dist/platform/jobs/domainJobHandlers.js +310 -3
  83. package/backend/dist/platform/jobs/domainJobHandlers.js.map +1 -1
  84. package/backend/dist/platform/jobs/embeddedDurableJobWorker.d.ts.map +1 -1
  85. package/backend/dist/platform/jobs/embeddedDurableJobWorker.js +4 -2
  86. package/backend/dist/platform/jobs/embeddedDurableJobWorker.js.map +1 -1
  87. package/backend/dist/platform/jobs/postgresDurableJobWorker.d.ts.map +1 -1
  88. package/backend/dist/platform/jobs/postgresDurableJobWorker.js +4 -2
  89. package/backend/dist/platform/jobs/postgresDurableJobWorker.js.map +1 -1
  90. package/backend/dist/platform/storage/pgVectorStore.d.ts +2 -1
  91. package/backend/dist/platform/storage/pgVectorStore.d.ts.map +1 -1
  92. package/backend/dist/platform/storage/pgVectorStore.js +51 -0
  93. package/backend/dist/platform/storage/pgVectorStore.js.map +1 -1
  94. package/backend/dist/platform/storage/platformDomainRepositories.d.ts +8 -2
  95. package/backend/dist/platform/storage/platformDomainRepositories.d.ts.map +1 -1
  96. package/backend/dist/platform/storage/postgresPlatformDomainRepositories.d.ts.map +1 -1
  97. package/backend/dist/platform/storage/postgresPlatformDomainRepositories.js +25 -4
  98. package/backend/dist/platform/storage/postgresPlatformDomainRepositories.js.map +1 -1
  99. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.d.ts +2 -1
  100. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.d.ts.map +1 -1
  101. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.js +35 -0
  102. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.js.map +1 -1
  103. package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.d.ts.map +1 -1
  104. package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.js +26 -3
  105. package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.js.map +1 -1
  106. package/backend/dist/platform/storage/vectorStore.d.ts +14 -0
  107. package/backend/dist/platform/storage/vectorStore.d.ts.map +1 -1
  108. package/backend/dist/platform/storage/vectorStore.js.map +1 -1
  109. package/backend/dist/routes/access.d.ts.map +1 -1
  110. package/backend/dist/routes/access.js +84 -3
  111. package/backend/dist/routes/access.js.map +1 -1
  112. package/backend/dist/routes/calendar.d.ts.map +1 -1
  113. package/backend/dist/routes/calendar.js +125 -22
  114. package/backend/dist/routes/calendar.js.map +1 -1
  115. package/backend/dist/routes/channels.d.ts +3 -0
  116. package/backend/dist/routes/channels.d.ts.map +1 -0
  117. package/backend/dist/routes/channels.js +447 -0
  118. package/backend/dist/routes/channels.js.map +1 -0
  119. package/backend/dist/routes/chat.d.ts.map +1 -1
  120. package/backend/dist/routes/chat.js +195 -7
  121. package/backend/dist/routes/chat.js.map +1 -1
  122. package/backend/dist/routes/costs.d.ts +3 -0
  123. package/backend/dist/routes/costs.d.ts.map +1 -0
  124. package/backend/dist/routes/costs.js +191 -0
  125. package/backend/dist/routes/costs.js.map +1 -0
  126. package/backend/dist/routes/documents.d.ts.map +1 -1
  127. package/backend/dist/routes/documents.js +64 -21
  128. package/backend/dist/routes/documents.js.map +1 -1
  129. package/backend/dist/routes/evaluations.d.ts +3 -0
  130. package/backend/dist/routes/evaluations.d.ts.map +1 -0
  131. package/backend/dist/routes/evaluations.js +352 -0
  132. package/backend/dist/routes/evaluations.js.map +1 -0
  133. package/backend/dist/routes/imageGen.d.ts.map +1 -1
  134. package/backend/dist/routes/imageGen.js +2 -1
  135. package/backend/dist/routes/imageGen.js.map +1 -1
  136. package/backend/dist/routes/media.d.ts.map +1 -1
  137. package/backend/dist/routes/media.js +228 -7
  138. package/backend/dist/routes/media.js.map +1 -1
  139. package/backend/dist/routes/notes.d.ts.map +1 -1
  140. package/backend/dist/routes/notes.js +149 -36
  141. package/backend/dist/routes/notes.js.map +1 -1
  142. package/backend/dist/routes/notifications.d.ts +3 -0
  143. package/backend/dist/routes/notifications.d.ts.map +1 -0
  144. package/backend/dist/routes/notifications.js +213 -0
  145. package/backend/dist/routes/notifications.js.map +1 -0
  146. package/backend/dist/routes/openaiCompat.d.ts +3 -0
  147. package/backend/dist/routes/openaiCompat.d.ts.map +1 -0
  148. package/backend/dist/routes/openaiCompat.js +314 -0
  149. package/backend/dist/routes/openaiCompat.js.map +1 -0
  150. package/backend/dist/routes/personas.js +1 -1
  151. package/backend/dist/routes/personas.js.map +1 -1
  152. package/backend/dist/routes/prompts.d.ts +3 -0
  153. package/backend/dist/routes/prompts.d.ts.map +1 -0
  154. package/backend/dist/routes/prompts.js +173 -0
  155. package/backend/dist/routes/prompts.js.map +1 -0
  156. package/backend/dist/routes/search.d.ts.map +1 -1
  157. package/backend/dist/routes/search.js +29 -1
  158. package/backend/dist/routes/search.js.map +1 -1
  159. package/backend/dist/routes/skills.d.ts +3 -0
  160. package/backend/dist/routes/skills.d.ts.map +1 -0
  161. package/backend/dist/routes/skills.js +246 -0
  162. package/backend/dist/routes/skills.js.map +1 -0
  163. package/backend/dist/routes/stt.d.ts.map +1 -1
  164. package/backend/dist/routes/stt.js +2 -1
  165. package/backend/dist/routes/stt.js.map +1 -1
  166. package/backend/dist/routes/tools.d.ts +3 -0
  167. package/backend/dist/routes/tools.d.ts.map +1 -0
  168. package/backend/dist/routes/tools.js +359 -0
  169. package/backend/dist/routes/tools.js.map +1 -0
  170. package/backend/dist/routes/tts.d.ts.map +1 -1
  171. package/backend/dist/routes/tts.js +63 -8
  172. package/backend/dist/routes/tts.js.map +1 -1
  173. package/backend/dist/routes/voiceAccess.d.ts +3 -0
  174. package/backend/dist/routes/voiceAccess.d.ts.map +1 -0
  175. package/backend/dist/routes/voiceAccess.js +102 -0
  176. package/backend/dist/routes/voiceAccess.js.map +1 -0
  177. package/backend/dist/services/apiTokenService.d.ts +2 -2
  178. package/backend/dist/services/apiTokenService.d.ts.map +1 -1
  179. package/backend/dist/services/apiTokenService.js +7 -0
  180. package/backend/dist/services/apiTokenService.js.map +1 -1
  181. package/backend/dist/services/authorizationService.d.ts +8 -4
  182. package/backend/dist/services/authorizationService.d.ts.map +1 -1
  183. package/backend/dist/services/authorizationService.js +31 -1
  184. package/backend/dist/services/authorizationService.js.map +1 -1
  185. package/backend/dist/services/automationSchedulerService.d.ts.map +1 -1
  186. package/backend/dist/services/automationSchedulerService.js +33 -3
  187. package/backend/dist/services/automationSchedulerService.js.map +1 -1
  188. package/backend/dist/services/automationService.d.ts +4 -1
  189. package/backend/dist/services/automationService.d.ts.map +1 -1
  190. package/backend/dist/services/automationService.js +25 -2
  191. package/backend/dist/services/automationService.js.map +1 -1
  192. package/backend/dist/services/builtinToolsService.d.ts +20 -0
  193. package/backend/dist/services/builtinToolsService.d.ts.map +1 -0
  194. package/backend/dist/services/builtinToolsService.js +661 -0
  195. package/backend/dist/services/builtinToolsService.js.map +1 -0
  196. package/backend/dist/services/calendarService.d.ts +74 -0
  197. package/backend/dist/services/calendarService.d.ts.map +1 -0
  198. package/backend/dist/services/calendarService.js +339 -0
  199. package/backend/dist/services/calendarService.js.map +1 -0
  200. package/backend/dist/services/channelService.d.ts +195 -0
  201. package/backend/dist/services/channelService.d.ts.map +1 -0
  202. package/backend/dist/services/channelService.js +1057 -0
  203. package/backend/dist/services/channelService.js.map +1 -0
  204. package/backend/dist/services/chatRequestService.d.ts.map +1 -1
  205. package/backend/dist/services/chatRequestService.js +29 -1
  206. package/backend/dist/services/chatRequestService.js.map +1 -1
  207. package/backend/dist/services/chatService.d.ts +61 -1
  208. package/backend/dist/services/chatService.d.ts.map +1 -1
  209. package/backend/dist/services/chatService.js +218 -1
  210. package/backend/dist/services/chatService.js.map +1 -1
  211. package/backend/dist/services/chatToolRuntimeService.d.ts +55 -0
  212. package/backend/dist/services/chatToolRuntimeService.d.ts.map +1 -0
  213. package/backend/dist/services/chatToolRuntimeService.js +389 -0
  214. package/backend/dist/services/chatToolRuntimeService.js.map +1 -0
  215. package/backend/dist/services/costGovernanceService.d.ts +102 -0
  216. package/backend/dist/services/costGovernanceService.d.ts.map +1 -0
  217. package/backend/dist/services/costGovernanceService.js +463 -0
  218. package/backend/dist/services/costGovernanceService.js.map +1 -0
  219. package/backend/dist/services/dataArchiveService.d.ts +6 -0
  220. package/backend/dist/services/dataArchiveService.d.ts.map +1 -1
  221. package/backend/dist/services/dataArchiveService.js +23 -4
  222. package/backend/dist/services/dataArchiveService.js.map +1 -1
  223. package/backend/dist/services/documentService.d.ts +58 -1
  224. package/backend/dist/services/documentService.d.ts.map +1 -1
  225. package/backend/dist/services/documentService.js +289 -84
  226. package/backend/dist/services/documentService.js.map +1 -1
  227. package/backend/dist/services/durableChatGenerationService.d.ts +9 -0
  228. package/backend/dist/services/durableChatGenerationService.d.ts.map +1 -1
  229. package/backend/dist/services/durableChatGenerationService.js +152 -11
  230. package/backend/dist/services/durableChatGenerationService.js.map +1 -1
  231. package/backend/dist/services/evaluationService.d.ts +111 -0
  232. package/backend/dist/services/evaluationService.d.ts.map +1 -0
  233. package/backend/dist/services/evaluationService.js +403 -0
  234. package/backend/dist/services/evaluationService.js.map +1 -0
  235. package/backend/dist/services/galleryService.d.ts +6 -0
  236. package/backend/dist/services/galleryService.d.ts.map +1 -1
  237. package/backend/dist/services/galleryService.js +26 -0
  238. package/backend/dist/services/galleryService.js.map +1 -1
  239. package/backend/dist/services/mcpClientService.d.ts +29 -0
  240. package/backend/dist/services/mcpClientService.d.ts.map +1 -0
  241. package/backend/dist/services/mcpClientService.js +189 -0
  242. package/backend/dist/services/mcpClientService.js.map +1 -0
  243. package/backend/dist/services/noteService.d.ts +71 -0
  244. package/backend/dist/services/noteService.d.ts.map +1 -0
  245. package/backend/dist/services/noteService.js +397 -0
  246. package/backend/dist/services/noteService.js.map +1 -0
  247. package/backend/dist/services/notificationService.d.ts +55 -0
  248. package/backend/dist/services/notificationService.d.ts.map +1 -0
  249. package/backend/dist/services/notificationService.js +308 -0
  250. package/backend/dist/services/notificationService.js.map +1 -0
  251. package/backend/dist/services/openApiToolService.d.ts +38 -0
  252. package/backend/dist/services/openApiToolService.d.ts.map +1 -0
  253. package/backend/dist/services/openApiToolService.js +254 -0
  254. package/backend/dist/services/openApiToolService.js.map +1 -0
  255. package/backend/dist/services/personaService.d.ts +18 -2
  256. package/backend/dist/services/personaService.d.ts.map +1 -1
  257. package/backend/dist/services/personaService.js +113 -4
  258. package/backend/dist/services/personaService.js.map +1 -1
  259. package/backend/dist/services/pluginImageGenerationService.d.ts +19 -0
  260. package/backend/dist/services/pluginImageGenerationService.d.ts.map +1 -1
  261. package/backend/dist/services/pluginImageGenerationService.js +121 -0
  262. package/backend/dist/services/pluginImageGenerationService.js.map +1 -1
  263. package/backend/dist/services/pluginService.d.ts +7 -0
  264. package/backend/dist/services/pluginService.d.ts.map +1 -1
  265. package/backend/dist/services/pluginService.js +8 -2
  266. package/backend/dist/services/pluginService.js.map +1 -1
  267. package/backend/dist/services/promptService.d.ts +74 -0
  268. package/backend/dist/services/promptService.d.ts.map +1 -0
  269. package/backend/dist/services/promptService.js +513 -0
  270. package/backend/dist/services/promptService.js.map +1 -0
  271. package/backend/dist/services/resourceGrantService.d.ts.map +1 -1
  272. package/backend/dist/services/resourceGrantService.js +62 -17
  273. package/backend/dist/services/resourceGrantService.js.map +1 -1
  274. package/backend/dist/services/searchService.d.ts +29 -0
  275. package/backend/dist/services/searchService.d.ts.map +1 -0
  276. package/backend/dist/services/searchService.js +0 -0
  277. package/backend/dist/services/searchService.js.map +1 -0
  278. package/backend/dist/services/securityAuditService.d.ts.map +1 -1
  279. package/backend/dist/services/securityAuditService.js +2 -1
  280. package/backend/dist/services/securityAuditService.js.map +1 -1
  281. package/backend/dist/services/sharedResourceAccess.d.ts +25 -0
  282. package/backend/dist/services/sharedResourceAccess.d.ts.map +1 -0
  283. package/backend/dist/services/sharedResourceAccess.js +59 -0
  284. package/backend/dist/services/sharedResourceAccess.js.map +1 -0
  285. package/backend/dist/services/skillService.d.ts +137 -0
  286. package/backend/dist/services/skillService.d.ts.map +1 -0
  287. package/backend/dist/services/skillService.js +852 -0
  288. package/backend/dist/services/skillService.js.map +1 -0
  289. package/backend/dist/services/toolAccessService.d.ts +9 -0
  290. package/backend/dist/services/toolAccessService.d.ts.map +1 -0
  291. package/backend/dist/services/toolAccessService.js +60 -0
  292. package/backend/dist/services/toolAccessService.js.map +1 -0
  293. package/backend/dist/services/toolApprovalService.d.ts +34 -0
  294. package/backend/dist/services/toolApprovalService.d.ts.map +1 -0
  295. package/backend/dist/services/toolApprovalService.js +188 -0
  296. package/backend/dist/services/toolApprovalService.js.map +1 -0
  297. package/backend/dist/services/toolGatewayService.d.ts +53 -0
  298. package/backend/dist/services/toolGatewayService.d.ts.map +1 -0
  299. package/backend/dist/services/toolGatewayService.js +249 -0
  300. package/backend/dist/services/toolGatewayService.js.map +1 -0
  301. package/backend/dist/services/toolServerService.d.ts +57 -0
  302. package/backend/dist/services/toolServerService.d.ts.map +1 -0
  303. package/backend/dist/services/toolServerService.js +521 -0
  304. package/backend/dist/services/toolServerService.js.map +1 -0
  305. package/backend/dist/services/voiceAccessService.d.ts +10 -0
  306. package/backend/dist/services/voiceAccessService.d.ts.map +1 -0
  307. package/backend/dist/services/voiceAccessService.js +74 -0
  308. package/backend/dist/services/voiceAccessService.js.map +1 -0
  309. package/backend/dist/services/voiceProfileService.d.ts +20 -0
  310. package/backend/dist/services/voiceProfileService.d.ts.map +1 -1
  311. package/backend/dist/services/voiceProfileService.js +60 -0
  312. package/backend/dist/services/voiceProfileService.js.map +1 -1
  313. package/backend/dist/storage.d.ts +28 -1
  314. package/backend/dist/storage.d.ts.map +1 -1
  315. package/backend/dist/storage.js +100 -1
  316. package/backend/dist/storage.js.map +1 -1
  317. package/backend/dist/storageMappers.d.ts +2 -2
  318. package/backend/dist/storageMappers.d.ts.map +1 -1
  319. package/backend/dist/storageMappers.js.map +1 -1
  320. package/backend/dist/types/index.d.ts +197 -2
  321. package/backend/dist/types/index.d.ts.map +1 -1
  322. package/backend/dist/types/index.js.map +1 -1
  323. package/backend/dist/types/tools.d.ts +137 -0
  324. package/backend/dist/types/tools.d.ts.map +1 -0
  325. package/backend/dist/types/tools.js +25 -0
  326. package/backend/dist/types/tools.js.map +1 -0
  327. package/backend/dist/utils/chatDocumentContext.d.ts +26 -1
  328. package/backend/dist/utils/chatDocumentContext.d.ts.map +1 -1
  329. package/backend/dist/utils/chatDocumentContext.js +87 -18
  330. package/backend/dist/utils/chatDocumentContext.js.map +1 -1
  331. package/backend/dist/utils/documentExtraction.d.ts +69 -0
  332. package/backend/dist/utils/documentExtraction.d.ts.map +1 -0
  333. package/backend/dist/utils/documentExtraction.js +545 -0
  334. package/backend/dist/utils/documentExtraction.js.map +1 -0
  335. package/backend/dist/utils/hybridRetrieval.d.ts +34 -0
  336. package/backend/dist/utils/hybridRetrieval.d.ts.map +1 -0
  337. package/backend/dist/utils/hybridRetrieval.js +119 -0
  338. package/backend/dist/utils/hybridRetrieval.js.map +1 -0
  339. package/backend/dist/utils/icsCalendar.d.ts +27 -0
  340. package/backend/dist/utils/icsCalendar.d.ts.map +1 -0
  341. package/backend/dist/utils/icsCalendar.js +313 -0
  342. package/backend/dist/utils/icsCalendar.js.map +1 -0
  343. package/backend/dist/utils/imageEditUpload.d.ts +29 -0
  344. package/backend/dist/utils/imageEditUpload.d.ts.map +1 -0
  345. package/backend/dist/utils/imageEditUpload.js +82 -0
  346. package/backend/dist/utils/imageEditUpload.js.map +1 -0
  347. package/backend/dist/utils/logRedaction.d.ts +2 -0
  348. package/backend/dist/utils/logRedaction.d.ts.map +1 -0
  349. package/backend/dist/utils/logRedaction.js +66 -0
  350. package/backend/dist/utils/logRedaction.js.map +1 -0
  351. package/backend/dist/utils/logger.d.ts +3 -0
  352. package/backend/dist/utils/logger.d.ts.map +1 -1
  353. package/backend/dist/utils/logger.js +63 -20
  354. package/backend/dist/utils/logger.js.map +1 -1
  355. package/backend/dist/utils/pluginChatAdapter.d.ts +5 -1
  356. package/backend/dist/utils/pluginChatAdapter.d.ts.map +1 -1
  357. package/backend/dist/utils/pluginChatAdapter.js +83 -0
  358. package/backend/dist/utils/pluginChatAdapter.js.map +1 -1
  359. package/backend/dist/utils/pluginDefinitionTrust.js +1 -1
  360. package/backend/dist/utils/resourceLimits.d.ts +61 -2
  361. package/backend/dist/utils/resourceLimits.d.ts.map +1 -1
  362. package/backend/dist/utils/resourceLimits.js +59 -0
  363. package/backend/dist/utils/resourceLimits.js.map +1 -1
  364. package/backend/dist/utils/toolEgress.d.ts +22 -0
  365. package/backend/dist/utils/toolEgress.d.ts.map +1 -0
  366. package/backend/dist/utils/toolEgress.js +157 -0
  367. package/backend/dist/utils/toolEgress.js.map +1 -0
  368. package/backend/dist/utils/websocketMessages.d.ts +6 -0
  369. package/backend/dist/utils/websocketMessages.d.ts.map +1 -1
  370. package/backend/dist/utils/websocketMessages.js +8 -0
  371. package/backend/dist/utils/websocketMessages.js.map +1 -1
  372. package/backend/dist/utils/zipArchive.d.ts +32 -0
  373. package/backend/dist/utils/zipArchive.d.ts.map +1 -0
  374. package/backend/dist/utils/zipArchive.js +171 -0
  375. package/backend/dist/utils/zipArchive.js.map +1 -0
  376. package/backend/dist/websocketServer.d.ts.map +1 -1
  377. package/backend/dist/websocketServer.js +142 -35
  378. package/backend/dist/websocketServer.js.map +1 -1
  379. package/backend/package.json +1 -1
  380. package/frontend/dist/artifact-runtime/.build-fingerprint +1 -1
  381. package/frontend/dist/assets/index-BPsH9FII.js +80 -0
  382. package/frontend/dist/assets/index-BtXF0Oxa.css +1 -0
  383. package/frontend/dist/index.html +18 -18
  384. package/frontend/dist/js/{ArtifactContainer-CpUi6ekF.js → ArtifactContainer-CIoFoMv8.js} +1 -1
  385. package/frontend/dist/js/{ArtifactDemoPage-BeNs3gHp.js → ArtifactDemoPage-D0JEIAbZ.js} +1 -1
  386. package/frontend/dist/js/{ArtifactSandboxFrame-qsj_Y3LV.js → ArtifactSandboxFrame-REazyIrx.js} +2 -2
  387. package/frontend/dist/js/ArtifactSlideOutPanel-A-i94ghF.js +1 -0
  388. package/frontend/dist/js/AutomationsPage-Dow_lbIf.js +1 -0
  389. package/frontend/dist/js/{Button-BPf_pOxH.js → Button-C0r3J9G_.js} +1 -1
  390. package/frontend/dist/js/CalendarPage-DLjasZOd.js +1 -0
  391. package/frontend/dist/js/ChannelsPage-C8hvTD7J.js +1 -0
  392. package/frontend/dist/js/ChatPage-BAgBHUES.js +16 -0
  393. package/frontend/dist/js/EvaluationsPage-Bm8FRoeX.js +2 -0
  394. package/frontend/dist/js/{FirstTimeSetup-Df0YJni3.js → FirstTimeSetup-BxJapNul.js} +1 -1
  395. package/frontend/dist/js/GalleryPage-DHz9DkK7.js +1 -0
  396. package/frontend/dist/js/HomePage-BqNy-FWX.js +1 -0
  397. package/frontend/dist/js/LibreClawPage-BDzDILjY.js +1 -0
  398. package/frontend/dist/js/{ModelManagerModals-DcLGYN3l.js → ModelManagerModals-DX9eT5r2.js} +1 -1
  399. package/frontend/dist/js/ModelSelector-ol8mK2ja.js +1 -0
  400. package/frontend/dist/js/NotesPage-DzHEx2O4.js +3 -0
  401. package/frontend/dist/js/{OptimizedSyntaxHighlighter-Dz7D1LPB.js → OptimizedSyntaxHighlighter-CZNPwN0i.js} +1 -1
  402. package/frontend/dist/js/PersonasPage-M_WKH2a9.js +13 -0
  403. package/frontend/dist/js/PluginUsagePage-Ctoka6JZ.js +1 -0
  404. package/frontend/dist/js/RichMessageContent-BqgafH4p.js +3 -0
  405. package/frontend/dist/js/SettingsModal-R9EDadAV.js +19 -0
  406. package/frontend/dist/js/{SettingsToggle-woG_WebF.js → SettingsToggle-BIVyfGjl.js} +1 -1
  407. package/frontend/dist/js/SystemPage-aOkrSuY7.js +1 -0
  408. package/frontend/dist/js/UserManagementPage-BOuaX3ay.js +1 -0
  409. package/frontend/dist/js/WorkPage-DxZISerQ.js +6 -0
  410. package/frontend/dist/js/api-6bSMtSNh.js +14 -0
  411. package/frontend/dist/js/{appStore-0JnCUlEq.js → appStore-CdObhQBb.js} +2 -2
  412. package/frontend/dist/js/ar-DT_VLBO3.js +2 -0
  413. package/frontend/dist/js/{authStore-DbMdvNgJ.js → authStore-DbqqrfZv.js} +3 -3
  414. package/frontend/dist/js/{babel-BsYoxD-7.js → babel-DSqqk5kt.js} +1 -1
  415. package/frontend/dist/js/bn-BrvEqhGA.js +2 -0
  416. package/frontend/dist/js/chatStore-DbZPJNsu.js +3 -0
  417. package/frontend/dist/js/{config-DSOYQ9a3.js → config-8KdgjtEx.js} +1 -1
  418. package/frontend/dist/js/cs-BthnM_YI.js +2 -0
  419. package/frontend/dist/js/da-DCth-6_j.js +2 -0
  420. package/frontend/dist/js/de-BBWt0gJ_.js +2 -0
  421. package/frontend/dist/js/es-DuslOPqM.js +2 -0
  422. package/frontend/dist/js/{estree-BcnBcOAx.js → estree-BU5-LV_1.js} +3 -3
  423. package/frontend/dist/js/evaluationsApi-CwZJsgeG.js +1 -0
  424. package/frontend/dist/js/fr-DZAeg46S.js +2 -0
  425. package/frontend/dist/js/hi-ijDSiY77.js +2 -0
  426. package/frontend/dist/js/{html-DkY55Jys.js → html-DeoAy2bo.js} +2 -2
  427. package/frontend/dist/js/id-BS6qS0oq.js +2 -0
  428. package/frontend/dist/js/is-CO2PMDte.js +2 -0
  429. package/frontend/dist/js/it-ChhxT4hs.js +2 -0
  430. package/frontend/dist/js/ja-cyeqUeJN.js +2 -0
  431. package/frontend/dist/js/ko-CfqHTLXj.js +2 -0
  432. package/frontend/dist/js/{markdown-IZSI9DXw.js → markdown-DOLsYkBY.js} +3 -3
  433. package/frontend/dist/js/markdown-core-BK7WVOb7.js +32 -0
  434. package/frontend/dist/js/{markdown-math-DK846gXU.js → markdown-math-e1ByS3KY.js} +5 -5
  435. package/frontend/dist/js/{messageCodeStyles-BFNR9Wv6.js → messageCodeStyles-DhVgizF-.js} +2 -2
  436. package/frontend/dist/js/ms-DZ5pACe1.js +2 -0
  437. package/frontend/dist/js/nl-BsRsnOVp.js +2 -0
  438. package/frontend/dist/js/pl-DXSTAW4M.js +2 -0
  439. package/frontend/dist/js/{postcss-BJ0okz7n.js → postcss-Bg15EcFE.js} +1 -1
  440. package/frontend/dist/js/pt-DmDx0JxB.js +2 -0
  441. package/frontend/dist/js/{react-vendor-C5yShNlM.js → react-vendor-BG_HPV6X.js} +1 -1
  442. package/frontend/dist/js/router-vendor-CbexJIsv.js +1 -0
  443. package/frontend/dist/js/ru-d2g8EWfY.js +2 -0
  444. package/frontend/dist/js/{standalone-Csw3jfJ_.js → standalone-BO5pzI8_.js} +3 -3
  445. package/frontend/dist/js/sv-CIhYUTVQ.js +2 -0
  446. package/frontend/dist/js/{syntax-highlight-3EEX6CGL.js → syntax-highlight-DcR9Vhb7.js} +3 -3
  447. package/frontend/dist/js/th-B3wSxanB.js +2 -0
  448. package/frontend/dist/js/tr-R4UGgPOY.js +2 -0
  449. package/frontend/dist/js/{typescript-CnF4UVx7.js → typescript-6GJ8APmn.js} +1 -1
  450. package/frontend/dist/js/ui-CV0LYmkM.js +2 -0
  451. package/frontend/dist/js/ui-vendor-CWjIBW-z.js +177 -0
  452. package/frontend/dist/js/uk-BzUF4Vkt.js +2 -0
  453. package/frontend/dist/js/{useTranslation-Bho4HCyg.js → useTranslation-5b5UhAc9.js} +1 -1
  454. package/frontend/dist/js/{utils-D9W1Lnkn.js → utils-BGrdX9ie.js} +1 -1
  455. package/frontend/dist/js/{utils-vendor-DWh39_3x.js → utils-vendor-9bQ62WeD.js} +1 -1
  456. package/frontend/dist/js/vi-CKc6Fg4h.js +2 -0
  457. package/frontend/dist/js/{workApi-C3uy9EDT.js → workApi-BruWTlHM.js} +3 -3
  458. package/frontend/dist/js/{yaml-DzT5hBSH.js → yaml-DUeWcOOH.js} +2 -2
  459. package/frontend/dist/js/zh-CXL-kDe8.js +2 -0
  460. package/package.json +2 -2
  461. package/plugins/openai.json +52 -9
  462. package/frontend/dist/assets/index-6o-Hc2wS.js +0 -41
  463. package/frontend/dist/assets/index-D-pM957b.css +0 -1
  464. package/frontend/dist/js/ArtifactSlideOutPanel-CSFRluWA.js +0 -1
  465. package/frontend/dist/js/AutomationsPage-KUeWoZNv.js +0 -1
  466. package/frontend/dist/js/CalendarPage-D9I972w8.js +0 -1
  467. package/frontend/dist/js/ChatPage-CmBx4oAL.js +0 -16
  468. package/frontend/dist/js/GalleryPage-bLCrdJN_.js +0 -1
  469. package/frontend/dist/js/HomePage-Uc6PEg-U.js +0 -1
  470. package/frontend/dist/js/LibreClawPage-CG63DHg1.js +0 -1
  471. package/frontend/dist/js/ModelSelector-yxJC64rM.js +0 -1
  472. package/frontend/dist/js/ModelsPage-1R_TxgmJ.js +0 -2
  473. package/frontend/dist/js/NotesPage-L65j5HoW.js +0 -1
  474. package/frontend/dist/js/PersonasPage-CKeBpHIH.js +0 -13
  475. package/frontend/dist/js/PluginUsagePage-BX1SKMbW.js +0 -1
  476. package/frontend/dist/js/RichMessageContent-DiySVYGB.js +0 -3
  477. package/frontend/dist/js/SettingsModal-68SG1uZ2.js +0 -1
  478. package/frontend/dist/js/SystemPage-CytpZiQV.js +0 -1
  479. package/frontend/dist/js/UserManagementPage-BzJBvZiS.js +0 -1
  480. package/frontend/dist/js/WorkPage-C8gr0QJe.js +0 -6
  481. package/frontend/dist/js/api-BDtqBtqw.js +0 -14
  482. package/frontend/dist/js/ar-CSpi37Gr.js +0 -2
  483. package/frontend/dist/js/bn-CyH1BeDD.js +0 -2
  484. package/frontend/dist/js/chatStore-CHQ2uINS.js +0 -3
  485. package/frontend/dist/js/cs-Cv2NfRK5.js +0 -2
  486. package/frontend/dist/js/da-D6D6IvPK.js +0 -2
  487. package/frontend/dist/js/de-BNoJJTrT.js +0 -2
  488. package/frontend/dist/js/es-kzo9C7q_.js +0 -2
  489. package/frontend/dist/js/fr-CjHYvJRp.js +0 -2
  490. package/frontend/dist/js/hi-BU98AdCQ.js +0 -2
  491. package/frontend/dist/js/id-CpdedejX.js +0 -2
  492. package/frontend/dist/js/is-Cp5dDWBH.js +0 -2
  493. package/frontend/dist/js/it-CuH_cK4U.js +0 -2
  494. package/frontend/dist/js/ja-BWXXXiHn.js +0 -2
  495. package/frontend/dist/js/ko-DQgv2M1t.js +0 -2
  496. package/frontend/dist/js/markdown-core-BmPPZiuD.js +0 -31
  497. package/frontend/dist/js/ms-DCcRoIcX.js +0 -2
  498. package/frontend/dist/js/nl-CbKR1_EU.js +0 -2
  499. package/frontend/dist/js/pl-CRyJZccn.js +0 -2
  500. package/frontend/dist/js/pt-BXYmHFGH.js +0 -2
  501. package/frontend/dist/js/router-vendor-DGFGM3EP.js +0 -1
  502. package/frontend/dist/js/ru-M9SyZG4b.js +0 -2
  503. package/frontend/dist/js/sv-DJ486LTU.js +0 -2
  504. package/frontend/dist/js/th-Ch7gKtZv.js +0 -2
  505. package/frontend/dist/js/tr-ClehWhSC.js +0 -2
  506. package/frontend/dist/js/ui-CC-K19wO.js +0 -2
  507. package/frontend/dist/js/ui-vendor-B1UgO3vd.js +0 -177
  508. package/frontend/dist/js/uk-BqAd5zHF.js +0 -2
  509. package/frontend/dist/js/vi-D9gjlDVB.js +0 -2
  510. package/frontend/dist/js/zh-DKTyHkiR.js +0 -2
@@ -34,17 +34,85 @@ const namedResource = (row) => ({
34
34
  });
35
35
  const note = (row) => ({
36
36
  ...row,
37
+ pinned: number(row.pinned ?? 0, 'note pinned'),
37
38
  created_at: number(row.created_at, 'note created_at'),
38
39
  updated_at: number(row.updated_at, 'note updated_at'),
39
40
  });
41
+ const noteRevision = (row) => ({
42
+ ...row,
43
+ created_at: number(row.created_at, 'note revision created_at'),
44
+ });
45
+ const noteAttachment = (row) => ({
46
+ ...row,
47
+ size: number(row.size, 'note attachment size'),
48
+ created_at: number(row.created_at, 'note attachment created_at'),
49
+ });
40
50
  const calendarEvent = (row) => ({
41
51
  ...row,
42
52
  start_at: number(row.start_at, 'calendar event start_at'),
43
53
  end_at: row.end_at === null ? null : number(row.end_at, 'calendar event end_at'),
44
54
  all_day: number(row.all_day, 'calendar event all_day'),
55
+ reminder_minutes: row.reminder_minutes === null
56
+ ? null
57
+ : number(row.reminder_minutes, 'calendar event reminder_minutes'),
58
+ last_reminded_occurrence: row.last_reminded_occurrence === null
59
+ ? null
60
+ : number(row.last_reminded_occurrence, 'calendar event last_reminded_occurrence'),
45
61
  created_at: number(row.created_at, 'calendar event created_at'),
46
62
  updated_at: number(row.updated_at, 'calendar event updated_at'),
47
63
  });
64
+ const calendar = (row) => ({
65
+ ...row,
66
+ created_at: number(row.created_at, 'calendar created_at'),
67
+ updated_at: number(row.updated_at, 'calendar updated_at'),
68
+ });
69
+ const channel = (row) => ({
70
+ ...row,
71
+ created_at: number(row.created_at, 'channel created_at'),
72
+ updated_at: number(row.updated_at, 'channel updated_at'),
73
+ archived_at: row.archived_at === null
74
+ ? null
75
+ : number(row.archived_at, 'channel archived_at'),
76
+ });
77
+ const channelMember = (row) => ({
78
+ ...row,
79
+ joined_at: number(row.joined_at, 'channel member joined_at'),
80
+ last_read_at: number(row.last_read_at, 'channel member last_read_at'),
81
+ });
82
+ const channelMessage = (row) => ({
83
+ ...row,
84
+ created_at: number(row.created_at, 'channel message created_at'),
85
+ updated_at: number(row.updated_at, 'channel message updated_at'),
86
+ edited_at: row.edited_at === null
87
+ ? null
88
+ : number(row.edited_at, 'channel message edited_at'),
89
+ deleted_at: row.deleted_at === null
90
+ ? null
91
+ : number(row.deleted_at, 'channel message deleted_at'),
92
+ pinned_at: row.pinned_at === null
93
+ ? null
94
+ : number(row.pinned_at, 'channel message pinned_at'),
95
+ });
96
+ const channelReaction = (row) => ({
97
+ ...row,
98
+ created_at: number(row.created_at, 'channel reaction created_at'),
99
+ });
100
+ const channelAttachment = (row) => ({
101
+ ...row,
102
+ size: number(row.size, 'channel attachment size'),
103
+ created_at: number(row.created_at, 'channel attachment created_at'),
104
+ });
105
+ const notification = (row) => ({
106
+ ...row,
107
+ created_at: number(row.created_at, 'notification created_at'),
108
+ read_at: row.read_at === null ? null : number(row.read_at, 'notification read_at'),
109
+ });
110
+ const webhookTarget = (row) => ({
111
+ ...row,
112
+ enabled: number(row.enabled, 'webhook target enabled'),
113
+ created_at: number(row.created_at, 'webhook target created_at'),
114
+ updated_at: number(row.updated_at, 'webhook target updated_at'),
115
+ });
48
116
  const automation = (row) => ({
49
117
  ...row,
50
118
  next_run_at: row.next_run_at === null
@@ -68,6 +136,66 @@ const automationRun = (row) => ({
68
136
  seen_at: row.seen_at === null ? null : number(row.seen_at, 'automation run seen_at'),
69
137
  created_at: number(row.created_at, 'automation run created_at'),
70
138
  });
139
+ const toolServer = (row) => ({
140
+ ...row,
141
+ spec_revision: number(row.spec_revision, 'tool server spec_revision'),
142
+ enabled: number(row.enabled, 'tool server enabled'),
143
+ timeout_ms: number(row.timeout_ms, 'tool server timeout_ms'),
144
+ max_response_bytes: number(row.max_response_bytes, 'tool server max_response_bytes'),
145
+ created_at: number(row.created_at, 'tool server created_at'),
146
+ updated_at: number(row.updated_at, 'tool server updated_at'),
147
+ });
148
+ const toolServerTool = (row) => ({
149
+ ...row,
150
+ side_effect: number(row.side_effect, 'tool side_effect'),
151
+ enabled: number(row.enabled, 'tool enabled'),
152
+ created_at: number(row.created_at, 'tool created_at'),
153
+ updated_at: number(row.updated_at, 'tool updated_at'),
154
+ });
155
+ const toolServerCredential = (row) => ({
156
+ ...row,
157
+ created_at: number(row.created_at, 'tool credential created_at'),
158
+ updated_at: number(row.updated_at, 'tool credential updated_at'),
159
+ });
160
+ const toolApproval = (row) => ({
161
+ ...row,
162
+ created_at: number(row.created_at, 'tool approval created_at'),
163
+ resolved_at: row.resolved_at === null
164
+ ? null
165
+ : number(row.resolved_at, 'tool approval resolved_at'),
166
+ expires_at: row.expires_at === null
167
+ ? null
168
+ : number(row.expires_at, 'tool approval expires_at'),
169
+ });
170
+ const prompt = (row) => ({
171
+ ...row,
172
+ version: number(row.version, 'prompt version'),
173
+ created_at: number(row.created_at, 'prompt created_at'),
174
+ updated_at: number(row.updated_at, 'prompt updated_at'),
175
+ });
176
+ const promptVersion = (row) => ({
177
+ ...row,
178
+ version: number(row.version, 'prompt revision version'),
179
+ created_at: number(row.created_at, 'prompt revision created_at'),
180
+ });
181
+ const skill = (row) => ({
182
+ ...row,
183
+ enabled: number(row.enabled, 'skill enabled'),
184
+ version: number(row.version, 'skill version'),
185
+ created_at: number(row.created_at, 'skill created_at'),
186
+ updated_at: number(row.updated_at, 'skill updated_at'),
187
+ });
188
+ const skillVersion = (row) => ({
189
+ ...row,
190
+ version: number(row.version, 'skill revision version'),
191
+ created_at: number(row.created_at, 'skill revision created_at'),
192
+ });
193
+ const skillFile = (row) => ({
194
+ ...row,
195
+ size: number(row.size, 'skill file size'),
196
+ created_at: number(row.created_at, 'skill file created_at'),
197
+ updated_at: number(row.updated_at, 'skill file updated_at'),
198
+ });
71
199
  const changes = (rowCount) => rowCount ?? 0;
72
200
  const lockOwner = async (client, userId) => {
73
201
  const result = await client.query('SELECT id FROM users WHERE id = $1 FOR UPDATE', [userId]);
@@ -251,6 +379,10 @@ class PostgresKnowledgeCollectionRepository {
251
379
  ORDER BY lower(name), name`, [userId]);
252
380
  return result.rows.map(namedResource);
253
381
  }
382
+ async findById(collectionId) {
383
+ const result = await this.database.query('SELECT * FROM knowledge_collections WHERE id = $1', [collectionId]);
384
+ return result.rows[0] ? namedResource(result.rows[0]) : null;
385
+ }
254
386
  async replace(collection) {
255
387
  await this.database.transaction(async (client) => {
256
388
  await lockOwner(client, collection.user_id);
@@ -282,7 +414,7 @@ class PostgresNoteRepository {
282
414
  async listByOwner(userId, maximum) {
283
415
  const result = await this.database.query(`SELECT * FROM notes
284
416
  WHERE user_id = $1
285
- ORDER BY updated_at DESC
417
+ ORDER BY pinned DESC, updated_at DESC
286
418
  LIMIT $2`, [userId, maximum]);
287
419
  return result.rows.map(note);
288
420
  }
@@ -303,17 +435,19 @@ class PostgresNoteRepository {
303
435
  throw new PersistenceResourceLimitError('note', maximum);
304
436
  }
305
437
  }
306
- const result = await client.query(`INSERT INTO notes (id, user_id, title, content, created_at, updated_at)
307
- VALUES ($1, $2, $3, $4, $5, $6)
438
+ const result = await client.query(`INSERT INTO notes (id, user_id, title, content, pinned, created_at, updated_at)
439
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
308
440
  ON CONFLICT (id) DO UPDATE SET
309
441
  title = EXCLUDED.title,
310
442
  content = EXCLUDED.content,
443
+ pinned = EXCLUDED.pinned,
311
444
  updated_at = EXCLUDED.updated_at
312
445
  WHERE notes.user_id = EXCLUDED.user_id`, [
313
446
  value.id,
314
447
  value.user_id,
315
448
  value.title,
316
449
  value.content,
450
+ value.pinned,
317
451
  value.created_at,
318
452
  value.updated_at,
319
453
  ]);
@@ -332,6 +466,8 @@ class PostgresNoteRepository {
332
466
  assign('title', patch.title);
333
467
  if (patch.content !== undefined)
334
468
  assign('content', patch.content);
469
+ if (patch.pinned !== undefined)
470
+ assign('pinned', patch.pinned);
335
471
  assign('updated_at', patch.updated_at);
336
472
  values.push(noteId, userId);
337
473
  const result = await this.database.query(`UPDATE notes SET ${assignments.join(', ')}
@@ -342,6 +478,100 @@ class PostgresNoteRepository {
342
478
  async deleteByOwner(noteId, userId) {
343
479
  return (changes((await this.database.query('DELETE FROM notes WHERE id = $1 AND user_id = $2', [noteId, userId])).rowCount) > 0);
344
480
  }
481
+ async findById(noteId) {
482
+ const result = await this.database.query('SELECT * FROM notes WHERE id = $1', [noteId]);
483
+ return result.rows[0] ? note(result.rows[0]) : null;
484
+ }
485
+ async patchById(noteId, patch) {
486
+ const assignments = [];
487
+ const values = [];
488
+ const assign = (column, value) => {
489
+ values.push(value);
490
+ assignments.push(`${column} = $${values.length}`);
491
+ };
492
+ if (patch.title !== undefined)
493
+ assign('title', patch.title);
494
+ if (patch.content !== undefined)
495
+ assign('content', patch.content);
496
+ if (patch.pinned !== undefined)
497
+ assign('pinned', patch.pinned);
498
+ assign('updated_at', patch.updated_at);
499
+ values.push(noteId);
500
+ const result = await this.database.query(`UPDATE notes SET ${assignments.join(', ')}
501
+ WHERE id = $${values.length}
502
+ RETURNING *`, values);
503
+ return result.rows[0] ? note(result.rows[0]) : null;
504
+ }
505
+ async listRevisions(noteId, maximum) {
506
+ const result = await this.database.query(`SELECT * FROM note_revisions
507
+ WHERE note_id = $1
508
+ ORDER BY created_at DESC, id DESC
509
+ LIMIT $2`, [noteId, maximum]);
510
+ return result.rows.map(noteRevision);
511
+ }
512
+ async findRevision(revisionId) {
513
+ const result = await this.database.query('SELECT * FROM note_revisions WHERE id = $1', [revisionId]);
514
+ return result.rows[0] ? noteRevision(result.rows[0]) : null;
515
+ }
516
+ async insertRevision(revision, maximum) {
517
+ await this.database.transaction(async (client) => {
518
+ // Strictly increasing per note so same-millisecond snapshots keep a
519
+ // deterministic order for listing, restore, and pruning.
520
+ await client.query(`INSERT INTO note_revisions (id, note_id, title, content, created_at)
521
+ SELECT $1, $2, $3, $4,
522
+ GREATEST($5::bigint, COALESCE(MAX(created_at), 0) + 1)
523
+ FROM note_revisions WHERE note_id = $2`, [
524
+ revision.id,
525
+ revision.note_id,
526
+ revision.title,
527
+ revision.content,
528
+ revision.created_at,
529
+ ]);
530
+ await client.query(`DELETE FROM note_revisions
531
+ WHERE note_id = $1
532
+ AND id NOT IN (
533
+ SELECT id FROM note_revisions
534
+ WHERE note_id = $1
535
+ ORDER BY created_at DESC, id DESC
536
+ LIMIT $2
537
+ )`, [revision.note_id, maximum]);
538
+ });
539
+ }
540
+ async listAttachments(noteId) {
541
+ const result = await this.database.query(`SELECT * FROM note_attachments
542
+ WHERE note_id = $1
543
+ ORDER BY created_at ASC, id ASC`, [noteId]);
544
+ return result.rows.map(noteAttachment);
545
+ }
546
+ async findAttachment(attachmentId) {
547
+ const result = await this.database.query('SELECT * FROM note_attachments WHERE id = $1', [attachmentId]);
548
+ return result.rows[0] ? noteAttachment(result.rows[0]) : null;
549
+ }
550
+ async insertAttachmentWithLimit(attachment, maximum) {
551
+ await this.database.transaction(async (client) => {
552
+ await client.query('SELECT id FROM notes WHERE id = $1 FOR UPDATE', [
553
+ attachment.note_id,
554
+ ]);
555
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM note_attachments WHERE note_id = $1', [attachment.note_id]);
556
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
557
+ throw new PersistenceResourceLimitError('note-attachment', maximum);
558
+ }
559
+ await client.query(`INSERT INTO note_attachments
560
+ (id, note_id, blob_id, filename, content_type, size, created_at)
561
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
562
+ attachment.id,
563
+ attachment.note_id,
564
+ attachment.blob_id,
565
+ attachment.filename,
566
+ attachment.content_type,
567
+ attachment.size,
568
+ attachment.created_at,
569
+ ]);
570
+ });
571
+ }
572
+ async deleteAttachment(attachmentId) {
573
+ return (changes((await this.database.query('DELETE FROM note_attachments WHERE id = $1', [attachmentId])).rowCount) > 0);
574
+ }
345
575
  }
346
576
  class PostgresCalendarEventRepository {
347
577
  constructor(database) {
@@ -361,10 +591,48 @@ class PostgresCalendarEventRepository {
361
591
  LIMIT $2`, [userId, maximum]);
362
592
  return result.rows.map(calendarEvent);
363
593
  }
594
+ async listByCalendarsBetween(calendarIds, from, to, maximum) {
595
+ if (calendarIds.length === 0)
596
+ return [];
597
+ const result = await this.database.query(`SELECT * FROM calendar_events
598
+ WHERE calendar_id = ANY($1::text[])
599
+ AND start_at >= $2 AND start_at < $3
600
+ ORDER BY start_at ASC
601
+ LIMIT $4`, [[...calendarIds], from, to, maximum]);
602
+ return result.rows.map(calendarEvent);
603
+ }
604
+ async listRecurringByCalendars(calendarIds, maximum) {
605
+ if (calendarIds.length === 0)
606
+ return [];
607
+ const result = await this.database.query(`SELECT * FROM calendar_events
608
+ WHERE calendar_id = ANY($1::text[])
609
+ AND recurrence IS NOT NULL
610
+ ORDER BY start_at ASC
611
+ LIMIT $2`, [[...calendarIds], maximum]);
612
+ return result.rows.map(calendarEvent);
613
+ }
364
614
  async findByOwner(eventId, userId) {
365
615
  const result = await this.database.query('SELECT * FROM calendar_events WHERE id = $1 AND user_id = $2', [eventId, userId]);
366
616
  return result.rows[0] ? calendarEvent(result.rows[0]) : null;
367
617
  }
618
+ async findById(eventId) {
619
+ const result = await this.database.query('SELECT * FROM calendar_events WHERE id = $1', [eventId]);
620
+ return result.rows[0] ? calendarEvent(result.rows[0]) : null;
621
+ }
622
+ async listWithReminders(maximum) {
623
+ const result = await this.database.query(`SELECT * FROM calendar_events
624
+ WHERE reminder_minutes IS NOT NULL
625
+ ORDER BY start_at ASC
626
+ LIMIT $1`, [maximum]);
627
+ return result.rows.map(calendarEvent);
628
+ }
629
+ async markReminded(eventId, occurrenceStart) {
630
+ return (changes((await this.database.query(`UPDATE calendar_events
631
+ SET last_reminded_occurrence = $1
632
+ WHERE id = $2
633
+ AND (last_reminded_occurrence IS NULL
634
+ OR last_reminded_occurrence < $1)`, [occurrenceStart, eventId])).rowCount) > 0);
635
+ }
368
636
  async replaceWithLimit(value, maximum) {
369
637
  await this.database.transaction(async (client) => {
370
638
  await lockOwner(client, value.user_id);
@@ -380,8 +648,9 @@ class PostgresCalendarEventRepository {
380
648
  }
381
649
  const result = await client.query(`INSERT INTO calendar_events
382
650
  (id, user_id, title, notes, start_at, end_at, all_day,
383
- recurrence, created_at, updated_at)
384
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
651
+ recurrence, calendar_id, reminder_minutes,
652
+ last_reminded_occurrence, created_at, updated_at)
653
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
385
654
  ON CONFLICT (id) DO UPDATE SET
386
655
  title = EXCLUDED.title,
387
656
  notes = EXCLUDED.notes,
@@ -389,6 +658,9 @@ class PostgresCalendarEventRepository {
389
658
  end_at = EXCLUDED.end_at,
390
659
  all_day = EXCLUDED.all_day,
391
660
  recurrence = EXCLUDED.recurrence,
661
+ calendar_id = EXCLUDED.calendar_id,
662
+ reminder_minutes = EXCLUDED.reminder_minutes,
663
+ last_reminded_occurrence = EXCLUDED.last_reminded_occurrence,
392
664
  updated_at = EXCLUDED.updated_at
393
665
  WHERE calendar_events.user_id = EXCLUDED.user_id`, [
394
666
  value.id,
@@ -399,6 +671,9 @@ class PostgresCalendarEventRepository {
399
671
  value.end_at,
400
672
  value.all_day,
401
673
  value.recurrence,
674
+ value.calendar_id,
675
+ value.reminder_minutes,
676
+ value.last_reminded_occurrence,
402
677
  value.created_at,
403
678
  value.updated_at,
404
679
  ]);
@@ -410,6 +685,553 @@ class PostgresCalendarEventRepository {
410
685
  return (changes((await this.database.query('DELETE FROM calendar_events WHERE id = $1 AND user_id = $2', [eventId, userId])).rowCount) > 0);
411
686
  }
412
687
  }
688
+ class PostgresCalendarRepository {
689
+ constructor(database) {
690
+ this.database = database;
691
+ }
692
+ async listByOwner(userId, maximum) {
693
+ const result = await this.database.query(`SELECT * FROM calendars
694
+ WHERE user_id = $1
695
+ ORDER BY created_at ASC
696
+ LIMIT $2`, [userId, maximum]);
697
+ return result.rows.map(calendar);
698
+ }
699
+ async findByOwner(calendarId, userId) {
700
+ const result = await this.database.query('SELECT * FROM calendars WHERE id = $1 AND user_id = $2', [calendarId, userId]);
701
+ return result.rows[0] ? calendar(result.rows[0]) : null;
702
+ }
703
+ async findById(calendarId) {
704
+ const result = await this.database.query('SELECT * FROM calendars WHERE id = $1', [calendarId]);
705
+ return result.rows[0] ? calendar(result.rows[0]) : null;
706
+ }
707
+ async replaceWithLimit(value, maximum) {
708
+ await this.database.transaction(async (client) => {
709
+ await lockOwner(client, value.user_id);
710
+ const existing = await client.query('SELECT user_id FROM calendars WHERE id = $1 FOR UPDATE', [value.id]);
711
+ if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
712
+ throw new PersistenceResourceConflictError();
713
+ }
714
+ if (!existing.rows[0]) {
715
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM calendars WHERE user_id = $1', [value.user_id]);
716
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
717
+ throw new PersistenceResourceLimitError('calendar', maximum);
718
+ }
719
+ }
720
+ const result = await client.query(`INSERT INTO calendars
721
+ (id, user_id, name, color, created_at, updated_at)
722
+ VALUES ($1, $2, $3, $4, $5, $6)
723
+ ON CONFLICT (id) DO UPDATE SET
724
+ name = EXCLUDED.name,
725
+ color = EXCLUDED.color,
726
+ updated_at = EXCLUDED.updated_at
727
+ WHERE calendars.user_id = EXCLUDED.user_id`, [
728
+ value.id,
729
+ value.user_id,
730
+ value.name,
731
+ value.color,
732
+ value.created_at,
733
+ value.updated_at,
734
+ ]);
735
+ if (result.rowCount !== 1)
736
+ throw new PersistenceResourceConflictError();
737
+ });
738
+ }
739
+ async deleteAndDetach(calendarId, userId) {
740
+ return this.database.transaction(async (client) => {
741
+ const deleted = changes((await client.query('DELETE FROM calendars WHERE id = $1 AND user_id = $2', [calendarId, userId])).rowCount) > 0;
742
+ if (deleted) {
743
+ await client.query(`UPDATE calendar_events
744
+ SET calendar_id = NULL
745
+ WHERE calendar_id = $1 AND user_id = $2`, [calendarId, userId]);
746
+ }
747
+ return deleted;
748
+ });
749
+ }
750
+ }
751
+ class PostgresChannelRepository {
752
+ constructor(database) {
753
+ this.database = database;
754
+ }
755
+ async insertWithOwner(value, owner, maximumPerUser) {
756
+ await this.database.transaction(async (client) => {
757
+ await lockOwner(client, owner.user_id);
758
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM channels WHERE created_by = $1', [owner.user_id]);
759
+ if (Number(count.rows[0]?.count || 0) >= maximumPerUser) {
760
+ throw new PersistenceResourceLimitError('channel', maximumPerUser);
761
+ }
762
+ await client.query(`INSERT INTO channels
763
+ (id, type, name, description, dm_key, created_by,
764
+ created_at, updated_at, archived_at)
765
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
766
+ value.id,
767
+ value.type,
768
+ value.name,
769
+ value.description,
770
+ value.dm_key,
771
+ value.created_by,
772
+ value.created_at,
773
+ value.updated_at,
774
+ value.archived_at,
775
+ ]);
776
+ await client.query(`INSERT INTO channel_members
777
+ (channel_id, user_id, role, joined_at, last_read_at)
778
+ VALUES ($1, $2, $3, $4, $5)`, [
779
+ owner.channel_id,
780
+ owner.user_id,
781
+ owner.role,
782
+ owner.joined_at,
783
+ owner.last_read_at,
784
+ ]);
785
+ });
786
+ }
787
+ async findById(channelId) {
788
+ const result = await this.database.query('SELECT * FROM channels WHERE id = $1', [channelId]);
789
+ return result.rows[0] ? channel(result.rows[0]) : null;
790
+ }
791
+ async findByDmKey(dmKey) {
792
+ const result = await this.database.query('SELECT * FROM channels WHERE dm_key = $1', [dmKey]);
793
+ return result.rows[0] ? channel(result.rows[0]) : null;
794
+ }
795
+ async listForUser(userId, maximum) {
796
+ const result = await this.database.query(`SELECT
797
+ c.id, c.type, c.name, c.description, c.dm_key, c.created_by,
798
+ c.created_at, c.updated_at, c.archived_at,
799
+ m.channel_id AS member_channel_id, m.user_id AS member_user_id,
800
+ m.role AS member_role, m.joined_at AS member_joined_at,
801
+ m.last_read_at AS member_last_read_at
802
+ FROM channel_members m
803
+ JOIN channels c ON c.id = m.channel_id
804
+ WHERE m.user_id = $1
805
+ ORDER BY c.updated_at DESC
806
+ LIMIT $2`, [userId, maximum]);
807
+ return result.rows.map(row => ({
808
+ channel: channel(row),
809
+ member: channelMember({
810
+ channel_id: row.member_channel_id,
811
+ user_id: row.member_user_id,
812
+ role: row.member_role,
813
+ joined_at: row.member_joined_at,
814
+ last_read_at: row.member_last_read_at,
815
+ }),
816
+ }));
817
+ }
818
+ async listPublic(maximum) {
819
+ const result = await this.database.query(`SELECT * FROM channels
820
+ WHERE type = 'public' AND archived_at IS NULL
821
+ ORDER BY updated_at DESC
822
+ LIMIT $1`, [maximum]);
823
+ return result.rows.map(channel);
824
+ }
825
+ async update(value) {
826
+ await this.database.query(`UPDATE channels SET
827
+ name = $1,
828
+ description = $2,
829
+ updated_at = $3,
830
+ archived_at = $4
831
+ WHERE id = $5`, [
832
+ value.name,
833
+ value.description,
834
+ value.updated_at,
835
+ value.archived_at,
836
+ value.id,
837
+ ]);
838
+ }
839
+ async delete(channelId) {
840
+ return (changes((await this.database.query('DELETE FROM channels WHERE id = $1', [
841
+ channelId,
842
+ ])).rowCount) > 0);
843
+ }
844
+ async upsertMember(member, maximumMembers) {
845
+ await this.database.transaction(async (client) => {
846
+ const existing = await client.query(`SELECT 1 FROM channel_members
847
+ WHERE channel_id = $1 AND user_id = $2
848
+ FOR UPDATE`, [member.channel_id, member.user_id]);
849
+ if (!existing.rows[0]) {
850
+ const count = await client.query(`SELECT COUNT(*)::text AS count FROM channel_members
851
+ WHERE channel_id = $1`, [member.channel_id]);
852
+ if (Number(count.rows[0]?.count || 0) >= maximumMembers) {
853
+ throw new PersistenceResourceLimitError('channel-member', maximumMembers);
854
+ }
855
+ }
856
+ await client.query(`INSERT INTO channel_members
857
+ (channel_id, user_id, role, joined_at, last_read_at)
858
+ VALUES ($1, $2, $3, $4, $5)
859
+ ON CONFLICT (channel_id, user_id) DO UPDATE SET
860
+ role = EXCLUDED.role`, [
861
+ member.channel_id,
862
+ member.user_id,
863
+ member.role,
864
+ member.joined_at,
865
+ member.last_read_at,
866
+ ]);
867
+ });
868
+ }
869
+ async findMember(channelId, userId) {
870
+ const result = await this.database.query('SELECT * FROM channel_members WHERE channel_id = $1 AND user_id = $2', [channelId, userId]);
871
+ return result.rows[0] ? channelMember(result.rows[0]) : null;
872
+ }
873
+ async listMembers(channelId, maximum) {
874
+ const result = await this.database.query(`SELECT * FROM channel_members
875
+ WHERE channel_id = $1
876
+ ORDER BY joined_at ASC
877
+ LIMIT $2`, [channelId, maximum]);
878
+ return result.rows.map(channelMember);
879
+ }
880
+ async removeMember(channelId, userId) {
881
+ return (changes((await this.database.query('DELETE FROM channel_members WHERE channel_id = $1 AND user_id = $2', [channelId, userId])).rowCount) > 0);
882
+ }
883
+ async advanceLastRead(channelId, userId, lastReadAt) {
884
+ return (changes((await this.database.query(`UPDATE channel_members
885
+ SET last_read_at = $1
886
+ WHERE channel_id = $2 AND user_id = $3 AND last_read_at < $1`, [lastReadAt, channelId, userId])).rowCount) > 0);
887
+ }
888
+ async unreadSummaryForUser(userId) {
889
+ const result = await this.database.query(`SELECT
890
+ m.channel_id,
891
+ COUNT(msg.id)::text AS unread_count,
892
+ MAX(msg.created_at) AS latest_message_at
893
+ FROM channel_members m
894
+ LEFT JOIN channel_messages msg
895
+ ON msg.channel_id = m.channel_id
896
+ AND msg.created_at > m.last_read_at
897
+ AND msg.deleted_at IS NULL
898
+ AND (msg.user_id IS NULL OR msg.user_id != m.user_id
899
+ OR msg.author_kind != 'user')
900
+ WHERE m.user_id = $1
901
+ GROUP BY m.channel_id`, [userId]);
902
+ return result.rows.map(row => ({
903
+ channel_id: row.channel_id,
904
+ unread_count: number(row.unread_count, 'channel unread_count'),
905
+ latest_message_at: row.latest_message_at === null
906
+ ? null
907
+ : number(row.latest_message_at, 'channel latest_message_at'),
908
+ }));
909
+ }
910
+ }
911
+ class PostgresChannelMessageRepository {
912
+ constructor(database) {
913
+ this.database = database;
914
+ }
915
+ async insertIfAbsent(message, attachments, maximumPerChannel) {
916
+ return this.database.transaction(async (client) => {
917
+ const existing = await client.query('SELECT * FROM channel_messages WHERE id = $1', [message.id]);
918
+ if (existing.rows[0]) {
919
+ return { stored: channelMessage(existing.rows[0]), inserted: false };
920
+ }
921
+ const count = await client.query(`SELECT COUNT(*)::text AS count FROM channel_messages
922
+ WHERE channel_id = $1`, [message.channel_id]);
923
+ if (Number(count.rows[0]?.count || 0) >= maximumPerChannel) {
924
+ throw new PersistenceResourceLimitError('channel-message', maximumPerChannel);
925
+ }
926
+ await client.query(`INSERT INTO channel_messages
927
+ (id, channel_id, user_id, parent_id, author_kind, model, content,
928
+ metadata, created_at, updated_at, edited_at, deleted_at,
929
+ pinned_at, pinned_by)
930
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`, [
931
+ message.id,
932
+ message.channel_id,
933
+ message.user_id,
934
+ message.parent_id,
935
+ message.author_kind,
936
+ message.model,
937
+ message.content,
938
+ message.metadata,
939
+ message.created_at,
940
+ message.updated_at,
941
+ message.edited_at,
942
+ message.deleted_at,
943
+ message.pinned_at,
944
+ message.pinned_by,
945
+ ]);
946
+ for (const attachment of attachments) {
947
+ if (attachment.message_id !== message.id) {
948
+ throw new Error('A channel attachment does not belong to its message');
949
+ }
950
+ await client.query(`INSERT INTO channel_attachments
951
+ (id, message_id, channel_id, blob_id, filename, content_type,
952
+ size, created_by, created_at)
953
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
954
+ attachment.id,
955
+ attachment.message_id,
956
+ attachment.channel_id,
957
+ attachment.blob_id,
958
+ attachment.filename,
959
+ attachment.content_type,
960
+ attachment.size,
961
+ attachment.created_by,
962
+ attachment.created_at,
963
+ ]);
964
+ }
965
+ await client.query('UPDATE channels SET updated_at = $1 WHERE id = $2', [
966
+ message.created_at,
967
+ message.channel_id,
968
+ ]);
969
+ return { stored: message, inserted: true };
970
+ });
971
+ }
972
+ async findById(messageId) {
973
+ const result = await this.database.query('SELECT * FROM channel_messages WHERE id = $1', [messageId]);
974
+ return result.rows[0] ? channelMessage(result.rows[0]) : null;
975
+ }
976
+ async listPage(channelId, options) {
977
+ const clauses = ['channel_id = $1'];
978
+ const parameters = [channelId];
979
+ if (options.parentId === null) {
980
+ clauses.push('parent_id IS NULL');
981
+ }
982
+ else if (options.parentId !== undefined) {
983
+ parameters.push(options.parentId);
984
+ clauses.push(`parent_id = $${parameters.length}`);
985
+ }
986
+ if (options.before) {
987
+ parameters.push(options.before.created_at);
988
+ const createdIndex = parameters.length;
989
+ parameters.push(options.before.id);
990
+ clauses.push(`(created_at < $${createdIndex} OR ` +
991
+ `(created_at = $${createdIndex} AND id < $${parameters.length}))`);
992
+ }
993
+ if (options.after) {
994
+ parameters.push(options.after.created_at);
995
+ const createdIndex = parameters.length;
996
+ parameters.push(options.after.id);
997
+ clauses.push(`(created_at > $${createdIndex} OR ` +
998
+ `(created_at = $${createdIndex} AND id > $${parameters.length}))`);
999
+ }
1000
+ const direction = options.after ? 'ASC' : 'DESC';
1001
+ parameters.push(options.limit);
1002
+ const result = await this.database.query(`SELECT * FROM channel_messages
1003
+ WHERE ${clauses.join(' AND ')}
1004
+ ORDER BY created_at ${direction}, id ${direction}
1005
+ LIMIT $${parameters.length}`, parameters);
1006
+ return result.rows.map(channelMessage);
1007
+ }
1008
+ async listThread(parentId, maximum) {
1009
+ const result = await this.database.query(`SELECT * FROM channel_messages
1010
+ WHERE parent_id = $1
1011
+ ORDER BY created_at ASC, id ASC
1012
+ LIMIT $2`, [parentId, maximum]);
1013
+ return result.rows.map(channelMessage);
1014
+ }
1015
+ async countThreadReplies(parentIds) {
1016
+ if (parentIds.length === 0)
1017
+ return {};
1018
+ const result = await this.database.query(`SELECT parent_id, COUNT(*)::text AS count
1019
+ FROM channel_messages
1020
+ WHERE parent_id = ANY($1::text[]) AND deleted_at IS NULL
1021
+ GROUP BY parent_id`, [[...parentIds]]);
1022
+ const counts = {};
1023
+ for (const row of result.rows) {
1024
+ counts[row.parent_id] = number(row.count, 'channel thread reply count');
1025
+ }
1026
+ return counts;
1027
+ }
1028
+ async update(message) {
1029
+ await this.database.query(`UPDATE channel_messages SET
1030
+ content = $1,
1031
+ metadata = $2,
1032
+ updated_at = $3,
1033
+ edited_at = $4,
1034
+ deleted_at = $5,
1035
+ pinned_at = $6,
1036
+ pinned_by = $7
1037
+ WHERE id = $8`, [
1038
+ message.content,
1039
+ message.metadata,
1040
+ message.updated_at,
1041
+ message.edited_at,
1042
+ message.deleted_at,
1043
+ message.pinned_at,
1044
+ message.pinned_by,
1045
+ message.id,
1046
+ ]);
1047
+ }
1048
+ async listPinned(channelId, maximum) {
1049
+ const result = await this.database.query(`SELECT * FROM channel_messages
1050
+ WHERE channel_id = $1 AND pinned_at IS NOT NULL
1051
+ AND deleted_at IS NULL
1052
+ ORDER BY pinned_at DESC
1053
+ LIMIT $2`, [channelId, maximum]);
1054
+ return result.rows.map(channelMessage);
1055
+ }
1056
+ async listRecentForChannels(channelIds, maximum) {
1057
+ if (channelIds.length === 0)
1058
+ return [];
1059
+ const result = await this.database.query(`SELECT * FROM channel_messages
1060
+ WHERE channel_id = ANY($1::text[]) AND deleted_at IS NULL
1061
+ ORDER BY created_at DESC
1062
+ LIMIT $2`, [[...channelIds], maximum]);
1063
+ return result.rows.map(channelMessage);
1064
+ }
1065
+ async addReaction(reaction, maximumPerMessage) {
1066
+ return this.database.transaction(async (client) => {
1067
+ const existing = await client.query(`SELECT 1 FROM channel_reactions
1068
+ WHERE message_id = $1 AND user_id = $2 AND emoji = $3`, [reaction.message_id, reaction.user_id, reaction.emoji]);
1069
+ if (existing.rows[0])
1070
+ return false;
1071
+ const count = await client.query(`SELECT COUNT(*)::text AS count FROM channel_reactions
1072
+ WHERE message_id = $1`, [reaction.message_id]);
1073
+ if (Number(count.rows[0]?.count || 0) >= maximumPerMessage) {
1074
+ throw new PersistenceResourceLimitError('channel-reaction', maximumPerMessage);
1075
+ }
1076
+ const result = await client.query(`INSERT INTO channel_reactions
1077
+ (id, message_id, user_id, emoji, created_at)
1078
+ VALUES ($1, $2, $3, $4, $5)
1079
+ ON CONFLICT (message_id, user_id, emoji) DO NOTHING`, [
1080
+ reaction.id,
1081
+ reaction.message_id,
1082
+ reaction.user_id,
1083
+ reaction.emoji,
1084
+ reaction.created_at,
1085
+ ]);
1086
+ return changes(result.rowCount) > 0;
1087
+ });
1088
+ }
1089
+ async removeReaction(messageId, userId, emoji) {
1090
+ return (changes((await this.database.query(`DELETE FROM channel_reactions
1091
+ WHERE message_id = $1 AND user_id = $2 AND emoji = $3`, [messageId, userId, emoji])).rowCount) > 0);
1092
+ }
1093
+ async listReactionsForMessages(messageIds) {
1094
+ if (messageIds.length === 0)
1095
+ return [];
1096
+ const result = await this.database.query(`SELECT * FROM channel_reactions
1097
+ WHERE message_id = ANY($1::text[])
1098
+ ORDER BY created_at ASC`, [[...messageIds]]);
1099
+ return result.rows.map(channelReaction);
1100
+ }
1101
+ async findAttachment(attachmentId) {
1102
+ const result = await this.database.query('SELECT * FROM channel_attachments WHERE id = $1', [attachmentId]);
1103
+ return result.rows[0] ? channelAttachment(result.rows[0]) : null;
1104
+ }
1105
+ async listAttachmentsForMessages(messageIds) {
1106
+ if (messageIds.length === 0)
1107
+ return [];
1108
+ const result = await this.database.query(`SELECT * FROM channel_attachments
1109
+ WHERE message_id = ANY($1::text[])
1110
+ ORDER BY created_at ASC`, [[...messageIds]]);
1111
+ return result.rows.map(channelAttachment);
1112
+ }
1113
+ async listAttachmentBlobIds(channelId) {
1114
+ const result = await this.database.query('SELECT blob_id, created_by FROM channel_attachments WHERE channel_id = $1', [channelId]);
1115
+ return result.rows;
1116
+ }
1117
+ }
1118
+ class PostgresNotificationRepository {
1119
+ constructor(database) {
1120
+ this.database = database;
1121
+ }
1122
+ async insertWithLimit(value, maximumPerUser) {
1123
+ return this.database.transaction(async (client) => {
1124
+ await lockOwner(client, value.user_id);
1125
+ const result = await client.query(`INSERT INTO notifications
1126
+ (id, user_id, type, title, body, href, source_key,
1127
+ created_at, read_at)
1128
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
1129
+ ON CONFLICT (user_id, source_key) DO NOTHING`, [
1130
+ value.id,
1131
+ value.user_id,
1132
+ value.type,
1133
+ value.title,
1134
+ value.body,
1135
+ value.href,
1136
+ value.source_key,
1137
+ value.created_at,
1138
+ value.read_at,
1139
+ ]);
1140
+ if (changes(result.rowCount) === 0)
1141
+ return false;
1142
+ await client.query(`DELETE FROM notifications
1143
+ WHERE user_id = $1
1144
+ AND id NOT IN (
1145
+ SELECT id FROM notifications
1146
+ WHERE user_id = $1
1147
+ ORDER BY created_at DESC, id DESC
1148
+ LIMIT $2
1149
+ )`, [value.user_id, maximumPerUser]);
1150
+ return true;
1151
+ });
1152
+ }
1153
+ async listByOwner(userId, options) {
1154
+ const clauses = ['user_id = $1'];
1155
+ const parameters = [userId];
1156
+ if (options.before !== undefined) {
1157
+ parameters.push(options.before);
1158
+ clauses.push(`created_at < $${parameters.length}`);
1159
+ }
1160
+ if (options.unreadOnly) {
1161
+ clauses.push('read_at IS NULL');
1162
+ }
1163
+ parameters.push(options.limit);
1164
+ const result = await this.database.query(`SELECT * FROM notifications
1165
+ WHERE ${clauses.join(' AND ')}
1166
+ ORDER BY created_at DESC, id DESC
1167
+ LIMIT $${parameters.length}`, parameters);
1168
+ return result.rows.map(notification);
1169
+ }
1170
+ async countUnread(userId) {
1171
+ const result = await this.database.query(`SELECT COUNT(*)::text AS count FROM notifications
1172
+ WHERE user_id = $1 AND read_at IS NULL`, [userId]);
1173
+ return Number(result.rows[0]?.count || 0);
1174
+ }
1175
+ async markRead(notificationId, userId, readAt) {
1176
+ return (changes((await this.database.query(`UPDATE notifications SET read_at = $1
1177
+ WHERE id = $2 AND user_id = $3 AND read_at IS NULL`, [readAt, notificationId, userId])).rowCount) > 0);
1178
+ }
1179
+ async markAllRead(userId, readAt) {
1180
+ return changes((await this.database.query(`UPDATE notifications SET read_at = $1
1181
+ WHERE user_id = $2 AND read_at IS NULL`, [readAt, userId])).rowCount);
1182
+ }
1183
+ async deleteByOwner(notificationId, userId) {
1184
+ return (changes((await this.database.query('DELETE FROM notifications WHERE id = $1 AND user_id = $2', [notificationId, userId])).rowCount) > 0);
1185
+ }
1186
+ }
1187
+ class PostgresWebhookTargetRepository {
1188
+ constructor(database) {
1189
+ this.database = database;
1190
+ }
1191
+ async list(maximum) {
1192
+ const result = await this.database.query('SELECT * FROM webhook_targets ORDER BY created_at ASC LIMIT $1', [maximum]);
1193
+ return result.rows.map(webhookTarget);
1194
+ }
1195
+ async findById(targetId) {
1196
+ const result = await this.database.query('SELECT * FROM webhook_targets WHERE id = $1', [targetId]);
1197
+ return result.rows[0] ? webhookTarget(result.rows[0]) : null;
1198
+ }
1199
+ async replaceWithLimit(value, maximum) {
1200
+ await this.database.transaction(async (client) => {
1201
+ const existing = await client.query('SELECT 1 FROM webhook_targets WHERE id = $1 FOR UPDATE', [value.id]);
1202
+ if (!existing.rows[0]) {
1203
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM webhook_targets');
1204
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
1205
+ throw new PersistenceResourceLimitError('webhook-target', maximum);
1206
+ }
1207
+ }
1208
+ await client.query(`INSERT INTO webhook_targets
1209
+ (id, name, url, secret, events, enabled, created_by,
1210
+ created_at, updated_at)
1211
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
1212
+ ON CONFLICT (id) DO UPDATE SET
1213
+ name = EXCLUDED.name,
1214
+ url = EXCLUDED.url,
1215
+ secret = EXCLUDED.secret,
1216
+ events = EXCLUDED.events,
1217
+ enabled = EXCLUDED.enabled,
1218
+ updated_at = EXCLUDED.updated_at`, [
1219
+ value.id,
1220
+ value.name,
1221
+ value.url,
1222
+ value.secret,
1223
+ value.events,
1224
+ value.enabled,
1225
+ value.created_by,
1226
+ value.created_at,
1227
+ value.updated_at,
1228
+ ]);
1229
+ });
1230
+ }
1231
+ async delete(targetId) {
1232
+ return (changes((await this.database.query('DELETE FROM webhook_targets WHERE id = $1', [targetId])).rowCount) > 0);
1233
+ }
1234
+ }
413
1235
  class PostgresAutomationRepository {
414
1236
  constructor(database) {
415
1237
  this.database = database;
@@ -583,15 +1405,504 @@ class PostgresAutomationRunRepository {
583
1405
  AND finished_at <= $3`, [seenAt, userId, seenAt])).rowCount);
584
1406
  }
585
1407
  }
586
- class PostgresSessionFolderRepository {
1408
+ class PostgresToolServerRepository {
587
1409
  constructor(database) {
588
1410
  this.database = database;
589
1411
  }
590
- async listByOwner(userId, maximum) {
591
- const result = await this.database.query(`SELECT * FROM session_folders
592
- WHERE user_id = $1
593
- ORDER BY lower(name), name
594
- LIMIT $2`, [userId, maximum]);
1412
+ async list(maximum) {
1413
+ const result = await this.database.query(`SELECT * FROM tool_servers
1414
+ ORDER BY updated_at DESC
1415
+ LIMIT $1`, [maximum]);
1416
+ return result.rows.map(toolServer);
1417
+ }
1418
+ async findById(serverId) {
1419
+ const result = await this.database.query('SELECT * FROM tool_servers WHERE id = $1', [serverId]);
1420
+ return result.rows[0] ? toolServer(result.rows[0]) : null;
1421
+ }
1422
+ async replaceWithLimit(value, maximum) {
1423
+ await this.database.transaction(async (client) => {
1424
+ await lockOwner(client, value.user_id);
1425
+ const existing = await client.query('SELECT id FROM tool_servers WHERE id = $1 FOR UPDATE', [value.id]);
1426
+ if (!existing.rows[0]) {
1427
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM tool_servers');
1428
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
1429
+ throw new PersistenceResourceLimitError('tool-server', maximum);
1430
+ }
1431
+ }
1432
+ await client.query(`INSERT INTO tool_servers
1433
+ (id, user_id, name, description, kind, base_url, spec, spec_digest,
1434
+ spec_revision, auth_mode, auth_header, access_mode, enabled,
1435
+ timeout_ms, max_response_bytes, created_at, updated_at)
1436
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14,
1437
+ $15, $16, $17)
1438
+ ON CONFLICT (id) DO UPDATE SET
1439
+ name = EXCLUDED.name,
1440
+ description = EXCLUDED.description,
1441
+ base_url = EXCLUDED.base_url,
1442
+ spec = EXCLUDED.spec,
1443
+ spec_digest = EXCLUDED.spec_digest,
1444
+ spec_revision = EXCLUDED.spec_revision,
1445
+ auth_mode = EXCLUDED.auth_mode,
1446
+ auth_header = EXCLUDED.auth_header,
1447
+ access_mode = EXCLUDED.access_mode,
1448
+ enabled = EXCLUDED.enabled,
1449
+ timeout_ms = EXCLUDED.timeout_ms,
1450
+ max_response_bytes = EXCLUDED.max_response_bytes,
1451
+ updated_at = EXCLUDED.updated_at`, [
1452
+ value.id,
1453
+ value.user_id,
1454
+ value.name,
1455
+ value.description,
1456
+ value.kind,
1457
+ value.base_url,
1458
+ value.spec,
1459
+ value.spec_digest,
1460
+ value.spec_revision,
1461
+ value.auth_mode,
1462
+ value.auth_header,
1463
+ value.access_mode,
1464
+ value.enabled,
1465
+ value.timeout_ms,
1466
+ value.max_response_bytes,
1467
+ value.created_at,
1468
+ value.updated_at,
1469
+ ]);
1470
+ });
1471
+ }
1472
+ async delete(serverId) {
1473
+ return (changes((await this.database.query('DELETE FROM tool_servers WHERE id = $1', [
1474
+ serverId,
1475
+ ])).rowCount) > 0);
1476
+ }
1477
+ }
1478
+ class PostgresToolServerToolRepository {
1479
+ constructor(database) {
1480
+ this.database = database;
1481
+ }
1482
+ async listByServer(serverId) {
1483
+ const result = await this.database.query(`SELECT * FROM tool_server_tools
1484
+ WHERE server_id = $1
1485
+ ORDER BY name ASC`, [serverId]);
1486
+ return result.rows.map(toolServerTool);
1487
+ }
1488
+ async replaceAllForServer(serverId, tools) {
1489
+ await this.database.transaction(async (client) => {
1490
+ const previousResult = await client.query(`SELECT name, enabled, side_effect FROM tool_server_tools
1491
+ WHERE server_id = $1 FOR UPDATE`, [serverId]);
1492
+ const previous = new Map(previousResult.rows.map(row => [
1493
+ row.name,
1494
+ {
1495
+ enabled: number(row.enabled, 'tool enabled'),
1496
+ side_effect: number(row.side_effect, 'tool side_effect'),
1497
+ },
1498
+ ]));
1499
+ await client.query('DELETE FROM tool_server_tools WHERE server_id = $1', [
1500
+ serverId,
1501
+ ]);
1502
+ for (const tool of tools) {
1503
+ if (tool.server_id !== serverId) {
1504
+ throw new Error('A tool row does not belong to its server');
1505
+ }
1506
+ const kept = previous.get(tool.name);
1507
+ await client.query(`INSERT INTO tool_server_tools
1508
+ (id, server_id, name, description, params_schema, detail,
1509
+ side_effect, enabled, created_at, updated_at)
1510
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
1511
+ tool.id,
1512
+ tool.server_id,
1513
+ tool.name,
1514
+ tool.description,
1515
+ tool.params_schema,
1516
+ tool.detail,
1517
+ kept ? kept.side_effect : tool.side_effect,
1518
+ kept ? kept.enabled : tool.enabled,
1519
+ tool.created_at,
1520
+ tool.updated_at,
1521
+ ]);
1522
+ }
1523
+ });
1524
+ }
1525
+ async updateOverrides(serverId, toolName, overrides, updatedAt) {
1526
+ const assignments = ['updated_at = $1'];
1527
+ const values = [updatedAt];
1528
+ if (overrides.enabled !== undefined) {
1529
+ values.push(overrides.enabled);
1530
+ assignments.push(`enabled = $${values.length}`);
1531
+ }
1532
+ if (overrides.side_effect !== undefined) {
1533
+ values.push(overrides.side_effect);
1534
+ assignments.push(`side_effect = $${values.length}`);
1535
+ }
1536
+ values.push(serverId, toolName);
1537
+ const result = await this.database.query(`UPDATE tool_server_tools
1538
+ SET ${assignments.join(', ')}
1539
+ WHERE server_id = $${values.length - 1} AND name = $${values.length}
1540
+ RETURNING *`, values);
1541
+ return result.rows[0] ? toolServerTool(result.rows[0]) : null;
1542
+ }
1543
+ }
1544
+ class PostgresToolServerCredentialRepository {
1545
+ constructor(database) {
1546
+ this.database = database;
1547
+ }
1548
+ async find(serverId, userId) {
1549
+ const result = await this.database.query(`SELECT * FROM tool_server_credentials
1550
+ WHERE server_id = $1 AND user_id = $2`, [serverId, userId]);
1551
+ return result.rows[0] ? toolServerCredential(result.rows[0]) : null;
1552
+ }
1553
+ async upsert(credential) {
1554
+ await this.database.query(`INSERT INTO tool_server_credentials
1555
+ (id, server_id, user_id, secret, created_at, updated_at)
1556
+ VALUES ($1, $2, $3, $4, $5, $6)
1557
+ ON CONFLICT (server_id, user_id) DO UPDATE SET
1558
+ secret = EXCLUDED.secret,
1559
+ updated_at = EXCLUDED.updated_at`, [
1560
+ credential.id,
1561
+ credential.server_id,
1562
+ credential.user_id,
1563
+ credential.secret,
1564
+ credential.created_at,
1565
+ credential.updated_at,
1566
+ ]);
1567
+ }
1568
+ async delete(serverId, userId) {
1569
+ return (changes((await this.database.query(`DELETE FROM tool_server_credentials
1570
+ WHERE server_id = $1 AND user_id = $2`, [serverId, userId])).rowCount) > 0);
1571
+ }
1572
+ }
1573
+ class PostgresToolApprovalRepository {
1574
+ constructor(database) {
1575
+ this.database = database;
1576
+ }
1577
+ async insert(approval) {
1578
+ await this.database.query(`INSERT INTO tool_approvals
1579
+ (id, user_id, session_id, server_id, tool_name, call_id,
1580
+ arguments_digest, scope, status, created_at, resolved_at, expires_at)
1581
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
1582
+ approval.id,
1583
+ approval.user_id,
1584
+ approval.session_id,
1585
+ approval.server_id,
1586
+ approval.tool_name,
1587
+ approval.call_id,
1588
+ approval.arguments_digest,
1589
+ approval.scope,
1590
+ approval.status,
1591
+ approval.created_at,
1592
+ approval.resolved_at,
1593
+ approval.expires_at,
1594
+ ]);
1595
+ }
1596
+ async findByOwner(approvalId, userId) {
1597
+ const result = await this.database.query('SELECT * FROM tool_approvals WHERE id = $1 AND user_id = $2', [approvalId, userId]);
1598
+ return result.rows[0] ? toolApproval(result.rows[0]) : null;
1599
+ }
1600
+ async findStanding(userId, serverId, toolName, sessionId) {
1601
+ const result = await this.database.query(`SELECT * FROM tool_approvals
1602
+ WHERE user_id = $1
1603
+ AND status = 'approved'
1604
+ AND tool_name = $2
1605
+ AND ((server_id IS NULL AND $3::text IS NULL) OR server_id = $3)
1606
+ AND (scope = 'always'
1607
+ OR (scope = 'session' AND session_id IS NOT NULL AND session_id = $4))
1608
+ ORDER BY resolved_at DESC
1609
+ LIMIT 1`, [userId, toolName, serverId, sessionId]);
1610
+ return result.rows[0] ? toolApproval(result.rows[0]) : null;
1611
+ }
1612
+ async resolvePending(approvalId, userId, status, scope, resolvedAt) {
1613
+ const result = await this.database.query(`UPDATE tool_approvals
1614
+ SET status = $1, scope = $2, resolved_at = $3
1615
+ WHERE id = $4 AND user_id = $5 AND status = 'pending'
1616
+ AND (expires_at IS NULL OR expires_at > $3)
1617
+ RETURNING *`, [status, scope, resolvedAt, approvalId, userId]);
1618
+ return result.rows[0] ? toolApproval(result.rows[0]) : null;
1619
+ }
1620
+ async listPendingByOwner(userId, maximum) {
1621
+ const result = await this.database.query(`SELECT * FROM tool_approvals
1622
+ WHERE user_id = $1 AND status = 'pending'
1623
+ ORDER BY created_at ASC
1624
+ LIMIT $2`, [userId, maximum]);
1625
+ return result.rows.map(toolApproval);
1626
+ }
1627
+ async listStandingByOwner(userId, maximum) {
1628
+ const result = await this.database.query(`SELECT * FROM tool_approvals
1629
+ WHERE user_id = $1 AND status = 'approved'
1630
+ AND scope IN ('session', 'always')
1631
+ ORDER BY resolved_at DESC
1632
+ LIMIT $2`, [userId, maximum]);
1633
+ return result.rows.map(toolApproval);
1634
+ }
1635
+ async expirePending(now) {
1636
+ return changes((await this.database.query(`UPDATE tool_approvals
1637
+ SET status = 'expired', resolved_at = $1
1638
+ WHERE status = 'pending' AND expires_at IS NOT NULL
1639
+ AND expires_at <= $1`, [now])).rowCount);
1640
+ }
1641
+ async deleteByOwner(approvalId, userId) {
1642
+ return (changes((await this.database.query('DELETE FROM tool_approvals WHERE id = $1 AND user_id = $2', [approvalId, userId])).rowCount) > 0);
1643
+ }
1644
+ }
1645
+ class PostgresPromptRepository {
1646
+ constructor(database) {
1647
+ this.database = database;
1648
+ }
1649
+ async listByOwner(userId, maximum) {
1650
+ const result = await this.database.query(`SELECT * FROM prompts
1651
+ WHERE user_id = $1
1652
+ ORDER BY updated_at DESC
1653
+ LIMIT $2`, [userId, maximum]);
1654
+ return result.rows.map(prompt);
1655
+ }
1656
+ async findByOwner(promptId, userId) {
1657
+ const result = await this.database.query('SELECT * FROM prompts WHERE id = $1 AND user_id = $2', [promptId, userId]);
1658
+ return result.rows[0] ? prompt(result.rows[0]) : null;
1659
+ }
1660
+ async findById(promptId) {
1661
+ const result = await this.database.query('SELECT * FROM prompts WHERE id = $1', [promptId]);
1662
+ return result.rows[0] ? prompt(result.rows[0]) : null;
1663
+ }
1664
+ async findBySlug(userId, slug) {
1665
+ const result = await this.database.query('SELECT * FROM prompts WHERE user_id = $1 AND slug = $2', [userId, slug]);
1666
+ return result.rows[0] ? prompt(result.rows[0]) : null;
1667
+ }
1668
+ async replaceWithLimit(value, maximum, archivedVersion) {
1669
+ await this.database.transaction(async (client) => {
1670
+ await lockOwner(client, value.user_id);
1671
+ const existing = await client.query('SELECT user_id FROM prompts WHERE id = $1 FOR UPDATE', [value.id]);
1672
+ if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
1673
+ throw new PersistenceResourceConflictError();
1674
+ }
1675
+ if (!existing.rows[0]) {
1676
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM prompts WHERE user_id = $1', [value.user_id]);
1677
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
1678
+ throw new PersistenceResourceLimitError('prompt', maximum);
1679
+ }
1680
+ }
1681
+ const result = await client.query(`INSERT INTO prompts
1682
+ (id, user_id, slug, title, description, content, variables, tags,
1683
+ version, created_at, updated_at)
1684
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
1685
+ ON CONFLICT (id) DO UPDATE SET
1686
+ slug = EXCLUDED.slug,
1687
+ title = EXCLUDED.title,
1688
+ description = EXCLUDED.description,
1689
+ content = EXCLUDED.content,
1690
+ variables = EXCLUDED.variables,
1691
+ tags = EXCLUDED.tags,
1692
+ version = EXCLUDED.version,
1693
+ updated_at = EXCLUDED.updated_at
1694
+ WHERE prompts.user_id = EXCLUDED.user_id`, [
1695
+ value.id,
1696
+ value.user_id,
1697
+ value.slug,
1698
+ value.title,
1699
+ value.description,
1700
+ value.content,
1701
+ value.variables,
1702
+ value.tags,
1703
+ value.version,
1704
+ value.created_at,
1705
+ value.updated_at,
1706
+ ]);
1707
+ if (result.rowCount !== 1)
1708
+ throw new PersistenceResourceConflictError();
1709
+ if (archivedVersion) {
1710
+ await client.query(`INSERT INTO prompt_versions
1711
+ (id, prompt_id, version, content, variables, created_at)
1712
+ VALUES ($1, $2, $3, $4, $5, $6)
1713
+ ON CONFLICT (prompt_id, version) DO NOTHING`, [
1714
+ archivedVersion.id,
1715
+ archivedVersion.prompt_id,
1716
+ archivedVersion.version,
1717
+ archivedVersion.content,
1718
+ archivedVersion.variables,
1719
+ archivedVersion.created_at,
1720
+ ]);
1721
+ }
1722
+ });
1723
+ }
1724
+ async listVersions(promptId, maximum) {
1725
+ const result = await this.database.query(`SELECT * FROM prompt_versions
1726
+ WHERE prompt_id = $1
1727
+ ORDER BY version DESC
1728
+ LIMIT $2`, [promptId, maximum]);
1729
+ return result.rows.map(promptVersion);
1730
+ }
1731
+ async findVersion(promptId, version) {
1732
+ const result = await this.database.query('SELECT * FROM prompt_versions WHERE prompt_id = $1 AND version = $2', [promptId, version]);
1733
+ return result.rows[0] ? promptVersion(result.rows[0]) : null;
1734
+ }
1735
+ async deleteByOwner(promptId, userId) {
1736
+ return (changes((await this.database.query('DELETE FROM prompts WHERE id = $1 AND user_id = $2', [promptId, userId])).rowCount) > 0);
1737
+ }
1738
+ }
1739
+ class PostgresSkillRepository {
1740
+ constructor(database) {
1741
+ this.database = database;
1742
+ }
1743
+ async listByOwner(userId, maximum) {
1744
+ const result = await this.database.query(`SELECT * FROM skills
1745
+ WHERE user_id = $1
1746
+ ORDER BY updated_at DESC
1747
+ LIMIT $2`, [userId, maximum]);
1748
+ return result.rows.map(skill);
1749
+ }
1750
+ async findByOwner(skillId, userId) {
1751
+ const result = await this.database.query('SELECT * FROM skills WHERE id = $1 AND user_id = $2', [skillId, userId]);
1752
+ return result.rows[0] ? skill(result.rows[0]) : null;
1753
+ }
1754
+ async findById(skillId) {
1755
+ const result = await this.database.query('SELECT * FROM skills WHERE id = $1', [skillId]);
1756
+ return result.rows[0] ? skill(result.rows[0]) : null;
1757
+ }
1758
+ async findBySlug(userId, slug) {
1759
+ const result = await this.database.query('SELECT * FROM skills WHERE user_id = $1 AND slug = $2', [userId, slug]);
1760
+ return result.rows[0] ? skill(result.rows[0]) : null;
1761
+ }
1762
+ async replaceWithLimit(value, maximum, archivedVersion) {
1763
+ await this.database.transaction(async (client) => {
1764
+ await lockOwner(client, value.user_id);
1765
+ const existing = await client.query('SELECT user_id FROM skills WHERE id = $1 FOR UPDATE', [value.id]);
1766
+ if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
1767
+ throw new PersistenceResourceConflictError();
1768
+ }
1769
+ if (!existing.rows[0]) {
1770
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM skills WHERE user_id = $1', [value.user_id]);
1771
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
1772
+ throw new PersistenceResourceLimitError('skill', maximum);
1773
+ }
1774
+ }
1775
+ const result = await client.query(`INSERT INTO skills
1776
+ (id, user_id, slug, name, description, instructions, enabled,
1777
+ version, created_at, updated_at)
1778
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
1779
+ ON CONFLICT (id) DO UPDATE SET
1780
+ slug = EXCLUDED.slug,
1781
+ name = EXCLUDED.name,
1782
+ description = EXCLUDED.description,
1783
+ instructions = EXCLUDED.instructions,
1784
+ enabled = EXCLUDED.enabled,
1785
+ version = EXCLUDED.version,
1786
+ updated_at = EXCLUDED.updated_at
1787
+ WHERE skills.user_id = EXCLUDED.user_id`, [
1788
+ value.id,
1789
+ value.user_id,
1790
+ value.slug,
1791
+ value.name,
1792
+ value.description,
1793
+ value.instructions,
1794
+ value.enabled,
1795
+ value.version,
1796
+ value.created_at,
1797
+ value.updated_at,
1798
+ ]);
1799
+ if (result.rowCount !== 1)
1800
+ throw new PersistenceResourceConflictError();
1801
+ if (archivedVersion) {
1802
+ await client.query(`INSERT INTO skill_versions
1803
+ (id, skill_id, version, instructions, created_at)
1804
+ VALUES ($1, $2, $3, $4, $5)
1805
+ ON CONFLICT (skill_id, version) DO NOTHING`, [
1806
+ archivedVersion.id,
1807
+ archivedVersion.skill_id,
1808
+ archivedVersion.version,
1809
+ archivedVersion.instructions,
1810
+ archivedVersion.created_at,
1811
+ ]);
1812
+ }
1813
+ });
1814
+ }
1815
+ async listVersions(skillId, maximum) {
1816
+ const result = await this.database.query(`SELECT * FROM skill_versions
1817
+ WHERE skill_id = $1
1818
+ ORDER BY version DESC
1819
+ LIMIT $2`, [skillId, maximum]);
1820
+ return result.rows.map(skillVersion);
1821
+ }
1822
+ async findVersion(skillId, version) {
1823
+ const result = await this.database.query('SELECT * FROM skill_versions WHERE skill_id = $1 AND version = $2', [skillId, version]);
1824
+ return result.rows[0] ? skillVersion(result.rows[0]) : null;
1825
+ }
1826
+ async deleteByOwner(skillId, userId) {
1827
+ return (changes((await this.database.query('DELETE FROM skills WHERE id = $1 AND user_id = $2', [skillId, userId])).rowCount) > 0);
1828
+ }
1829
+ }
1830
+ class PostgresSkillFileRepository {
1831
+ constructor(database) {
1832
+ this.database = database;
1833
+ }
1834
+ async listBySkill(skillId) {
1835
+ const result = await this.database.query(`SELECT * FROM skill_files
1836
+ WHERE skill_id = $1
1837
+ ORDER BY path ASC`, [skillId]);
1838
+ return result.rows.map(skillFile);
1839
+ }
1840
+ async find(skillId, path) {
1841
+ const result = await this.database.query('SELECT * FROM skill_files WHERE skill_id = $1 AND path = $2', [skillId, path]);
1842
+ return result.rows[0] ? skillFile(result.rows[0]) : null;
1843
+ }
1844
+ async upsert(file, maximumPerSkill) {
1845
+ await this.database.transaction(async (client) => {
1846
+ const existing = await client.query('SELECT id FROM skill_files WHERE skill_id = $1 AND path = $2 FOR UPDATE', [file.skill_id, file.path]);
1847
+ if (!existing.rows[0]) {
1848
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM skill_files WHERE skill_id = $1', [file.skill_id]);
1849
+ if (Number(count.rows[0]?.count || 0) >= maximumPerSkill) {
1850
+ throw new PersistenceResourceLimitError('skill-file', maximumPerSkill);
1851
+ }
1852
+ }
1853
+ await client.query(`INSERT INTO skill_files
1854
+ (id, skill_id, path, content, size, created_at, updated_at)
1855
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
1856
+ ON CONFLICT (skill_id, path) DO UPDATE SET
1857
+ content = EXCLUDED.content,
1858
+ size = EXCLUDED.size,
1859
+ updated_at = EXCLUDED.updated_at`, [
1860
+ file.id,
1861
+ file.skill_id,
1862
+ file.path,
1863
+ file.content,
1864
+ file.size,
1865
+ file.created_at,
1866
+ file.updated_at,
1867
+ ]);
1868
+ });
1869
+ }
1870
+ async replaceAllForSkill(skillId, files) {
1871
+ await this.database.transaction(async (client) => {
1872
+ await client.query('DELETE FROM skill_files WHERE skill_id = $1', [
1873
+ skillId,
1874
+ ]);
1875
+ for (const file of files) {
1876
+ if (file.skill_id !== skillId) {
1877
+ throw new Error('A skill file does not belong to its skill');
1878
+ }
1879
+ await client.query(`INSERT INTO skill_files
1880
+ (id, skill_id, path, content, size, created_at, updated_at)
1881
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
1882
+ file.id,
1883
+ file.skill_id,
1884
+ file.path,
1885
+ file.content,
1886
+ file.size,
1887
+ file.created_at,
1888
+ file.updated_at,
1889
+ ]);
1890
+ }
1891
+ });
1892
+ }
1893
+ async delete(skillId, path) {
1894
+ return (changes((await this.database.query('DELETE FROM skill_files WHERE skill_id = $1 AND path = $2', [skillId, path])).rowCount) > 0);
1895
+ }
1896
+ }
1897
+ class PostgresSessionFolderRepository {
1898
+ constructor(database) {
1899
+ this.database = database;
1900
+ }
1901
+ async listByOwner(userId, maximum) {
1902
+ const result = await this.database.query(`SELECT * FROM session_folders
1903
+ WHERE user_id = $1
1904
+ ORDER BY lower(name), name
1905
+ LIMIT $2`, [userId, maximum]);
595
1906
  return result.rows.map(namedResource);
596
1907
  }
597
1908
  async replaceWithLimit(folder, maximum) {
@@ -736,6 +2047,10 @@ const ARCHIVE_TABLES = {
736
2047
  'knowledge-collection': 'knowledge_collections',
737
2048
  document: 'documents',
738
2049
  persona: 'personas',
2050
+ prompt: 'prompts',
2051
+ skill: 'skills',
2052
+ 'tool-server': 'tool_servers',
2053
+ calendar: 'calendars',
739
2054
  };
740
2055
  class PostgresDataArchiveRepository {
741
2056
  constructor(database) {
@@ -1027,17 +2342,337 @@ class PostgresDataArchiveRepository {
1027
2342
  });
1028
2343
  }
1029
2344
  }
2345
+ const modelTariff = (row) => ({
2346
+ ...row,
2347
+ input_per_million: row.input_per_million === null ? null : Number(row.input_per_million),
2348
+ output_per_million: row.output_per_million === null ? null : Number(row.output_per_million),
2349
+ unit_price: row.unit_price === null ? null : Number(row.unit_price),
2350
+ effective_from: number(row.effective_from, 'tariff effective_from'),
2351
+ created_at: number(row.created_at, 'tariff created_at'),
2352
+ });
2353
+ const usageBudget = (row) => ({
2354
+ ...row,
2355
+ amount_usd: Number(row.amount_usd),
2356
+ created_at: number(row.created_at, 'budget created_at'),
2357
+ updated_at: number(row.updated_at, 'budget updated_at'),
2358
+ });
2359
+ const messageFeedback = (row) => ({
2360
+ ...row,
2361
+ rating: number(row.rating, 'feedback rating'),
2362
+ created_at: number(row.created_at, 'feedback created_at'),
2363
+ updated_at: number(row.updated_at, 'feedback updated_at'),
2364
+ });
2365
+ const arenaVote = (row) => ({
2366
+ ...row,
2367
+ created_at: number(row.created_at, 'arena vote created_at'),
2368
+ });
2369
+ const evalSet = (row) => ({
2370
+ ...row,
2371
+ created_at: number(row.created_at, 'eval set created_at'),
2372
+ updated_at: number(row.updated_at, 'eval set updated_at'),
2373
+ });
2374
+ const evalRun = (row) => ({
2375
+ ...row,
2376
+ created_at: number(row.created_at, 'eval run created_at'),
2377
+ updated_at: number(row.updated_at, 'eval run updated_at'),
2378
+ completed_at: row.completed_at === null
2379
+ ? null
2380
+ : number(row.completed_at, 'eval run completed_at'),
2381
+ });
2382
+ class PostgresModelTariffRepository {
2383
+ constructor(database) {
2384
+ this.database = database;
2385
+ }
2386
+ async listAll(maximum) {
2387
+ const result = await this.database.query(`SELECT * FROM model_tariffs
2388
+ ORDER BY plugin_id ASC, model ASC, effective_from DESC
2389
+ LIMIT $1`, [maximum]);
2390
+ return result.rows.map(modelTariff);
2391
+ }
2392
+ async insert(tariff) {
2393
+ await this.database.query(`INSERT INTO model_tariffs
2394
+ (id, plugin_id, model, input_per_million, output_per_million,
2395
+ unit_price, currency, effective_from, created_by, created_at)
2396
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
2397
+ tariff.id,
2398
+ tariff.plugin_id,
2399
+ tariff.model,
2400
+ tariff.input_per_million,
2401
+ tariff.output_per_million,
2402
+ tariff.unit_price,
2403
+ tariff.currency,
2404
+ tariff.effective_from,
2405
+ tariff.created_by,
2406
+ tariff.created_at,
2407
+ ]);
2408
+ }
2409
+ async deleteById(tariffId) {
2410
+ const result = await this.database.query('DELETE FROM model_tariffs WHERE id = $1', [tariffId]);
2411
+ return changes(result.rowCount) > 0;
2412
+ }
2413
+ }
2414
+ class PostgresUsageBudgetRepository {
2415
+ constructor(database) {
2416
+ this.database = database;
2417
+ }
2418
+ async listAll(maximum) {
2419
+ const result = await this.database.query(`SELECT * FROM usage_budgets
2420
+ ORDER BY created_at ASC, id ASC
2421
+ LIMIT $1`, [maximum]);
2422
+ return result.rows.map(usageBudget);
2423
+ }
2424
+ async findById(budgetId) {
2425
+ const result = await this.database.query('SELECT * FROM usage_budgets WHERE id = $1', [budgetId]);
2426
+ return result.rows[0] ? usageBudget(result.rows[0]) : null;
2427
+ }
2428
+ async replace(budget) {
2429
+ await this.database.query(`INSERT INTO usage_budgets
2430
+ (id, name, principal_type, principal_id, period, amount_usd,
2431
+ mode, created_by, created_at, updated_at)
2432
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
2433
+ ON CONFLICT (id) DO UPDATE SET
2434
+ name = EXCLUDED.name,
2435
+ principal_type = EXCLUDED.principal_type,
2436
+ principal_id = EXCLUDED.principal_id,
2437
+ period = EXCLUDED.period,
2438
+ amount_usd = EXCLUDED.amount_usd,
2439
+ mode = EXCLUDED.mode,
2440
+ updated_at = EXCLUDED.updated_at`, [
2441
+ budget.id,
2442
+ budget.name,
2443
+ budget.principal_type,
2444
+ budget.principal_id,
2445
+ budget.period,
2446
+ budget.amount_usd,
2447
+ budget.mode,
2448
+ budget.created_by,
2449
+ budget.created_at,
2450
+ budget.updated_at,
2451
+ ]);
2452
+ }
2453
+ async deleteById(budgetId) {
2454
+ const result = await this.database.query('DELETE FROM usage_budgets WHERE id = $1', [budgetId]);
2455
+ return changes(result.rowCount) > 0;
2456
+ }
2457
+ }
2458
+ class PostgresMessageFeedbackRepository {
2459
+ constructor(database) {
2460
+ this.database = database;
2461
+ }
2462
+ async upsertByMessage(feedback) {
2463
+ await this.database.query(`INSERT INTO message_feedback
2464
+ (id, user_id, session_id, message_id, rating, tags, comment,
2465
+ model, plugin_id, snapshot, created_at, updated_at)
2466
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
2467
+ ON CONFLICT (user_id, message_id) DO UPDATE SET
2468
+ rating = EXCLUDED.rating,
2469
+ tags = EXCLUDED.tags,
2470
+ comment = EXCLUDED.comment,
2471
+ model = EXCLUDED.model,
2472
+ plugin_id = EXCLUDED.plugin_id,
2473
+ snapshot = EXCLUDED.snapshot,
2474
+ updated_at = EXCLUDED.updated_at`, [
2475
+ feedback.id,
2476
+ feedback.user_id,
2477
+ feedback.session_id,
2478
+ feedback.message_id,
2479
+ feedback.rating,
2480
+ feedback.tags,
2481
+ feedback.comment,
2482
+ feedback.model,
2483
+ feedback.plugin_id,
2484
+ feedback.snapshot,
2485
+ feedback.created_at,
2486
+ feedback.updated_at,
2487
+ ]);
2488
+ }
2489
+ async deleteByMessage(userId, messageId) {
2490
+ const result = await this.database.query('DELETE FROM message_feedback WHERE user_id = $1 AND message_id = $2', [userId, messageId]);
2491
+ return changes(result.rowCount) > 0;
2492
+ }
2493
+ async listByOwner(userId, maximum) {
2494
+ const result = await this.database.query(`SELECT * FROM message_feedback
2495
+ WHERE user_id = $1
2496
+ ORDER BY created_at DESC, id DESC
2497
+ LIMIT $2`, [userId, maximum]);
2498
+ return result.rows.map(messageFeedback);
2499
+ }
2500
+ async listAll(maximum) {
2501
+ const result = await this.database.query(`SELECT * FROM message_feedback
2502
+ ORDER BY created_at DESC, id DESC
2503
+ LIMIT $1`, [maximum]);
2504
+ return result.rows.map(messageFeedback);
2505
+ }
2506
+ }
2507
+ class PostgresArenaVoteRepository {
2508
+ constructor(database) {
2509
+ this.database = database;
2510
+ }
2511
+ async insertOnce(vote) {
2512
+ const result = await this.database.query(`INSERT INTO arena_votes
2513
+ (id, user_id, compare_group, model_a, model_b, winner, created_at)
2514
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
2515
+ ON CONFLICT (user_id, compare_group) DO NOTHING`, [
2516
+ vote.id,
2517
+ vote.user_id,
2518
+ vote.compare_group,
2519
+ vote.model_a,
2520
+ vote.model_b,
2521
+ vote.winner,
2522
+ vote.created_at,
2523
+ ]);
2524
+ return changes(result.rowCount) > 0;
2525
+ }
2526
+ async listAllOrdered(maximum) {
2527
+ const result = await this.database.query(`SELECT * FROM arena_votes
2528
+ ORDER BY created_at ASC, id ASC
2529
+ LIMIT $1`, [maximum]);
2530
+ return result.rows.map(arenaVote);
2531
+ }
2532
+ }
2533
+ class PostgresEvalSetRepository {
2534
+ constructor(database) {
2535
+ this.database = database;
2536
+ }
2537
+ async listByOwner(userId, maximum) {
2538
+ const result = await this.database.query(`SELECT * FROM eval_sets
2539
+ WHERE user_id = $1
2540
+ ORDER BY updated_at DESC, id DESC
2541
+ LIMIT $2`, [userId, maximum]);
2542
+ return result.rows.map(evalSet);
2543
+ }
2544
+ async findByOwner(setId, userId) {
2545
+ const result = await this.database.query('SELECT * FROM eval_sets WHERE id = $1 AND user_id = $2', [setId, userId]);
2546
+ return result.rows[0] ? evalSet(result.rows[0]) : null;
2547
+ }
2548
+ async replaceWithLimit(value, maximum) {
2549
+ await this.database.transaction(async (client) => {
2550
+ await lockOwner(client, value.user_id);
2551
+ const existing = await client.query('SELECT user_id FROM eval_sets WHERE id = $1 FOR UPDATE', [value.id]);
2552
+ if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
2553
+ throw new PersistenceResourceConflictError();
2554
+ }
2555
+ if (!existing.rows[0]) {
2556
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM eval_sets WHERE user_id = $1', [value.user_id]);
2557
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
2558
+ throw new PersistenceResourceLimitError('eval-set', maximum);
2559
+ }
2560
+ }
2561
+ const result = await client.query(`INSERT INTO eval_sets
2562
+ (id, user_id, name, description, items, created_at, updated_at)
2563
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
2564
+ ON CONFLICT (id) DO UPDATE SET
2565
+ name = EXCLUDED.name,
2566
+ description = EXCLUDED.description,
2567
+ items = EXCLUDED.items,
2568
+ updated_at = EXCLUDED.updated_at
2569
+ WHERE eval_sets.user_id = EXCLUDED.user_id`, [
2570
+ value.id,
2571
+ value.user_id,
2572
+ value.name,
2573
+ value.description,
2574
+ value.items,
2575
+ value.created_at,
2576
+ value.updated_at,
2577
+ ]);
2578
+ if (result.rowCount !== 1)
2579
+ throw new PersistenceResourceConflictError();
2580
+ });
2581
+ }
2582
+ async deleteByOwner(setId, userId) {
2583
+ const result = await this.database.query('DELETE FROM eval_sets WHERE id = $1 AND user_id = $2', [setId, userId]);
2584
+ return changes(result.rowCount) > 0;
2585
+ }
2586
+ }
2587
+ class PostgresEvalRunRepository {
2588
+ constructor(database) {
2589
+ this.database = database;
2590
+ }
2591
+ async listByOwner(userId, maximum) {
2592
+ const result = await this.database.query(`SELECT * FROM eval_runs
2593
+ WHERE user_id = $1
2594
+ ORDER BY created_at DESC, id DESC
2595
+ LIMIT $2`, [userId, maximum]);
2596
+ return result.rows.map(evalRun);
2597
+ }
2598
+ async listBySet(setId, userId, maximum) {
2599
+ const result = await this.database.query(`SELECT * FROM eval_runs
2600
+ WHERE set_id = $1 AND user_id = $2
2601
+ ORDER BY created_at DESC, id DESC
2602
+ LIMIT $3`, [setId, userId, maximum]);
2603
+ return result.rows.map(evalRun);
2604
+ }
2605
+ async findByOwner(runId, userId) {
2606
+ const result = await this.database.query('SELECT * FROM eval_runs WHERE id = $1 AND user_id = $2', [runId, userId]);
2607
+ return result.rows[0] ? evalRun(result.rows[0]) : null;
2608
+ }
2609
+ async insert(run) {
2610
+ await this.database.query(`INSERT INTO eval_runs
2611
+ (id, set_id, user_id, label, plugin_id, model, status, results,
2612
+ error, created_at, updated_at, completed_at)
2613
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
2614
+ run.id,
2615
+ run.set_id,
2616
+ run.user_id,
2617
+ run.label,
2618
+ run.plugin_id,
2619
+ run.model,
2620
+ run.status,
2621
+ run.results,
2622
+ run.error,
2623
+ run.created_at,
2624
+ run.updated_at,
2625
+ run.completed_at,
2626
+ ]);
2627
+ }
2628
+ async update(runId, userId, updates) {
2629
+ const result = await this.database.query(`UPDATE eval_runs SET
2630
+ status = $1,
2631
+ results = COALESCE($2, results),
2632
+ error = $3,
2633
+ updated_at = $4,
2634
+ completed_at = COALESCE($5, completed_at)
2635
+ WHERE id = $6 AND user_id = $7`, [
2636
+ updates.status,
2637
+ updates.results ?? null,
2638
+ updates.error ?? null,
2639
+ updates.updated_at,
2640
+ updates.completed_at ?? null,
2641
+ runId,
2642
+ userId,
2643
+ ]);
2644
+ return changes(result.rowCount) > 0;
2645
+ }
2646
+ }
1030
2647
  export const createPostgresResourceRepositories = (database) => ({
1031
2648
  chatSessions: new PostgresChatSessionRepository(database),
1032
2649
  knowledgeCollections: new PostgresKnowledgeCollectionRepository(database),
1033
2650
  notes: new PostgresNoteRepository(database),
2651
+ calendars: new PostgresCalendarRepository(database),
1034
2652
  calendarEvents: new PostgresCalendarEventRepository(database),
2653
+ channels: new PostgresChannelRepository(database),
2654
+ channelMessages: new PostgresChannelMessageRepository(database),
2655
+ notifications: new PostgresNotificationRepository(database),
2656
+ webhookTargets: new PostgresWebhookTargetRepository(database),
1035
2657
  automations: new PostgresAutomationRepository(database),
1036
2658
  automationRuns: new PostgresAutomationRunRepository(database),
2659
+ toolServers: new PostgresToolServerRepository(database),
2660
+ toolServerTools: new PostgresToolServerToolRepository(database),
2661
+ toolServerCredentials: new PostgresToolServerCredentialRepository(database),
2662
+ toolApprovals: new PostgresToolApprovalRepository(database),
2663
+ prompts: new PostgresPromptRepository(database),
2664
+ skills: new PostgresSkillRepository(database),
2665
+ skillFiles: new PostgresSkillFileRepository(database),
1037
2666
  sessionFolders: new PostgresSessionFolderRepository(database),
1038
2667
  preferences: new PostgresPreferenceRepository(database),
1039
2668
  systemSettings: new PostgresSystemSettingRepository(database),
1040
2669
  archive: new PostgresDataArchiveRepository(database),
2670
+ modelTariffs: new PostgresModelTariffRepository(database),
2671
+ usageBudgets: new PostgresUsageBudgetRepository(database),
2672
+ messageFeedback: new PostgresMessageFeedbackRepository(database),
2673
+ arenaVotes: new PostgresArenaVoteRepository(database),
2674
+ evalSets: new PostgresEvalSetRepository(database),
2675
+ evalRuns: new PostgresEvalRunRepository(database),
1041
2676
  });
1042
2677
  /** Create transaction-scoped repositories over one pinned pooled client. */
1043
2678
  export const createPostgresTransactionalResourceRepositories = (database, client) => {