libre-webui 0.24.1 → 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 (543) hide show
  1. package/README.md +36 -15
  2. package/backend/dist/index.d.ts.map +1 -1
  3. package/backend/dist/index.js +35 -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 +12 -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/postgresPersonalAutomationsMigration.d.ts +8 -0
  51. package/backend/dist/persistence/postgresPersonalAutomationsMigration.d.ts.map +1 -0
  52. package/backend/dist/persistence/postgresPersonalAutomationsMigration.js +98 -0
  53. package/backend/dist/persistence/postgresPersonalAutomationsMigration.js.map +1 -0
  54. package/backend/dist/persistence/postgresResourceRepositories.d.ts.map +1 -1
  55. package/backend/dist/persistence/postgresResourceRepositories.js +1972 -63
  56. package/backend/dist/persistence/postgresResourceRepositories.js.map +1 -1
  57. package/backend/dist/persistence/postgresSkillFilesMigration.d.ts +9 -0
  58. package/backend/dist/persistence/postgresSkillFilesMigration.d.ts.map +1 -0
  59. package/backend/dist/persistence/postgresSkillFilesMigration.js +50 -0
  60. package/backend/dist/persistence/postgresSkillFilesMigration.js.map +1 -0
  61. package/backend/dist/persistence/postgresTeamCollaborationMigration.d.ts +10 -0
  62. package/backend/dist/persistence/postgresTeamCollaborationMigration.d.ts.map +1 -0
  63. package/backend/dist/persistence/postgresTeamCollaborationMigration.js +161 -0
  64. package/backend/dist/persistence/postgresTeamCollaborationMigration.js.map +1 -0
  65. package/backend/dist/persistence/resourceTypes.d.ts +622 -3
  66. package/backend/dist/persistence/resourceTypes.d.ts.map +1 -1
  67. package/backend/dist/persistence/resourceTypes.js.map +1 -1
  68. package/backend/dist/persistence/sqliteExtensionRepositories.d.ts.map +1 -1
  69. package/backend/dist/persistence/sqliteExtensionRepositories.js +26 -4
  70. package/backend/dist/persistence/sqliteExtensionRepositories.js.map +1 -1
  71. package/backend/dist/persistence/sqliteMigrations.d.ts +10 -0
  72. package/backend/dist/persistence/sqliteMigrations.d.ts.map +1 -1
  73. package/backend/dist/persistence/sqliteMigrations.js +1396 -48
  74. package/backend/dist/persistence/sqliteMigrations.js.map +1 -1
  75. package/backend/dist/persistence/sqliteResourceRepositories.d.ts.map +1 -1
  76. package/backend/dist/persistence/sqliteResourceRepositories.js +1680 -58
  77. package/backend/dist/persistence/sqliteResourceRepositories.js.map +1 -1
  78. package/backend/dist/persistence/sqliteToPostgresMigration.d.ts.map +1 -1
  79. package/backend/dist/persistence/sqliteToPostgresMigration.js +297 -2
  80. package/backend/dist/persistence/sqliteToPostgresMigration.js.map +1 -1
  81. package/backend/dist/platform/jobs/domainJobContracts.d.ts +11 -0
  82. package/backend/dist/platform/jobs/domainJobContracts.d.ts.map +1 -1
  83. package/backend/dist/platform/jobs/domainJobContracts.js +11 -0
  84. package/backend/dist/platform/jobs/domainJobContracts.js.map +1 -1
  85. package/backend/dist/platform/jobs/domainJobHandlers.d.ts.map +1 -1
  86. package/backend/dist/platform/jobs/domainJobHandlers.js +409 -2
  87. package/backend/dist/platform/jobs/domainJobHandlers.js.map +1 -1
  88. package/backend/dist/platform/jobs/embeddedDurableJobWorker.d.ts.map +1 -1
  89. package/backend/dist/platform/jobs/embeddedDurableJobWorker.js +4 -2
  90. package/backend/dist/platform/jobs/embeddedDurableJobWorker.js.map +1 -1
  91. package/backend/dist/platform/jobs/postgresDurableJobWorker.d.ts.map +1 -1
  92. package/backend/dist/platform/jobs/postgresDurableJobWorker.js +4 -2
  93. package/backend/dist/platform/jobs/postgresDurableJobWorker.js.map +1 -1
  94. package/backend/dist/platform/storage/pgVectorStore.d.ts +2 -1
  95. package/backend/dist/platform/storage/pgVectorStore.d.ts.map +1 -1
  96. package/backend/dist/platform/storage/pgVectorStore.js +51 -0
  97. package/backend/dist/platform/storage/pgVectorStore.js.map +1 -1
  98. package/backend/dist/platform/storage/platformDomainRepositories.d.ts +8 -2
  99. package/backend/dist/platform/storage/platformDomainRepositories.d.ts.map +1 -1
  100. package/backend/dist/platform/storage/postgresPlatformDomainRepositories.d.ts.map +1 -1
  101. package/backend/dist/platform/storage/postgresPlatformDomainRepositories.js +25 -4
  102. package/backend/dist/platform/storage/postgresPlatformDomainRepositories.js.map +1 -1
  103. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.d.ts +2 -1
  104. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.d.ts.map +1 -1
  105. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.js +35 -0
  106. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.js.map +1 -1
  107. package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.d.ts.map +1 -1
  108. package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.js +26 -3
  109. package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.js.map +1 -1
  110. package/backend/dist/platform/storage/vectorStore.d.ts +14 -0
  111. package/backend/dist/platform/storage/vectorStore.d.ts.map +1 -1
  112. package/backend/dist/platform/storage/vectorStore.js.map +1 -1
  113. package/backend/dist/routes/access.d.ts.map +1 -1
  114. package/backend/dist/routes/access.js +84 -3
  115. package/backend/dist/routes/access.js.map +1 -1
  116. package/backend/dist/routes/automations.d.ts +3 -0
  117. package/backend/dist/routes/automations.d.ts.map +1 -0
  118. package/backend/dist/routes/automations.js +232 -0
  119. package/backend/dist/routes/automations.js.map +1 -0
  120. package/backend/dist/routes/calendar.d.ts +3 -0
  121. package/backend/dist/routes/calendar.d.ts.map +1 -0
  122. package/backend/dist/routes/calendar.js +309 -0
  123. package/backend/dist/routes/calendar.js.map +1 -0
  124. package/backend/dist/routes/channels.d.ts +3 -0
  125. package/backend/dist/routes/channels.d.ts.map +1 -0
  126. package/backend/dist/routes/channels.js +447 -0
  127. package/backend/dist/routes/channels.js.map +1 -0
  128. package/backend/dist/routes/chat.d.ts.map +1 -1
  129. package/backend/dist/routes/chat.js +237 -10
  130. package/backend/dist/routes/chat.js.map +1 -1
  131. package/backend/dist/routes/costs.d.ts +3 -0
  132. package/backend/dist/routes/costs.d.ts.map +1 -0
  133. package/backend/dist/routes/costs.js +191 -0
  134. package/backend/dist/routes/costs.js.map +1 -0
  135. package/backend/dist/routes/documents.d.ts.map +1 -1
  136. package/backend/dist/routes/documents.js +64 -21
  137. package/backend/dist/routes/documents.js.map +1 -1
  138. package/backend/dist/routes/evaluations.d.ts +3 -0
  139. package/backend/dist/routes/evaluations.d.ts.map +1 -0
  140. package/backend/dist/routes/evaluations.js +352 -0
  141. package/backend/dist/routes/evaluations.js.map +1 -0
  142. package/backend/dist/routes/imageGen.d.ts.map +1 -1
  143. package/backend/dist/routes/imageGen.js +2 -1
  144. package/backend/dist/routes/imageGen.js.map +1 -1
  145. package/backend/dist/routes/media.d.ts.map +1 -1
  146. package/backend/dist/routes/media.js +228 -7
  147. package/backend/dist/routes/media.js.map +1 -1
  148. package/backend/dist/routes/notes.d.ts.map +1 -1
  149. package/backend/dist/routes/notes.js +149 -36
  150. package/backend/dist/routes/notes.js.map +1 -1
  151. package/backend/dist/routes/notifications.d.ts +3 -0
  152. package/backend/dist/routes/notifications.d.ts.map +1 -0
  153. package/backend/dist/routes/notifications.js +213 -0
  154. package/backend/dist/routes/notifications.js.map +1 -0
  155. package/backend/dist/routes/ollama.d.ts.map +1 -1
  156. package/backend/dist/routes/ollama.js +4 -1
  157. package/backend/dist/routes/ollama.js.map +1 -1
  158. package/backend/dist/routes/openaiCompat.d.ts +3 -0
  159. package/backend/dist/routes/openaiCompat.d.ts.map +1 -0
  160. package/backend/dist/routes/openaiCompat.js +314 -0
  161. package/backend/dist/routes/openaiCompat.js.map +1 -0
  162. package/backend/dist/routes/personas.js +1 -1
  163. package/backend/dist/routes/personas.js.map +1 -1
  164. package/backend/dist/routes/prompts.d.ts +3 -0
  165. package/backend/dist/routes/prompts.d.ts.map +1 -0
  166. package/backend/dist/routes/prompts.js +173 -0
  167. package/backend/dist/routes/prompts.js.map +1 -0
  168. package/backend/dist/routes/search.d.ts.map +1 -1
  169. package/backend/dist/routes/search.js +34 -4
  170. package/backend/dist/routes/search.js.map +1 -1
  171. package/backend/dist/routes/skills.d.ts +3 -0
  172. package/backend/dist/routes/skills.d.ts.map +1 -0
  173. package/backend/dist/routes/skills.js +246 -0
  174. package/backend/dist/routes/skills.js.map +1 -0
  175. package/backend/dist/routes/stt.d.ts.map +1 -1
  176. package/backend/dist/routes/stt.js +2 -1
  177. package/backend/dist/routes/stt.js.map +1 -1
  178. package/backend/dist/routes/tools.d.ts +3 -0
  179. package/backend/dist/routes/tools.d.ts.map +1 -0
  180. package/backend/dist/routes/tools.js +359 -0
  181. package/backend/dist/routes/tools.js.map +1 -0
  182. package/backend/dist/routes/tts.d.ts.map +1 -1
  183. package/backend/dist/routes/tts.js +63 -8
  184. package/backend/dist/routes/tts.js.map +1 -1
  185. package/backend/dist/routes/voiceAccess.d.ts +3 -0
  186. package/backend/dist/routes/voiceAccess.d.ts.map +1 -0
  187. package/backend/dist/routes/voiceAccess.js +102 -0
  188. package/backend/dist/routes/voiceAccess.js.map +1 -0
  189. package/backend/dist/services/apiTokenService.d.ts +2 -2
  190. package/backend/dist/services/apiTokenService.d.ts.map +1 -1
  191. package/backend/dist/services/apiTokenService.js +7 -0
  192. package/backend/dist/services/apiTokenService.js.map +1 -1
  193. package/backend/dist/services/authorizationService.d.ts +8 -4
  194. package/backend/dist/services/authorizationService.d.ts.map +1 -1
  195. package/backend/dist/services/authorizationService.js +31 -1
  196. package/backend/dist/services/authorizationService.js.map +1 -1
  197. package/backend/dist/services/automationSchedulerService.d.ts +28 -0
  198. package/backend/dist/services/automationSchedulerService.d.ts.map +1 -0
  199. package/backend/dist/services/automationSchedulerService.js +221 -0
  200. package/backend/dist/services/automationSchedulerService.js.map +1 -0
  201. package/backend/dist/services/automationService.d.ts +56 -0
  202. package/backend/dist/services/automationService.d.ts.map +1 -0
  203. package/backend/dist/services/automationService.js +247 -0
  204. package/backend/dist/services/automationService.js.map +1 -0
  205. package/backend/dist/services/builtinToolsService.d.ts +20 -0
  206. package/backend/dist/services/builtinToolsService.d.ts.map +1 -0
  207. package/backend/dist/services/builtinToolsService.js +661 -0
  208. package/backend/dist/services/builtinToolsService.js.map +1 -0
  209. package/backend/dist/services/calendarService.d.ts +74 -0
  210. package/backend/dist/services/calendarService.d.ts.map +1 -0
  211. package/backend/dist/services/calendarService.js +339 -0
  212. package/backend/dist/services/calendarService.js.map +1 -0
  213. package/backend/dist/services/channelService.d.ts +195 -0
  214. package/backend/dist/services/channelService.d.ts.map +1 -0
  215. package/backend/dist/services/channelService.js +1057 -0
  216. package/backend/dist/services/channelService.js.map +1 -0
  217. package/backend/dist/services/chatGenerationService.d.ts.map +1 -1
  218. package/backend/dist/services/chatGenerationService.js +7 -1
  219. package/backend/dist/services/chatGenerationService.js.map +1 -1
  220. package/backend/dist/services/chatRequestService.d.ts.map +1 -1
  221. package/backend/dist/services/chatRequestService.js +29 -1
  222. package/backend/dist/services/chatRequestService.js.map +1 -1
  223. package/backend/dist/services/chatService.d.ts +62 -1
  224. package/backend/dist/services/chatService.d.ts.map +1 -1
  225. package/backend/dist/services/chatService.js +242 -1
  226. package/backend/dist/services/chatService.js.map +1 -1
  227. package/backend/dist/services/chatToolRuntimeService.d.ts +55 -0
  228. package/backend/dist/services/chatToolRuntimeService.d.ts.map +1 -0
  229. package/backend/dist/services/chatToolRuntimeService.js +389 -0
  230. package/backend/dist/services/chatToolRuntimeService.js.map +1 -0
  231. package/backend/dist/services/costGovernanceService.d.ts +102 -0
  232. package/backend/dist/services/costGovernanceService.d.ts.map +1 -0
  233. package/backend/dist/services/costGovernanceService.js +463 -0
  234. package/backend/dist/services/costGovernanceService.js.map +1 -0
  235. package/backend/dist/services/dataArchiveService.d.ts +6 -0
  236. package/backend/dist/services/dataArchiveService.d.ts.map +1 -1
  237. package/backend/dist/services/dataArchiveService.js +23 -4
  238. package/backend/dist/services/dataArchiveService.js.map +1 -1
  239. package/backend/dist/services/documentService.d.ts +58 -1
  240. package/backend/dist/services/documentService.d.ts.map +1 -1
  241. package/backend/dist/services/documentService.js +289 -84
  242. package/backend/dist/services/documentService.js.map +1 -1
  243. package/backend/dist/services/durableChatGenerationService.d.ts +9 -0
  244. package/backend/dist/services/durableChatGenerationService.d.ts.map +1 -1
  245. package/backend/dist/services/durableChatGenerationService.js +160 -13
  246. package/backend/dist/services/durableChatGenerationService.js.map +1 -1
  247. package/backend/dist/services/evaluationService.d.ts +111 -0
  248. package/backend/dist/services/evaluationService.d.ts.map +1 -0
  249. package/backend/dist/services/evaluationService.js +403 -0
  250. package/backend/dist/services/evaluationService.js.map +1 -0
  251. package/backend/dist/services/galleryService.d.ts +6 -0
  252. package/backend/dist/services/galleryService.d.ts.map +1 -1
  253. package/backend/dist/services/galleryService.js +26 -0
  254. package/backend/dist/services/galleryService.js.map +1 -1
  255. package/backend/dist/services/mcpClientService.d.ts +29 -0
  256. package/backend/dist/services/mcpClientService.d.ts.map +1 -0
  257. package/backend/dist/services/mcpClientService.js +189 -0
  258. package/backend/dist/services/mcpClientService.js.map +1 -0
  259. package/backend/dist/services/noteService.d.ts +71 -0
  260. package/backend/dist/services/noteService.d.ts.map +1 -0
  261. package/backend/dist/services/noteService.js +397 -0
  262. package/backend/dist/services/noteService.js.map +1 -0
  263. package/backend/dist/services/notificationService.d.ts +55 -0
  264. package/backend/dist/services/notificationService.d.ts.map +1 -0
  265. package/backend/dist/services/notificationService.js +308 -0
  266. package/backend/dist/services/notificationService.js.map +1 -0
  267. package/backend/dist/services/ollamaService.d.ts.map +1 -1
  268. package/backend/dist/services/ollamaService.js +3 -1
  269. package/backend/dist/services/ollamaService.js.map +1 -1
  270. package/backend/dist/services/openApiToolService.d.ts +38 -0
  271. package/backend/dist/services/openApiToolService.d.ts.map +1 -0
  272. package/backend/dist/services/openApiToolService.js +254 -0
  273. package/backend/dist/services/openApiToolService.js.map +1 -0
  274. package/backend/dist/services/personaService.d.ts +18 -2
  275. package/backend/dist/services/personaService.d.ts.map +1 -1
  276. package/backend/dist/services/personaService.js +113 -4
  277. package/backend/dist/services/personaService.js.map +1 -1
  278. package/backend/dist/services/pluginImageGenerationService.d.ts +19 -0
  279. package/backend/dist/services/pluginImageGenerationService.d.ts.map +1 -1
  280. package/backend/dist/services/pluginImageGenerationService.js +121 -0
  281. package/backend/dist/services/pluginImageGenerationService.js.map +1 -1
  282. package/backend/dist/services/pluginService.d.ts +7 -0
  283. package/backend/dist/services/pluginService.d.ts.map +1 -1
  284. package/backend/dist/services/pluginService.js +8 -2
  285. package/backend/dist/services/pluginService.js.map +1 -1
  286. package/backend/dist/services/promptService.d.ts +74 -0
  287. package/backend/dist/services/promptService.d.ts.map +1 -0
  288. package/backend/dist/services/promptService.js +513 -0
  289. package/backend/dist/services/promptService.js.map +1 -0
  290. package/backend/dist/services/resourceGrantService.d.ts.map +1 -1
  291. package/backend/dist/services/resourceGrantService.js +62 -17
  292. package/backend/dist/services/resourceGrantService.js.map +1 -1
  293. package/backend/dist/services/searchService.d.ts +29 -0
  294. package/backend/dist/services/searchService.d.ts.map +1 -0
  295. package/backend/dist/services/searchService.js +0 -0
  296. package/backend/dist/services/searchService.js.map +1 -0
  297. package/backend/dist/services/securityAuditService.d.ts.map +1 -1
  298. package/backend/dist/services/securityAuditService.js +2 -1
  299. package/backend/dist/services/securityAuditService.js.map +1 -1
  300. package/backend/dist/services/sharedResourceAccess.d.ts +25 -0
  301. package/backend/dist/services/sharedResourceAccess.d.ts.map +1 -0
  302. package/backend/dist/services/sharedResourceAccess.js +59 -0
  303. package/backend/dist/services/sharedResourceAccess.js.map +1 -0
  304. package/backend/dist/services/skillService.d.ts +137 -0
  305. package/backend/dist/services/skillService.d.ts.map +1 -0
  306. package/backend/dist/services/skillService.js +852 -0
  307. package/backend/dist/services/skillService.js.map +1 -0
  308. package/backend/dist/services/titleGenerationService.d.ts.map +1 -1
  309. package/backend/dist/services/titleGenerationService.js +5 -1
  310. package/backend/dist/services/titleGenerationService.js.map +1 -1
  311. package/backend/dist/services/toolAccessService.d.ts +9 -0
  312. package/backend/dist/services/toolAccessService.d.ts.map +1 -0
  313. package/backend/dist/services/toolAccessService.js +60 -0
  314. package/backend/dist/services/toolAccessService.js.map +1 -0
  315. package/backend/dist/services/toolApprovalService.d.ts +34 -0
  316. package/backend/dist/services/toolApprovalService.d.ts.map +1 -0
  317. package/backend/dist/services/toolApprovalService.js +188 -0
  318. package/backend/dist/services/toolApprovalService.js.map +1 -0
  319. package/backend/dist/services/toolGatewayService.d.ts +53 -0
  320. package/backend/dist/services/toolGatewayService.d.ts.map +1 -0
  321. package/backend/dist/services/toolGatewayService.js +249 -0
  322. package/backend/dist/services/toolGatewayService.js.map +1 -0
  323. package/backend/dist/services/toolServerService.d.ts +57 -0
  324. package/backend/dist/services/toolServerService.d.ts.map +1 -0
  325. package/backend/dist/services/toolServerService.js +521 -0
  326. package/backend/dist/services/toolServerService.js.map +1 -0
  327. package/backend/dist/services/voiceAccessService.d.ts +10 -0
  328. package/backend/dist/services/voiceAccessService.d.ts.map +1 -0
  329. package/backend/dist/services/voiceAccessService.js +74 -0
  330. package/backend/dist/services/voiceAccessService.js.map +1 -0
  331. package/backend/dist/services/voiceProfileService.d.ts +20 -0
  332. package/backend/dist/services/voiceProfileService.d.ts.map +1 -1
  333. package/backend/dist/services/voiceProfileService.js +60 -0
  334. package/backend/dist/services/voiceProfileService.js.map +1 -1
  335. package/backend/dist/services/webSearchPlanService.d.ts +18 -0
  336. package/backend/dist/services/webSearchPlanService.d.ts.map +1 -0
  337. package/backend/dist/services/webSearchPlanService.js +160 -0
  338. package/backend/dist/services/webSearchPlanService.js.map +1 -0
  339. package/backend/dist/services/webSearchService.d.ts +9 -3
  340. package/backend/dist/services/webSearchService.d.ts.map +1 -1
  341. package/backend/dist/services/webSearchService.js +8 -2
  342. package/backend/dist/services/webSearchService.js.map +1 -1
  343. package/backend/dist/storage.d.ts +34 -1
  344. package/backend/dist/storage.d.ts.map +1 -1
  345. package/backend/dist/storage.js +164 -1
  346. package/backend/dist/storage.js.map +1 -1
  347. package/backend/dist/storageMappers.d.ts +2 -2
  348. package/backend/dist/storageMappers.d.ts.map +1 -1
  349. package/backend/dist/storageMappers.js.map +1 -1
  350. package/backend/dist/types/index.d.ts +269 -2
  351. package/backend/dist/types/index.d.ts.map +1 -1
  352. package/backend/dist/types/index.js.map +1 -1
  353. package/backend/dist/types/tools.d.ts +137 -0
  354. package/backend/dist/types/tools.d.ts.map +1 -0
  355. package/backend/dist/types/tools.js +25 -0
  356. package/backend/dist/types/tools.js.map +1 -0
  357. package/backend/dist/utils/automationSchedule.d.ts +26 -0
  358. package/backend/dist/utils/automationSchedule.d.ts.map +1 -0
  359. package/backend/dist/utils/automationSchedule.js +231 -0
  360. package/backend/dist/utils/automationSchedule.js.map +1 -0
  361. package/backend/dist/utils/chatDocumentContext.d.ts +26 -1
  362. package/backend/dist/utils/chatDocumentContext.d.ts.map +1 -1
  363. package/backend/dist/utils/chatDocumentContext.js +87 -18
  364. package/backend/dist/utils/chatDocumentContext.js.map +1 -1
  365. package/backend/dist/utils/documentExtraction.d.ts +69 -0
  366. package/backend/dist/utils/documentExtraction.d.ts.map +1 -0
  367. package/backend/dist/utils/documentExtraction.js +545 -0
  368. package/backend/dist/utils/documentExtraction.js.map +1 -0
  369. package/backend/dist/utils/hybridRetrieval.d.ts +34 -0
  370. package/backend/dist/utils/hybridRetrieval.d.ts.map +1 -0
  371. package/backend/dist/utils/hybridRetrieval.js +119 -0
  372. package/backend/dist/utils/hybridRetrieval.js.map +1 -0
  373. package/backend/dist/utils/icsCalendar.d.ts +27 -0
  374. package/backend/dist/utils/icsCalendar.d.ts.map +1 -0
  375. package/backend/dist/utils/icsCalendar.js +313 -0
  376. package/backend/dist/utils/icsCalendar.js.map +1 -0
  377. package/backend/dist/utils/imageEditUpload.d.ts +29 -0
  378. package/backend/dist/utils/imageEditUpload.d.ts.map +1 -0
  379. package/backend/dist/utils/imageEditUpload.js +82 -0
  380. package/backend/dist/utils/imageEditUpload.js.map +1 -0
  381. package/backend/dist/utils/logRedaction.d.ts +2 -0
  382. package/backend/dist/utils/logRedaction.d.ts.map +1 -0
  383. package/backend/dist/utils/logRedaction.js +66 -0
  384. package/backend/dist/utils/logRedaction.js.map +1 -0
  385. package/backend/dist/utils/logger.d.ts +3 -0
  386. package/backend/dist/utils/logger.d.ts.map +1 -1
  387. package/backend/dist/utils/logger.js +63 -20
  388. package/backend/dist/utils/logger.js.map +1 -1
  389. package/backend/dist/utils/pluginChatAdapter.d.ts +5 -1
  390. package/backend/dist/utils/pluginChatAdapter.d.ts.map +1 -1
  391. package/backend/dist/utils/pluginChatAdapter.js +83 -0
  392. package/backend/dist/utils/pluginChatAdapter.js.map +1 -1
  393. package/backend/dist/utils/pluginDefinitionTrust.js +1 -1
  394. package/backend/dist/utils/resourceLimits.d.ts +68 -2
  395. package/backend/dist/utils/resourceLimits.d.ts.map +1 -1
  396. package/backend/dist/utils/resourceLimits.js +66 -0
  397. package/backend/dist/utils/resourceLimits.js.map +1 -1
  398. package/backend/dist/utils/toolEgress.d.ts +22 -0
  399. package/backend/dist/utils/toolEgress.d.ts.map +1 -0
  400. package/backend/dist/utils/toolEgress.js +157 -0
  401. package/backend/dist/utils/toolEgress.js.map +1 -0
  402. package/backend/dist/utils/websocketMessages.d.ts +6 -0
  403. package/backend/dist/utils/websocketMessages.d.ts.map +1 -1
  404. package/backend/dist/utils/websocketMessages.js +8 -0
  405. package/backend/dist/utils/websocketMessages.js.map +1 -1
  406. package/backend/dist/utils/zipArchive.d.ts +32 -0
  407. package/backend/dist/utils/zipArchive.d.ts.map +1 -0
  408. package/backend/dist/utils/zipArchive.js +171 -0
  409. package/backend/dist/utils/zipArchive.js.map +1 -0
  410. package/backend/dist/websocketServer.d.ts.map +1 -1
  411. package/backend/dist/websocketServer.js +150 -37
  412. package/backend/dist/websocketServer.js.map +1 -1
  413. package/backend/package.json +1 -1
  414. package/frontend/dist/artifact-runtime/.build-fingerprint +1 -1
  415. package/frontend/dist/assets/index-BPsH9FII.js +80 -0
  416. package/frontend/dist/assets/index-BtXF0Oxa.css +1 -0
  417. package/frontend/dist/index.html +18 -18
  418. package/frontend/dist/js/{ArtifactContainer-B-hs0F5K.js → ArtifactContainer-CIoFoMv8.js} +1 -1
  419. package/frontend/dist/js/{ArtifactDemoPage-QFtJw-u4.js → ArtifactDemoPage-D0JEIAbZ.js} +1 -1
  420. package/frontend/dist/js/{ArtifactSandboxFrame-S9g_q47-.js → ArtifactSandboxFrame-REazyIrx.js} +2 -2
  421. package/frontend/dist/js/ArtifactSlideOutPanel-A-i94ghF.js +1 -0
  422. package/frontend/dist/js/AutomationsPage-Dow_lbIf.js +1 -0
  423. package/frontend/dist/js/{Button-BPf_pOxH.js → Button-C0r3J9G_.js} +1 -1
  424. package/frontend/dist/js/CalendarPage-DLjasZOd.js +1 -0
  425. package/frontend/dist/js/ChannelsPage-C8hvTD7J.js +1 -0
  426. package/frontend/dist/js/ChatPage-BAgBHUES.js +16 -0
  427. package/frontend/dist/js/EvaluationsPage-Bm8FRoeX.js +2 -0
  428. package/frontend/dist/js/FirstTimeSetup-BxJapNul.js +1 -0
  429. package/frontend/dist/js/GalleryPage-DHz9DkK7.js +1 -0
  430. package/frontend/dist/js/HomePage-BqNy-FWX.js +1 -0
  431. package/frontend/dist/js/LibreClawPage-BDzDILjY.js +1 -0
  432. package/frontend/dist/js/{ModelManagerModals-DBmm_slu.js → ModelManagerModals-DX9eT5r2.js} +1 -1
  433. package/frontend/dist/js/ModelSelector-ol8mK2ja.js +1 -0
  434. package/frontend/dist/js/NotesPage-DzHEx2O4.js +3 -0
  435. package/frontend/dist/js/{OptimizedSyntaxHighlighter-Dz7D1LPB.js → OptimizedSyntaxHighlighter-CZNPwN0i.js} +1 -1
  436. package/frontend/dist/js/PersonasPage-M_WKH2a9.js +13 -0
  437. package/frontend/dist/js/PluginUsagePage-Ctoka6JZ.js +1 -0
  438. package/frontend/dist/js/RichMessageContent-BqgafH4p.js +3 -0
  439. package/frontend/dist/js/SettingsModal-R9EDadAV.js +19 -0
  440. package/frontend/dist/js/{SettingsToggle-woG_WebF.js → SettingsToggle-BIVyfGjl.js} +1 -1
  441. package/frontend/dist/js/{SystemPage-Xr7XJv8c.js → SystemPage-aOkrSuY7.js} +1 -1
  442. package/frontend/dist/js/UserManagementPage-BOuaX3ay.js +1 -0
  443. package/frontend/dist/js/WorkPage-DxZISerQ.js +6 -0
  444. package/frontend/dist/js/api-6bSMtSNh.js +14 -0
  445. package/frontend/dist/js/{appStore-DXlmTjIC.js → appStore-CdObhQBb.js} +2 -2
  446. package/frontend/dist/js/ar-DT_VLBO3.js +2 -0
  447. package/frontend/dist/js/{authStore-B97lJ5Ee.js → authStore-DbqqrfZv.js} +3 -3
  448. package/frontend/dist/js/{babel-D24oDI9y.js → babel-DSqqk5kt.js} +1 -1
  449. package/frontend/dist/js/bn-BrvEqhGA.js +2 -0
  450. package/frontend/dist/js/calendarDates-Co3o_05Y.js +1 -0
  451. package/frontend/dist/js/chatStore-DbZPJNsu.js +3 -0
  452. package/frontend/dist/js/{config-CmEh9o8U.js → config-8KdgjtEx.js} +1 -1
  453. package/frontend/dist/js/cs-BthnM_YI.js +2 -0
  454. package/frontend/dist/js/da-DCth-6_j.js +2 -0
  455. package/frontend/dist/js/de-BBWt0gJ_.js +2 -0
  456. package/frontend/dist/js/es-DuslOPqM.js +2 -0
  457. package/frontend/dist/js/{estree-CZO8RS52.js → estree-BU5-LV_1.js} +7 -7
  458. package/frontend/dist/js/evaluationsApi-CwZJsgeG.js +1 -0
  459. package/frontend/dist/js/fr-DZAeg46S.js +2 -0
  460. package/frontend/dist/js/hi-ijDSiY77.js +2 -0
  461. package/frontend/dist/js/{html-C31pxhBG.js → html-DeoAy2bo.js} +10 -10
  462. package/frontend/dist/js/id-BS6qS0oq.js +2 -0
  463. package/frontend/dist/js/is-CO2PMDte.js +2 -0
  464. package/frontend/dist/js/it-ChhxT4hs.js +2 -0
  465. package/frontend/dist/js/ja-cyeqUeJN.js +2 -0
  466. package/frontend/dist/js/ko-CfqHTLXj.js +2 -0
  467. package/frontend/dist/js/{markdown-C1CEqXpL.js → markdown-DOLsYkBY.js} +6 -6
  468. package/frontend/dist/js/markdown-core-BK7WVOb7.js +32 -0
  469. package/frontend/dist/js/{markdown-math-DK846gXU.js → markdown-math-e1ByS3KY.js} +5 -5
  470. package/frontend/dist/js/{messageCodeStyles-BqQ6kOSU.js → messageCodeStyles-DhVgizF-.js} +2 -2
  471. package/frontend/dist/js/ms-DZ5pACe1.js +2 -0
  472. package/frontend/dist/js/nl-BsRsnOVp.js +2 -0
  473. package/frontend/dist/js/pl-DXSTAW4M.js +2 -0
  474. package/frontend/dist/js/{postcss-Bx9CTcIc.js → postcss-Bg15EcFE.js} +1 -1
  475. package/frontend/dist/js/pt-DmDx0JxB.js +2 -0
  476. package/frontend/dist/js/{react-vendor-C5yShNlM.js → react-vendor-BG_HPV6X.js} +1 -1
  477. package/frontend/dist/js/router-vendor-CbexJIsv.js +1 -0
  478. package/frontend/dist/js/ru-d2g8EWfY.js +2 -0
  479. package/frontend/dist/js/{standalone-Dz0pkxn0.js → standalone-BO5pzI8_.js} +5 -5
  480. package/frontend/dist/js/sv-CIhYUTVQ.js +2 -0
  481. package/frontend/dist/js/{syntax-highlight-3EEX6CGL.js → syntax-highlight-DcR9Vhb7.js} +3 -3
  482. package/frontend/dist/js/th-B3wSxanB.js +2 -0
  483. package/frontend/dist/js/tr-R4UGgPOY.js +2 -0
  484. package/frontend/dist/js/{typescript-DeXPhp90.js → typescript-6GJ8APmn.js} +2 -2
  485. package/frontend/dist/js/ui-CV0LYmkM.js +2 -0
  486. package/frontend/dist/js/ui-vendor-CWjIBW-z.js +177 -0
  487. package/frontend/dist/js/uk-BzUF4Vkt.js +2 -0
  488. package/frontend/dist/js/{useTranslation-Bho4HCyg.js → useTranslation-5b5UhAc9.js} +1 -1
  489. package/frontend/dist/js/{utils-D9W1Lnkn.js → utils-BGrdX9ie.js} +1 -1
  490. package/frontend/dist/js/{utils-vendor-DWh39_3x.js → utils-vendor-9bQ62WeD.js} +1 -1
  491. package/frontend/dist/js/vi-CKc6Fg4h.js +2 -0
  492. package/frontend/dist/js/{workApi-C4QtuhcE.js → workApi-BruWTlHM.js} +2 -2
  493. package/frontend/dist/js/{yaml-DioTxVLA.js → yaml-DUeWcOOH.js} +5 -5
  494. package/frontend/dist/js/zh-CXL-kDe8.js +2 -0
  495. package/package.json +2 -2
  496. package/plugins/openai.json +52 -9
  497. package/frontend/dist/assets/index-CDQ12KBq.js +0 -34
  498. package/frontend/dist/assets/index-DkFWzmFD.css +0 -1
  499. package/frontend/dist/js/ArtifactSlideOutPanel-D0UOo8RW.js +0 -1
  500. package/frontend/dist/js/ChatPage-fdd8500b.js +0 -23
  501. package/frontend/dist/js/FirstTimeSetup-BTjafGOq.js +0 -1
  502. package/frontend/dist/js/GalleryPage-hR9AJ6b-.js +0 -1
  503. package/frontend/dist/js/HomePage-JvTzq-Pp.js +0 -1
  504. package/frontend/dist/js/LibreClawPage-CLJvtXft.js +0 -1
  505. package/frontend/dist/js/ModelSelector-A-viVFEc.js +0 -1
  506. package/frontend/dist/js/ModelsPage-C4Rm-TZF.js +0 -2
  507. package/frontend/dist/js/NotesPage-C7bPU_KF.js +0 -1
  508. package/frontend/dist/js/PersonasPage-DpvxbFzv.js +0 -13
  509. package/frontend/dist/js/PluginUsagePage-laF-M2X9.js +0 -1
  510. package/frontend/dist/js/RichMessageContent-D0mK9vRA.js +0 -3
  511. package/frontend/dist/js/SettingsModal-DNEQ5sTt.js +0 -1
  512. package/frontend/dist/js/UserManagementPage-DGmbGTC2.js +0 -1
  513. package/frontend/dist/js/WorkPage-DNKFD9gS.js +0 -6
  514. package/frontend/dist/js/api-DGVZHSkS.js +0 -14
  515. package/frontend/dist/js/ar-DW79oSyN.js +0 -2
  516. package/frontend/dist/js/bn-BEAmE7VH.js +0 -2
  517. package/frontend/dist/js/chatStore-zilBUd1p.js +0 -3
  518. package/frontend/dist/js/cs-irpRMzRo.js +0 -2
  519. package/frontend/dist/js/da-cgilTVxc.js +0 -2
  520. package/frontend/dist/js/de-BU-eH3gU.js +0 -2
  521. package/frontend/dist/js/es-B2LRpZ0p.js +0 -2
  522. package/frontend/dist/js/fr-Cd5JM-oQ.js +0 -2
  523. package/frontend/dist/js/hi-CvOhdtiK.js +0 -2
  524. package/frontend/dist/js/id-BtRK5nR_.js +0 -2
  525. package/frontend/dist/js/is-CnEqwgRF.js +0 -2
  526. package/frontend/dist/js/it-CI6WjId6.js +0 -2
  527. package/frontend/dist/js/ja-B6gFOHi6.js +0 -2
  528. package/frontend/dist/js/ko-DuAhnmMf.js +0 -2
  529. package/frontend/dist/js/markdown-core-BmPPZiuD.js +0 -31
  530. package/frontend/dist/js/ms-C8BIP7MZ.js +0 -2
  531. package/frontend/dist/js/nl-DnEdspER.js +0 -2
  532. package/frontend/dist/js/pl-Ddyyvbse.js +0 -2
  533. package/frontend/dist/js/pt-BYuzm70F.js +0 -2
  534. package/frontend/dist/js/router-vendor-CbPDks39.js +0 -1
  535. package/frontend/dist/js/ru-CmSPFxPX.js +0 -2
  536. package/frontend/dist/js/sv-Dd6SQ9f1.js +0 -2
  537. package/frontend/dist/js/th-CpH8745B.js +0 -2
  538. package/frontend/dist/js/tr-DuVD75iJ.js +0 -2
  539. package/frontend/dist/js/ui-CO0DPqrI.js +0 -2
  540. package/frontend/dist/js/ui-vendor-mqjSfAaS.js +0 -177
  541. package/frontend/dist/js/uk-B0MU-M8k.js +0 -2
  542. package/frontend/dist/js/vi-BLmFNp_z.js +0 -2
  543. package/frontend/dist/js/zh-BQu0TLhw.js +0 -2
@@ -34,9 +34,168 @@ 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
+ });
50
+ const calendarEvent = (row) => ({
51
+ ...row,
52
+ start_at: number(row.start_at, 'calendar event start_at'),
53
+ end_at: row.end_at === null ? null : number(row.end_at, 'calendar event end_at'),
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'),
61
+ created_at: number(row.created_at, 'calendar event created_at'),
62
+ updated_at: number(row.updated_at, 'calendar event updated_at'),
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
+ });
116
+ const automation = (row) => ({
117
+ ...row,
118
+ next_run_at: row.next_run_at === null
119
+ ? null
120
+ : number(row.next_run_at, 'automation next_run_at'),
121
+ last_run_at: row.last_run_at === null
122
+ ? null
123
+ : number(row.last_run_at, 'automation last_run_at'),
124
+ created_at: number(row.created_at, 'automation created_at'),
125
+ updated_at: number(row.updated_at, 'automation updated_at'),
126
+ });
127
+ const automationRun = (row) => ({
128
+ ...row,
129
+ scheduled_for: number(row.scheduled_for, 'automation run scheduled_for'),
130
+ started_at: row.started_at === null
131
+ ? null
132
+ : number(row.started_at, 'automation run started_at'),
133
+ finished_at: row.finished_at === null
134
+ ? null
135
+ : number(row.finished_at, 'automation run finished_at'),
136
+ seen_at: row.seen_at === null ? null : number(row.seen_at, 'automation run seen_at'),
137
+ created_at: number(row.created_at, 'automation run created_at'),
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
+ });
40
199
  const changes = (rowCount) => rowCount ?? 0;
41
200
  const lockOwner = async (client, userId) => {
42
201
  const result = await client.query('SELECT id FROM users WHERE id = $1 FOR UPDATE', [userId]);
@@ -198,118 +357,1541 @@ class PostgresChatSessionRepository {
198
357
  AND (id = $3 OR parent_id = $3)`, [previousActiveMessageId, sessionId, row.parent_id]);
199
358
  }
200
359
  }
201
- return changes(deleted.rowCount) > 0;
202
- }, { isolationLevel: 'serializable' });
360
+ return changes(deleted.rowCount) > 0;
361
+ }, { isolationLevel: 'serializable' });
362
+ }
363
+ async deleteByOwner(sessionId, userId, beforeCommit) {
364
+ return this.database.transaction(async (client) => changes((await client.query('DELETE FROM sessions WHERE id = $1 AND user_id = $2', [sessionId, userId])).rowCount) > 0, { isolationLevel: 'serializable', beforeCommit });
365
+ }
366
+ async deleteAllByOwner(userId) {
367
+ return changes((await this.database.query('DELETE FROM sessions WHERE user_id = $1', [
368
+ userId,
369
+ ])).rowCount);
370
+ }
371
+ }
372
+ class PostgresKnowledgeCollectionRepository {
373
+ constructor(database) {
374
+ this.database = database;
375
+ }
376
+ async listByOwner(userId) {
377
+ const result = await this.database.query(`SELECT * FROM knowledge_collections
378
+ WHERE user_id = $1
379
+ ORDER BY lower(name), name`, [userId]);
380
+ return result.rows.map(namedResource);
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
+ }
386
+ async replace(collection) {
387
+ await this.database.transaction(async (client) => {
388
+ await lockOwner(client, collection.user_id);
389
+ const result = await client.query(`INSERT INTO knowledge_collections
390
+ (id, user_id, name, created_at, updated_at)
391
+ VALUES ($1, $2, $3, $4, $5)
392
+ ON CONFLICT (id) DO UPDATE SET
393
+ name = EXCLUDED.name,
394
+ updated_at = EXCLUDED.updated_at
395
+ WHERE knowledge_collections.user_id = EXCLUDED.user_id`, [
396
+ collection.id,
397
+ collection.user_id,
398
+ collection.name,
399
+ collection.created_at,
400
+ collection.updated_at,
401
+ ]);
402
+ if (result.rowCount !== 1)
403
+ throw new PersistenceResourceConflictError();
404
+ });
405
+ }
406
+ async deleteAndDetach(collectionId, userId) {
407
+ return (changes((await this.database.query('DELETE FROM knowledge_collections WHERE id = $1 AND user_id = $2', [collectionId, userId])).rowCount) > 0);
408
+ }
409
+ }
410
+ class PostgresNoteRepository {
411
+ constructor(database) {
412
+ this.database = database;
413
+ }
414
+ async listByOwner(userId, maximum) {
415
+ const result = await this.database.query(`SELECT * FROM notes
416
+ WHERE user_id = $1
417
+ ORDER BY pinned DESC, updated_at DESC
418
+ LIMIT $2`, [userId, maximum]);
419
+ return result.rows.map(note);
420
+ }
421
+ async findByOwner(noteId, userId) {
422
+ const result = await this.database.query('SELECT * FROM notes WHERE id = $1 AND user_id = $2', [noteId, userId]);
423
+ return result.rows[0] ? note(result.rows[0]) : null;
424
+ }
425
+ async replaceWithLimit(value, maximum) {
426
+ await this.database.transaction(async (client) => {
427
+ await lockOwner(client, value.user_id);
428
+ const existing = await client.query('SELECT user_id FROM notes WHERE id = $1 FOR UPDATE', [value.id]);
429
+ if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
430
+ throw new PersistenceResourceConflictError();
431
+ }
432
+ if (!existing.rows[0]) {
433
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM notes WHERE user_id = $1', [value.user_id]);
434
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
435
+ throw new PersistenceResourceLimitError('note', maximum);
436
+ }
437
+ }
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)
440
+ ON CONFLICT (id) DO UPDATE SET
441
+ title = EXCLUDED.title,
442
+ content = EXCLUDED.content,
443
+ pinned = EXCLUDED.pinned,
444
+ updated_at = EXCLUDED.updated_at
445
+ WHERE notes.user_id = EXCLUDED.user_id`, [
446
+ value.id,
447
+ value.user_id,
448
+ value.title,
449
+ value.content,
450
+ value.pinned,
451
+ value.created_at,
452
+ value.updated_at,
453
+ ]);
454
+ if (result.rowCount !== 1)
455
+ throw new PersistenceResourceConflictError();
456
+ });
457
+ }
458
+ async patchByOwner(noteId, userId, patch) {
459
+ const assignments = [];
460
+ const values = [];
461
+ const assign = (column, value) => {
462
+ values.push(value);
463
+ assignments.push(`${column} = $${values.length}`);
464
+ };
465
+ if (patch.title !== undefined)
466
+ assign('title', patch.title);
467
+ if (patch.content !== undefined)
468
+ assign('content', patch.content);
469
+ if (patch.pinned !== undefined)
470
+ assign('pinned', patch.pinned);
471
+ assign('updated_at', patch.updated_at);
472
+ values.push(noteId, userId);
473
+ const result = await this.database.query(`UPDATE notes SET ${assignments.join(', ')}
474
+ WHERE id = $${values.length - 1} AND user_id = $${values.length}
475
+ RETURNING *`, values);
476
+ return result.rows[0] ? note(result.rows[0]) : null;
477
+ }
478
+ async deleteByOwner(noteId, userId) {
479
+ return (changes((await this.database.query('DELETE FROM notes WHERE id = $1 AND user_id = $2', [noteId, userId])).rowCount) > 0);
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
+ }
575
+ }
576
+ class PostgresCalendarEventRepository {
577
+ constructor(database) {
578
+ this.database = database;
579
+ }
580
+ async listByOwnerBetween(userId, from, to, maximum) {
581
+ const result = await this.database.query(`SELECT * FROM calendar_events
582
+ WHERE user_id = $1 AND start_at >= $2 AND start_at < $3
583
+ ORDER BY start_at ASC
584
+ LIMIT $4`, [userId, from, to, maximum]);
585
+ return result.rows.map(calendarEvent);
586
+ }
587
+ async listRecurringByOwner(userId, maximum) {
588
+ const result = await this.database.query(`SELECT * FROM calendar_events
589
+ WHERE user_id = $1 AND recurrence IS NOT NULL
590
+ ORDER BY start_at ASC
591
+ LIMIT $2`, [userId, maximum]);
592
+ return result.rows.map(calendarEvent);
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
+ }
614
+ async findByOwner(eventId, userId) {
615
+ const result = await this.database.query('SELECT * FROM calendar_events WHERE id = $1 AND user_id = $2', [eventId, userId]);
616
+ return result.rows[0] ? calendarEvent(result.rows[0]) : null;
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
+ }
636
+ async replaceWithLimit(value, maximum) {
637
+ await this.database.transaction(async (client) => {
638
+ await lockOwner(client, value.user_id);
639
+ const existing = await client.query('SELECT user_id FROM calendar_events WHERE id = $1 FOR UPDATE', [value.id]);
640
+ if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
641
+ throw new PersistenceResourceConflictError();
642
+ }
643
+ if (!existing.rows[0]) {
644
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM calendar_events WHERE user_id = $1', [value.user_id]);
645
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
646
+ throw new PersistenceResourceLimitError('calendar-event', maximum);
647
+ }
648
+ }
649
+ const result = await client.query(`INSERT INTO calendar_events
650
+ (id, user_id, title, notes, start_at, end_at, all_day,
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)
654
+ ON CONFLICT (id) DO UPDATE SET
655
+ title = EXCLUDED.title,
656
+ notes = EXCLUDED.notes,
657
+ start_at = EXCLUDED.start_at,
658
+ end_at = EXCLUDED.end_at,
659
+ all_day = EXCLUDED.all_day,
660
+ recurrence = EXCLUDED.recurrence,
661
+ calendar_id = EXCLUDED.calendar_id,
662
+ reminder_minutes = EXCLUDED.reminder_minutes,
663
+ last_reminded_occurrence = EXCLUDED.last_reminded_occurrence,
664
+ updated_at = EXCLUDED.updated_at
665
+ WHERE calendar_events.user_id = EXCLUDED.user_id`, [
666
+ value.id,
667
+ value.user_id,
668
+ value.title,
669
+ value.notes,
670
+ value.start_at,
671
+ value.end_at,
672
+ value.all_day,
673
+ value.recurrence,
674
+ value.calendar_id,
675
+ value.reminder_minutes,
676
+ value.last_reminded_occurrence,
677
+ value.created_at,
678
+ value.updated_at,
679
+ ]);
680
+ if (result.rowCount !== 1)
681
+ throw new PersistenceResourceConflictError();
682
+ });
683
+ }
684
+ async deleteByOwner(eventId, userId) {
685
+ return (changes((await this.database.query('DELETE FROM calendar_events WHERE id = $1 AND user_id = $2', [eventId, userId])).rowCount) > 0);
686
+ }
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
+ }
1235
+ class PostgresAutomationRepository {
1236
+ constructor(database) {
1237
+ this.database = database;
1238
+ }
1239
+ async listByOwner(userId, maximum) {
1240
+ const result = await this.database.query(`SELECT * FROM automations
1241
+ WHERE user_id = $1
1242
+ ORDER BY updated_at DESC
1243
+ LIMIT $2`, [userId, maximum]);
1244
+ return result.rows.map(automation);
1245
+ }
1246
+ async findByOwner(automationId, userId) {
1247
+ const result = await this.database.query('SELECT * FROM automations WHERE id = $1 AND user_id = $2', [automationId, userId]);
1248
+ return result.rows[0] ? automation(result.rows[0]) : null;
1249
+ }
1250
+ async findById(automationId) {
1251
+ const result = await this.database.query('SELECT * FROM automations WHERE id = $1', [automationId]);
1252
+ return result.rows[0] ? automation(result.rows[0]) : null;
1253
+ }
1254
+ async replaceWithLimit(value, maximum) {
1255
+ await this.database.transaction(async (client) => {
1256
+ await lockOwner(client, value.user_id);
1257
+ const existing = await client.query('SELECT user_id FROM automations WHERE id = $1 FOR UPDATE', [value.id]);
1258
+ if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
1259
+ throw new PersistenceResourceConflictError();
1260
+ }
1261
+ if (!existing.rows[0]) {
1262
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM automations WHERE user_id = $1', [value.user_id]);
1263
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
1264
+ throw new PersistenceResourceLimitError('automation', maximum);
1265
+ }
1266
+ }
1267
+ const result = await client.query(`INSERT INTO automations
1268
+ (id, user_id, name, instructions, triggers, provider, model,
1269
+ notify, status, next_run_at, last_run_at, created_at, updated_at)
1270
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
1271
+ ON CONFLICT (id) DO UPDATE SET
1272
+ name = EXCLUDED.name,
1273
+ instructions = EXCLUDED.instructions,
1274
+ triggers = EXCLUDED.triggers,
1275
+ provider = EXCLUDED.provider,
1276
+ model = EXCLUDED.model,
1277
+ notify = EXCLUDED.notify,
1278
+ status = EXCLUDED.status,
1279
+ next_run_at = EXCLUDED.next_run_at,
1280
+ last_run_at = EXCLUDED.last_run_at,
1281
+ updated_at = EXCLUDED.updated_at
1282
+ WHERE automations.user_id = EXCLUDED.user_id`, [
1283
+ value.id,
1284
+ value.user_id,
1285
+ value.name,
1286
+ value.instructions,
1287
+ value.triggers,
1288
+ value.provider,
1289
+ value.model,
1290
+ value.notify,
1291
+ value.status,
1292
+ value.next_run_at,
1293
+ value.last_run_at,
1294
+ value.created_at,
1295
+ value.updated_at,
1296
+ ]);
1297
+ if (result.rowCount !== 1)
1298
+ throw new PersistenceResourceConflictError();
1299
+ });
1300
+ }
1301
+ async listDue(now, maximum) {
1302
+ const result = await this.database.query(`SELECT * FROM automations
1303
+ WHERE status = 'active'
1304
+ AND next_run_at IS NOT NULL
1305
+ AND next_run_at <= $1
1306
+ ORDER BY next_run_at ASC
1307
+ LIMIT $2`, [now, maximum]);
1308
+ return result.rows.map(automation);
1309
+ }
1310
+ async advanceNextRun(automationId, observedNextRunAt, nextRunAt, lastRunAt) {
1311
+ return (changes((await this.database.query(`UPDATE automations
1312
+ SET next_run_at = $1, last_run_at = $2
1313
+ WHERE id = $3 AND next_run_at = $4`, [nextRunAt, lastRunAt, automationId, observedNextRunAt])).rowCount) > 0);
1314
+ }
1315
+ async setStatus(automationId, userId, status, nextRunAt, updatedAt) {
1316
+ return (changes((await this.database.query(`UPDATE automations
1317
+ SET status = $1, next_run_at = $2, updated_at = $3
1318
+ WHERE id = $4 AND user_id = $5`, [status, nextRunAt, updatedAt, automationId, userId])).rowCount) > 0);
1319
+ }
1320
+ async deleteByOwner(automationId, userId) {
1321
+ return (changes((await this.database.query('DELETE FROM automations WHERE id = $1 AND user_id = $2', [automationId, userId])).rowCount) > 0);
1322
+ }
1323
+ }
1324
+ class PostgresAutomationRunRepository {
1325
+ constructor(database) {
1326
+ this.database = database;
1327
+ }
1328
+ async insert(run) {
1329
+ await this.database.query(`INSERT INTO automation_runs
1330
+ (id, automation_id, user_id, scheduled_for, started_at, finished_at,
1331
+ status, session_id, assistant_message_id, error, seen_at, created_at)
1332
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
1333
+ run.id,
1334
+ run.automation_id,
1335
+ run.user_id,
1336
+ run.scheduled_for,
1337
+ run.started_at,
1338
+ run.finished_at,
1339
+ run.status,
1340
+ run.session_id,
1341
+ run.assistant_message_id,
1342
+ run.error,
1343
+ run.seen_at,
1344
+ run.created_at,
1345
+ ]);
1346
+ }
1347
+ async findByOwner(runId, userId) {
1348
+ const result = await this.database.query('SELECT * FROM automation_runs WHERE id = $1 AND user_id = $2', [runId, userId]);
1349
+ return result.rows[0] ? automationRun(result.rows[0]) : null;
1350
+ }
1351
+ async listByOwner(userId, options) {
1352
+ const clauses = ['user_id = $1'];
1353
+ const values = [userId];
1354
+ if (options.automationId !== undefined) {
1355
+ values.push(options.automationId);
1356
+ clauses.push(`automation_id = $${values.length}`);
1357
+ }
1358
+ if (options.from !== undefined) {
1359
+ values.push(options.from);
1360
+ clauses.push(`scheduled_for >= $${values.length}`);
1361
+ }
1362
+ if (options.to !== undefined) {
1363
+ values.push(options.to);
1364
+ clauses.push(`scheduled_for < $${values.length}`);
1365
+ }
1366
+ values.push(options.maximum);
1367
+ const result = await this.database.query(`SELECT * FROM automation_runs
1368
+ WHERE ${clauses.join(' AND ')}
1369
+ ORDER BY scheduled_for DESC
1370
+ LIMIT $${values.length}`, values);
1371
+ return result.rows.map(automationRun);
1372
+ }
1373
+ async listUnfinished(maximum) {
1374
+ const result = await this.database.query(`SELECT * FROM automation_runs
1375
+ WHERE status IN ('queued', 'running')
1376
+ ORDER BY scheduled_for ASC
1377
+ LIMIT $1`, [maximum]);
1378
+ return result.rows.map(automationRun);
1379
+ }
1380
+ async markStarted(runId, sessionId, assistantMessageId, startedAt) {
1381
+ return (changes((await this.database.query(`UPDATE automation_runs
1382
+ SET session_id = $1, assistant_message_id = $2, started_at = $3,
1383
+ status = 'running'
1384
+ WHERE id = $4 AND status = 'queued'`, [sessionId, assistantMessageId, startedAt, runId])).rowCount) > 0);
1385
+ }
1386
+ async finalize(runId, status, finishedAt, error) {
1387
+ return (changes((await this.database.query(`UPDATE automation_runs
1388
+ SET status = $1, finished_at = $2, error = $3
1389
+ WHERE id = $4 AND status IN ('queued', 'running')`, [status, finishedAt, error, runId])).rowCount) > 0);
1390
+ }
1391
+ async countUnseenFinished(userId) {
1392
+ const result = await this.database.query(`SELECT COUNT(*)::text AS count FROM automation_runs
1393
+ WHERE user_id = $1
1394
+ AND status IN ('succeeded', 'failed')
1395
+ AND seen_at IS NULL`, [userId]);
1396
+ return Number(result.rows[0]?.count || 0);
1397
+ }
1398
+ async markSeenBefore(userId, seenAt) {
1399
+ return changes((await this.database.query(`UPDATE automation_runs
1400
+ SET seen_at = $1
1401
+ WHERE user_id = $2
1402
+ AND status IN ('succeeded', 'failed')
1403
+ AND seen_at IS NULL
1404
+ AND finished_at IS NOT NULL
1405
+ AND finished_at <= $3`, [seenAt, userId, seenAt])).rowCount);
1406
+ }
1407
+ }
1408
+ class PostgresToolServerRepository {
1409
+ constructor(database) {
1410
+ this.database = database;
1411
+ }
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
+ });
203
1524
  }
204
- async deleteByOwner(sessionId, userId, beforeCommit) {
205
- return this.database.transaction(async (client) => changes((await client.query('DELETE FROM sessions WHERE id = $1 AND user_id = $2', [sessionId, userId])).rowCount) > 0, { isolationLevel: 'serializable', beforeCommit });
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;
206
1542
  }
207
- async deleteAllByOwner(userId) {
208
- return changes((await this.database.query('DELETE FROM sessions WHERE user_id = $1', [
209
- userId,
210
- ])).rowCount);
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);
211
1571
  }
212
1572
  }
213
- class PostgresKnowledgeCollectionRepository {
1573
+ class PostgresToolApprovalRepository {
214
1574
  constructor(database) {
215
1575
  this.database = database;
216
1576
  }
217
- async listByOwner(userId) {
218
- const result = await this.database.query(`SELECT * FROM knowledge_collections
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
219
1602
  WHERE user_id = $1
220
- ORDER BY lower(name), name`, [userId]);
221
- return result.rows.map(namedResource);
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;
222
1611
  }
223
- async replace(collection) {
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) {
224
1669
  await this.database.transaction(async (client) => {
225
- await lockOwner(client, collection.user_id);
226
- const result = await client.query(`INSERT INTO knowledge_collections
227
- (id, user_id, name, created_at, updated_at)
228
- VALUES ($1, $2, $3, $4, $5)
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)
229
1685
  ON CONFLICT (id) DO UPDATE SET
230
- name = EXCLUDED.name,
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,
231
1693
  updated_at = EXCLUDED.updated_at
232
- WHERE knowledge_collections.user_id = EXCLUDED.user_id`, [
233
- collection.id,
234
- collection.user_id,
235
- collection.name,
236
- collection.created_at,
237
- collection.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,
238
1706
  ]);
239
1707
  if (result.rowCount !== 1)
240
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
+ }
241
1722
  });
242
1723
  }
243
- async deleteAndDetach(collectionId, userId) {
244
- return (changes((await this.database.query('DELETE FROM knowledge_collections WHERE id = $1 AND user_id = $2', [collectionId, userId])).rowCount) > 0);
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);
245
1737
  }
246
1738
  }
247
- class PostgresNoteRepository {
1739
+ class PostgresSkillRepository {
248
1740
  constructor(database) {
249
1741
  this.database = database;
250
1742
  }
251
1743
  async listByOwner(userId, maximum) {
252
- const result = await this.database.query(`SELECT * FROM notes
1744
+ const result = await this.database.query(`SELECT * FROM skills
253
1745
  WHERE user_id = $1
254
1746
  ORDER BY updated_at DESC
255
1747
  LIMIT $2`, [userId, maximum]);
256
- return result.rows.map(note);
1748
+ return result.rows.map(skill);
257
1749
  }
258
- async findByOwner(noteId, userId) {
259
- const result = await this.database.query('SELECT * FROM notes WHERE id = $1 AND user_id = $2', [noteId, userId]);
260
- return result.rows[0] ? note(result.rows[0]) : null;
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;
261
1753
  }
262
- async replaceWithLimit(value, maximum) {
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) {
263
1763
  await this.database.transaction(async (client) => {
264
1764
  await lockOwner(client, value.user_id);
265
- const existing = await client.query('SELECT user_id FROM notes WHERE id = $1 FOR UPDATE', [value.id]);
1765
+ const existing = await client.query('SELECT user_id FROM skills WHERE id = $1 FOR UPDATE', [value.id]);
266
1766
  if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
267
1767
  throw new PersistenceResourceConflictError();
268
1768
  }
269
1769
  if (!existing.rows[0]) {
270
- const count = await client.query('SELECT COUNT(*)::text AS count FROM notes WHERE user_id = $1', [value.user_id]);
1770
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM skills WHERE user_id = $1', [value.user_id]);
271
1771
  if (Number(count.rows[0]?.count || 0) >= maximum) {
272
- throw new PersistenceResourceLimitError('note', maximum);
1772
+ throw new PersistenceResourceLimitError('skill', maximum);
273
1773
  }
274
1774
  }
275
- const result = await client.query(`INSERT INTO notes (id, user_id, title, content, created_at, updated_at)
276
- VALUES ($1, $2, $3, $4, $5, $6)
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)
277
1779
  ON CONFLICT (id) DO UPDATE SET
278
- title = EXCLUDED.title,
279
- content = EXCLUDED.content,
1780
+ slug = EXCLUDED.slug,
1781
+ name = EXCLUDED.name,
1782
+ description = EXCLUDED.description,
1783
+ instructions = EXCLUDED.instructions,
1784
+ enabled = EXCLUDED.enabled,
1785
+ version = EXCLUDED.version,
280
1786
  updated_at = EXCLUDED.updated_at
281
- WHERE notes.user_id = EXCLUDED.user_id`, [
1787
+ WHERE skills.user_id = EXCLUDED.user_id`, [
282
1788
  value.id,
283
1789
  value.user_id,
284
- value.title,
285
- value.content,
1790
+ value.slug,
1791
+ value.name,
1792
+ value.description,
1793
+ value.instructions,
1794
+ value.enabled,
1795
+ value.version,
286
1796
  value.created_at,
287
1797
  value.updated_at,
288
1798
  ]);
289
1799
  if (result.rowCount !== 1)
290
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
+ }
291
1813
  });
292
1814
  }
293
- async patchByOwner(noteId, userId, patch) {
294
- const assignments = [];
295
- const values = [];
296
- const assign = (column, value) => {
297
- values.push(value);
298
- assignments.push(`${column} = $${values.length}`);
299
- };
300
- if (patch.title !== undefined)
301
- assign('title', patch.title);
302
- if (patch.content !== undefined)
303
- assign('content', patch.content);
304
- assign('updated_at', patch.updated_at);
305
- values.push(noteId, userId);
306
- const result = await this.database.query(`UPDATE notes SET ${assignments.join(', ')}
307
- WHERE id = $${values.length - 1} AND user_id = $${values.length}
308
- RETURNING *`, values);
309
- return result.rows[0] ? note(result.rows[0]) : null;
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);
310
1821
  }
311
- async deleteByOwner(noteId, userId) {
312
- return (changes((await this.database.query('DELETE FROM notes WHERE id = $1 AND user_id = $2', [noteId, userId])).rowCount) > 0);
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);
313
1895
  }
314
1896
  }
315
1897
  class PostgresSessionFolderRepository {
@@ -465,6 +2047,10 @@ const ARCHIVE_TABLES = {
465
2047
  'knowledge-collection': 'knowledge_collections',
466
2048
  document: 'documents',
467
2049
  persona: 'personas',
2050
+ prompt: 'prompts',
2051
+ skill: 'skills',
2052
+ 'tool-server': 'tool_servers',
2053
+ calendar: 'calendars',
468
2054
  };
469
2055
  class PostgresDataArchiveRepository {
470
2056
  constructor(database) {
@@ -756,14 +2342,337 @@ class PostgresDataArchiveRepository {
756
2342
  });
757
2343
  }
758
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
+ }
759
2647
  export const createPostgresResourceRepositories = (database) => ({
760
2648
  chatSessions: new PostgresChatSessionRepository(database),
761
2649
  knowledgeCollections: new PostgresKnowledgeCollectionRepository(database),
762
2650
  notes: new PostgresNoteRepository(database),
2651
+ calendars: new PostgresCalendarRepository(database),
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),
2657
+ automations: new PostgresAutomationRepository(database),
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),
763
2666
  sessionFolders: new PostgresSessionFolderRepository(database),
764
2667
  preferences: new PostgresPreferenceRepository(database),
765
2668
  systemSettings: new PostgresSystemSettingRepository(database),
766
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),
767
2676
  });
768
2677
  /** Create transaction-scoped repositories over one pinned pooled client. */
769
2678
  export const createPostgresTransactionalResourceRepositories = (database, client) => {