libre-webui 0.25.0 → 0.27.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 (588) hide show
  1. package/README.md +32 -22
  2. package/backend/dist/index.d.ts.map +1 -1
  3. package/backend/dist/index.js +33 -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/postgresMfaPushRecoveryMigration.d.ts +9 -0
  44. package/backend/dist/persistence/postgresMfaPushRecoveryMigration.d.ts.map +1 -0
  45. package/backend/dist/persistence/postgresMfaPushRecoveryMigration.js +107 -0
  46. package/backend/dist/persistence/postgresMfaPushRecoveryMigration.js.map +1 -0
  47. package/backend/dist/persistence/postgresMigrationRegistry.d.ts.map +1 -1
  48. package/backend/dist/persistence/postgresMigrationRegistry.js +12 -0
  49. package/backend/dist/persistence/postgresMigrationRegistry.js.map +1 -1
  50. package/backend/dist/persistence/postgresNotesV2Migration.d.ts +4 -0
  51. package/backend/dist/persistence/postgresNotesV2Migration.d.ts.map +1 -0
  52. package/backend/dist/persistence/postgresNotesV2Migration.js +57 -0
  53. package/backend/dist/persistence/postgresNotesV2Migration.js.map +1 -0
  54. package/backend/dist/persistence/postgresResourceRepositories.d.ts.map +1 -1
  55. package/backend/dist/persistence/postgresResourceRepositories.js +1930 -133
  56. package/backend/dist/persistence/postgresResourceRepositories.js.map +1 -1
  57. package/backend/dist/persistence/postgresSecurityRepositories.d.ts.map +1 -1
  58. package/backend/dist/persistence/postgresSecurityRepositories.js +150 -0
  59. package/backend/dist/persistence/postgresSecurityRepositories.js.map +1 -1
  60. package/backend/dist/persistence/postgresSkillFilesMigration.d.ts +9 -0
  61. package/backend/dist/persistence/postgresSkillFilesMigration.d.ts.map +1 -0
  62. package/backend/dist/persistence/postgresSkillFilesMigration.js +50 -0
  63. package/backend/dist/persistence/postgresSkillFilesMigration.js.map +1 -0
  64. package/backend/dist/persistence/postgresTeamCollaborationMigration.d.ts +10 -0
  65. package/backend/dist/persistence/postgresTeamCollaborationMigration.d.ts.map +1 -0
  66. package/backend/dist/persistence/postgresTeamCollaborationMigration.js +161 -0
  67. package/backend/dist/persistence/postgresTeamCollaborationMigration.js.map +1 -0
  68. package/backend/dist/persistence/resourceTypes.d.ts +600 -3
  69. package/backend/dist/persistence/resourceTypes.d.ts.map +1 -1
  70. package/backend/dist/persistence/resourceTypes.js.map +1 -1
  71. package/backend/dist/persistence/securityTypes.d.ts +59 -0
  72. package/backend/dist/persistence/securityTypes.d.ts.map +1 -1
  73. package/backend/dist/persistence/sqliteExtensionRepositories.d.ts.map +1 -1
  74. package/backend/dist/persistence/sqliteExtensionRepositories.js +26 -4
  75. package/backend/dist/persistence/sqliteExtensionRepositories.js.map +1 -1
  76. package/backend/dist/persistence/sqliteMigrations.d.ts +10 -0
  77. package/backend/dist/persistence/sqliteMigrations.d.ts.map +1 -1
  78. package/backend/dist/persistence/sqliteMigrations.js +1492 -131
  79. package/backend/dist/persistence/sqliteMigrations.js.map +1 -1
  80. package/backend/dist/persistence/sqliteResourceRepositories.d.ts.map +1 -1
  81. package/backend/dist/persistence/sqliteResourceRepositories.js +1657 -134
  82. package/backend/dist/persistence/sqliteResourceRepositories.js.map +1 -1
  83. package/backend/dist/persistence/sqliteSecurityRepositories.d.ts.map +1 -1
  84. package/backend/dist/persistence/sqliteSecurityRepositories.js +134 -0
  85. package/backend/dist/persistence/sqliteSecurityRepositories.js.map +1 -1
  86. package/backend/dist/persistence/sqliteToPostgresMigration.d.ts.map +1 -1
  87. package/backend/dist/persistence/sqliteToPostgresMigration.js +302 -3
  88. package/backend/dist/persistence/sqliteToPostgresMigration.js.map +1 -1
  89. package/backend/dist/platform/jobs/domainJobContracts.d.ts +10 -0
  90. package/backend/dist/platform/jobs/domainJobContracts.d.ts.map +1 -1
  91. package/backend/dist/platform/jobs/domainJobContracts.js +10 -0
  92. package/backend/dist/platform/jobs/domainJobContracts.js.map +1 -1
  93. package/backend/dist/platform/jobs/domainJobHandlers.d.ts.map +1 -1
  94. package/backend/dist/platform/jobs/domainJobHandlers.js +374 -4
  95. package/backend/dist/platform/jobs/domainJobHandlers.js.map +1 -1
  96. package/backend/dist/platform/jobs/embeddedDurableJobWorker.d.ts.map +1 -1
  97. package/backend/dist/platform/jobs/embeddedDurableJobWorker.js +4 -2
  98. package/backend/dist/platform/jobs/embeddedDurableJobWorker.js.map +1 -1
  99. package/backend/dist/platform/jobs/postgresDurableJobWorker.d.ts.map +1 -1
  100. package/backend/dist/platform/jobs/postgresDurableJobWorker.js +4 -2
  101. package/backend/dist/platform/jobs/postgresDurableJobWorker.js.map +1 -1
  102. package/backend/dist/platform/storage/pgVectorStore.d.ts +2 -1
  103. package/backend/dist/platform/storage/pgVectorStore.d.ts.map +1 -1
  104. package/backend/dist/platform/storage/pgVectorStore.js +51 -0
  105. package/backend/dist/platform/storage/pgVectorStore.js.map +1 -1
  106. package/backend/dist/platform/storage/platformDomainRepositories.d.ts +8 -2
  107. package/backend/dist/platform/storage/platformDomainRepositories.d.ts.map +1 -1
  108. package/backend/dist/platform/storage/postgresPlatformDomainRepositories.d.ts.map +1 -1
  109. package/backend/dist/platform/storage/postgresPlatformDomainRepositories.js +25 -4
  110. package/backend/dist/platform/storage/postgresPlatformDomainRepositories.js.map +1 -1
  111. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.d.ts +2 -1
  112. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.d.ts.map +1 -1
  113. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.js +35 -0
  114. package/backend/dist/platform/storage/sqliteEncryptedVectorStore.js.map +1 -1
  115. package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.d.ts.map +1 -1
  116. package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.js +26 -3
  117. package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.js.map +1 -1
  118. package/backend/dist/platform/storage/vectorStore.d.ts +14 -0
  119. package/backend/dist/platform/storage/vectorStore.d.ts.map +1 -1
  120. package/backend/dist/platform/storage/vectorStore.js.map +1 -1
  121. package/backend/dist/routes/access.d.ts.map +1 -1
  122. package/backend/dist/routes/access.js +84 -3
  123. package/backend/dist/routes/access.js.map +1 -1
  124. package/backend/dist/routes/auth.d.ts.map +1 -1
  125. package/backend/dist/routes/auth.js +419 -1
  126. package/backend/dist/routes/auth.js.map +1 -1
  127. package/backend/dist/routes/calendar.d.ts.map +1 -1
  128. package/backend/dist/routes/calendar.js +125 -22
  129. package/backend/dist/routes/calendar.js.map +1 -1
  130. package/backend/dist/routes/channels.d.ts +3 -0
  131. package/backend/dist/routes/channels.d.ts.map +1 -0
  132. package/backend/dist/routes/channels.js +447 -0
  133. package/backend/dist/routes/channels.js.map +1 -0
  134. package/backend/dist/routes/chat.d.ts.map +1 -1
  135. package/backend/dist/routes/chat.js +195 -7
  136. package/backend/dist/routes/chat.js.map +1 -1
  137. package/backend/dist/routes/costs.d.ts +3 -0
  138. package/backend/dist/routes/costs.d.ts.map +1 -0
  139. package/backend/dist/routes/costs.js +191 -0
  140. package/backend/dist/routes/costs.js.map +1 -0
  141. package/backend/dist/routes/documents.d.ts.map +1 -1
  142. package/backend/dist/routes/documents.js +68 -21
  143. package/backend/dist/routes/documents.js.map +1 -1
  144. package/backend/dist/routes/evaluations.d.ts +3 -0
  145. package/backend/dist/routes/evaluations.d.ts.map +1 -0
  146. package/backend/dist/routes/evaluations.js +352 -0
  147. package/backend/dist/routes/evaluations.js.map +1 -0
  148. package/backend/dist/routes/imageGen.d.ts.map +1 -1
  149. package/backend/dist/routes/imageGen.js +2 -1
  150. package/backend/dist/routes/imageGen.js.map +1 -1
  151. package/backend/dist/routes/media.d.ts.map +1 -1
  152. package/backend/dist/routes/media.js +228 -7
  153. package/backend/dist/routes/media.js.map +1 -1
  154. package/backend/dist/routes/notes.d.ts.map +1 -1
  155. package/backend/dist/routes/notes.js +149 -36
  156. package/backend/dist/routes/notes.js.map +1 -1
  157. package/backend/dist/routes/notifications.d.ts +3 -0
  158. package/backend/dist/routes/notifications.d.ts.map +1 -0
  159. package/backend/dist/routes/notifications.js +213 -0
  160. package/backend/dist/routes/notifications.js.map +1 -0
  161. package/backend/dist/routes/openaiCompat.d.ts +3 -0
  162. package/backend/dist/routes/openaiCompat.d.ts.map +1 -0
  163. package/backend/dist/routes/openaiCompat.js +314 -0
  164. package/backend/dist/routes/openaiCompat.js.map +1 -0
  165. package/backend/dist/routes/personas.js +1 -1
  166. package/backend/dist/routes/personas.js.map +1 -1
  167. package/backend/dist/routes/prompts.d.ts +3 -0
  168. package/backend/dist/routes/prompts.d.ts.map +1 -0
  169. package/backend/dist/routes/prompts.js +173 -0
  170. package/backend/dist/routes/prompts.js.map +1 -0
  171. package/backend/dist/routes/push.d.ts +3 -0
  172. package/backend/dist/routes/push.d.ts.map +1 -0
  173. package/backend/dist/routes/push.js +105 -0
  174. package/backend/dist/routes/push.js.map +1 -0
  175. package/backend/dist/routes/recovery.d.ts +3 -0
  176. package/backend/dist/routes/recovery.d.ts.map +1 -0
  177. package/backend/dist/routes/recovery.js +83 -0
  178. package/backend/dist/routes/recovery.js.map +1 -0
  179. package/backend/dist/routes/search.d.ts.map +1 -1
  180. package/backend/dist/routes/search.js +29 -1
  181. package/backend/dist/routes/search.js.map +1 -1
  182. package/backend/dist/routes/skills.d.ts +3 -0
  183. package/backend/dist/routes/skills.d.ts.map +1 -0
  184. package/backend/dist/routes/skills.js +246 -0
  185. package/backend/dist/routes/skills.js.map +1 -0
  186. package/backend/dist/routes/stt.d.ts.map +1 -1
  187. package/backend/dist/routes/stt.js +2 -1
  188. package/backend/dist/routes/stt.js.map +1 -1
  189. package/backend/dist/routes/tools.d.ts +3 -0
  190. package/backend/dist/routes/tools.d.ts.map +1 -0
  191. package/backend/dist/routes/tools.js +359 -0
  192. package/backend/dist/routes/tools.js.map +1 -0
  193. package/backend/dist/routes/tts.d.ts.map +1 -1
  194. package/backend/dist/routes/tts.js +63 -8
  195. package/backend/dist/routes/tts.js.map +1 -1
  196. package/backend/dist/routes/users.d.ts.map +1 -1
  197. package/backend/dist/routes/users.js +35 -0
  198. package/backend/dist/routes/users.js.map +1 -1
  199. package/backend/dist/routes/voiceAccess.d.ts +3 -0
  200. package/backend/dist/routes/voiceAccess.d.ts.map +1 -0
  201. package/backend/dist/routes/voiceAccess.js +102 -0
  202. package/backend/dist/routes/voiceAccess.js.map +1 -0
  203. package/backend/dist/services/apiTokenService.d.ts +2 -2
  204. package/backend/dist/services/apiTokenService.d.ts.map +1 -1
  205. package/backend/dist/services/apiTokenService.js +7 -0
  206. package/backend/dist/services/apiTokenService.js.map +1 -1
  207. package/backend/dist/services/authService.d.ts +6 -0
  208. package/backend/dist/services/authService.d.ts.map +1 -1
  209. package/backend/dist/services/authService.js +9 -0
  210. package/backend/dist/services/authService.js.map +1 -1
  211. package/backend/dist/services/authorizationService.d.ts +8 -4
  212. package/backend/dist/services/authorizationService.d.ts.map +1 -1
  213. package/backend/dist/services/authorizationService.js +31 -1
  214. package/backend/dist/services/authorizationService.js.map +1 -1
  215. package/backend/dist/services/automationSchedulerService.d.ts.map +1 -1
  216. package/backend/dist/services/automationSchedulerService.js +40 -3
  217. package/backend/dist/services/automationSchedulerService.js.map +1 -1
  218. package/backend/dist/services/automationService.d.ts +4 -1
  219. package/backend/dist/services/automationService.d.ts.map +1 -1
  220. package/backend/dist/services/automationService.js +25 -2
  221. package/backend/dist/services/automationService.js.map +1 -1
  222. package/backend/dist/services/builtinToolsService.d.ts +20 -0
  223. package/backend/dist/services/builtinToolsService.d.ts.map +1 -0
  224. package/backend/dist/services/builtinToolsService.js +661 -0
  225. package/backend/dist/services/builtinToolsService.js.map +1 -0
  226. package/backend/dist/services/calendarService.d.ts +74 -0
  227. package/backend/dist/services/calendarService.d.ts.map +1 -0
  228. package/backend/dist/services/calendarService.js +339 -0
  229. package/backend/dist/services/calendarService.js.map +1 -0
  230. package/backend/dist/services/channelService.d.ts +195 -0
  231. package/backend/dist/services/channelService.d.ts.map +1 -0
  232. package/backend/dist/services/channelService.js +1057 -0
  233. package/backend/dist/services/channelService.js.map +1 -0
  234. package/backend/dist/services/chatRequestService.d.ts.map +1 -1
  235. package/backend/dist/services/chatRequestService.js +29 -1
  236. package/backend/dist/services/chatRequestService.js.map +1 -1
  237. package/backend/dist/services/chatService.d.ts +61 -1
  238. package/backend/dist/services/chatService.d.ts.map +1 -1
  239. package/backend/dist/services/chatService.js +218 -1
  240. package/backend/dist/services/chatService.js.map +1 -1
  241. package/backend/dist/services/chatToolRuntimeService.d.ts +55 -0
  242. package/backend/dist/services/chatToolRuntimeService.d.ts.map +1 -0
  243. package/backend/dist/services/chatToolRuntimeService.js +389 -0
  244. package/backend/dist/services/chatToolRuntimeService.js.map +1 -0
  245. package/backend/dist/services/costGovernanceService.d.ts +102 -0
  246. package/backend/dist/services/costGovernanceService.d.ts.map +1 -0
  247. package/backend/dist/services/costGovernanceService.js +463 -0
  248. package/backend/dist/services/costGovernanceService.js.map +1 -0
  249. package/backend/dist/services/dataArchiveService.d.ts +6 -0
  250. package/backend/dist/services/dataArchiveService.d.ts.map +1 -1
  251. package/backend/dist/services/dataArchiveService.js +26 -4
  252. package/backend/dist/services/dataArchiveService.js.map +1 -1
  253. package/backend/dist/services/documentMediaExtractionService.d.ts +40 -0
  254. package/backend/dist/services/documentMediaExtractionService.d.ts.map +1 -0
  255. package/backend/dist/services/documentMediaExtractionService.js +157 -0
  256. package/backend/dist/services/documentMediaExtractionService.js.map +1 -0
  257. package/backend/dist/services/documentService.d.ts +64 -1
  258. package/backend/dist/services/documentService.d.ts.map +1 -1
  259. package/backend/dist/services/documentService.js +342 -83
  260. package/backend/dist/services/documentService.js.map +1 -1
  261. package/backend/dist/services/durableChatGenerationService.d.ts +9 -0
  262. package/backend/dist/services/durableChatGenerationService.d.ts.map +1 -1
  263. package/backend/dist/services/durableChatGenerationService.js +152 -11
  264. package/backend/dist/services/durableChatGenerationService.js.map +1 -1
  265. package/backend/dist/services/encryptionService.d.ts +6 -0
  266. package/backend/dist/services/encryptionService.d.ts.map +1 -1
  267. package/backend/dist/services/encryptionService.js +12 -0
  268. package/backend/dist/services/encryptionService.js.map +1 -1
  269. package/backend/dist/services/evaluationService.d.ts +111 -0
  270. package/backend/dist/services/evaluationService.d.ts.map +1 -0
  271. package/backend/dist/services/evaluationService.js +403 -0
  272. package/backend/dist/services/evaluationService.js.map +1 -0
  273. package/backend/dist/services/galleryService.d.ts +6 -0
  274. package/backend/dist/services/galleryService.d.ts.map +1 -1
  275. package/backend/dist/services/galleryService.js +26 -0
  276. package/backend/dist/services/galleryService.js.map +1 -1
  277. package/backend/dist/services/mcpClientService.d.ts +29 -0
  278. package/backend/dist/services/mcpClientService.d.ts.map +1 -0
  279. package/backend/dist/services/mcpClientService.js +189 -0
  280. package/backend/dist/services/mcpClientService.js.map +1 -0
  281. package/backend/dist/services/mfaService.d.ts +64 -0
  282. package/backend/dist/services/mfaService.d.ts.map +1 -0
  283. package/backend/dist/services/mfaService.js +263 -0
  284. package/backend/dist/services/mfaService.js.map +1 -0
  285. package/backend/dist/services/noteService.d.ts +71 -0
  286. package/backend/dist/services/noteService.d.ts.map +1 -0
  287. package/backend/dist/services/noteService.js +397 -0
  288. package/backend/dist/services/noteService.js.map +1 -0
  289. package/backend/dist/services/notificationService.d.ts +60 -0
  290. package/backend/dist/services/notificationService.d.ts.map +1 -0
  291. package/backend/dist/services/notificationService.js +340 -0
  292. package/backend/dist/services/notificationService.js.map +1 -0
  293. package/backend/dist/services/openApiToolService.d.ts +38 -0
  294. package/backend/dist/services/openApiToolService.d.ts.map +1 -0
  295. package/backend/dist/services/openApiToolService.js +254 -0
  296. package/backend/dist/services/openApiToolService.js.map +1 -0
  297. package/backend/dist/services/personaService.d.ts +18 -2
  298. package/backend/dist/services/personaService.d.ts.map +1 -1
  299. package/backend/dist/services/personaService.js +113 -4
  300. package/backend/dist/services/personaService.js.map +1 -1
  301. package/backend/dist/services/pluginImageGenerationService.d.ts +19 -0
  302. package/backend/dist/services/pluginImageGenerationService.d.ts.map +1 -1
  303. package/backend/dist/services/pluginImageGenerationService.js +121 -0
  304. package/backend/dist/services/pluginImageGenerationService.js.map +1 -1
  305. package/backend/dist/services/pluginService.d.ts +7 -0
  306. package/backend/dist/services/pluginService.d.ts.map +1 -1
  307. package/backend/dist/services/pluginService.js +8 -2
  308. package/backend/dist/services/pluginService.js.map +1 -1
  309. package/backend/dist/services/preferencesService.d.ts +1 -1
  310. package/backend/dist/services/preferencesService.d.ts.map +1 -1
  311. package/backend/dist/services/preferencesService.js +1 -0
  312. package/backend/dist/services/preferencesService.js.map +1 -1
  313. package/backend/dist/services/promptService.d.ts +74 -0
  314. package/backend/dist/services/promptService.d.ts.map +1 -0
  315. package/backend/dist/services/promptService.js +513 -0
  316. package/backend/dist/services/promptService.js.map +1 -0
  317. package/backend/dist/services/recoveryDrillService.d.ts +57 -0
  318. package/backend/dist/services/recoveryDrillService.d.ts.map +1 -0
  319. package/backend/dist/services/recoveryDrillService.js +363 -0
  320. package/backend/dist/services/recoveryDrillService.js.map +1 -0
  321. package/backend/dist/services/resourceGrantService.d.ts.map +1 -1
  322. package/backend/dist/services/resourceGrantService.js +62 -17
  323. package/backend/dist/services/resourceGrantService.js.map +1 -1
  324. package/backend/dist/services/searchService.d.ts +29 -0
  325. package/backend/dist/services/searchService.d.ts.map +1 -0
  326. package/backend/dist/services/searchService.js +0 -0
  327. package/backend/dist/services/searchService.js.map +1 -0
  328. package/backend/dist/services/securityAuditService.d.ts.map +1 -1
  329. package/backend/dist/services/securityAuditService.js +2 -1
  330. package/backend/dist/services/securityAuditService.js.map +1 -1
  331. package/backend/dist/services/sharedResourceAccess.d.ts +25 -0
  332. package/backend/dist/services/sharedResourceAccess.d.ts.map +1 -0
  333. package/backend/dist/services/sharedResourceAccess.js +59 -0
  334. package/backend/dist/services/sharedResourceAccess.js.map +1 -0
  335. package/backend/dist/services/skillService.d.ts +137 -0
  336. package/backend/dist/services/skillService.d.ts.map +1 -0
  337. package/backend/dist/services/skillService.js +852 -0
  338. package/backend/dist/services/skillService.js.map +1 -0
  339. package/backend/dist/services/toolAccessService.d.ts +9 -0
  340. package/backend/dist/services/toolAccessService.d.ts.map +1 -0
  341. package/backend/dist/services/toolAccessService.js +60 -0
  342. package/backend/dist/services/toolAccessService.js.map +1 -0
  343. package/backend/dist/services/toolApprovalService.d.ts +34 -0
  344. package/backend/dist/services/toolApprovalService.d.ts.map +1 -0
  345. package/backend/dist/services/toolApprovalService.js +188 -0
  346. package/backend/dist/services/toolApprovalService.js.map +1 -0
  347. package/backend/dist/services/toolGatewayService.d.ts +53 -0
  348. package/backend/dist/services/toolGatewayService.d.ts.map +1 -0
  349. package/backend/dist/services/toolGatewayService.js +249 -0
  350. package/backend/dist/services/toolGatewayService.js.map +1 -0
  351. package/backend/dist/services/toolServerService.d.ts +57 -0
  352. package/backend/dist/services/toolServerService.d.ts.map +1 -0
  353. package/backend/dist/services/toolServerService.js +521 -0
  354. package/backend/dist/services/toolServerService.js.map +1 -0
  355. package/backend/dist/services/voiceAccessService.d.ts +10 -0
  356. package/backend/dist/services/voiceAccessService.d.ts.map +1 -0
  357. package/backend/dist/services/voiceAccessService.js +74 -0
  358. package/backend/dist/services/voiceAccessService.js.map +1 -0
  359. package/backend/dist/services/voiceProfileService.d.ts +20 -0
  360. package/backend/dist/services/voiceProfileService.d.ts.map +1 -1
  361. package/backend/dist/services/voiceProfileService.js +60 -0
  362. package/backend/dist/services/voiceProfileService.js.map +1 -1
  363. package/backend/dist/services/webPushService.d.ts +71 -0
  364. package/backend/dist/services/webPushService.d.ts.map +1 -0
  365. package/backend/dist/services/webPushService.js +328 -0
  366. package/backend/dist/services/webPushService.js.map +1 -0
  367. package/backend/dist/services/webauthnService.d.ts +58 -0
  368. package/backend/dist/services/webauthnService.d.ts.map +1 -0
  369. package/backend/dist/services/webauthnService.js +300 -0
  370. package/backend/dist/services/webauthnService.js.map +1 -0
  371. package/backend/dist/storage.d.ts +28 -1
  372. package/backend/dist/storage.d.ts.map +1 -1
  373. package/backend/dist/storage.js +100 -1
  374. package/backend/dist/storage.js.map +1 -1
  375. package/backend/dist/storageMappers.d.ts +2 -2
  376. package/backend/dist/storageMappers.d.ts.map +1 -1
  377. package/backend/dist/storageMappers.js.map +1 -1
  378. package/backend/dist/types/index.d.ts +199 -3
  379. package/backend/dist/types/index.d.ts.map +1 -1
  380. package/backend/dist/types/index.js.map +1 -1
  381. package/backend/dist/types/tools.d.ts +137 -0
  382. package/backend/dist/types/tools.d.ts.map +1 -0
  383. package/backend/dist/types/tools.js +25 -0
  384. package/backend/dist/types/tools.js.map +1 -0
  385. package/backend/dist/utils/chatDocumentContext.d.ts +26 -1
  386. package/backend/dist/utils/chatDocumentContext.d.ts.map +1 -1
  387. package/backend/dist/utils/chatDocumentContext.js +87 -18
  388. package/backend/dist/utils/chatDocumentContext.js.map +1 -1
  389. package/backend/dist/utils/documentExtraction.d.ts +97 -0
  390. package/backend/dist/utils/documentExtraction.d.ts.map +1 -0
  391. package/backend/dist/utils/documentExtraction.js +651 -0
  392. package/backend/dist/utils/documentExtraction.js.map +1 -0
  393. package/backend/dist/utils/hybridRetrieval.d.ts +34 -0
  394. package/backend/dist/utils/hybridRetrieval.d.ts.map +1 -0
  395. package/backend/dist/utils/hybridRetrieval.js +119 -0
  396. package/backend/dist/utils/hybridRetrieval.js.map +1 -0
  397. package/backend/dist/utils/icsCalendar.d.ts +27 -0
  398. package/backend/dist/utils/icsCalendar.d.ts.map +1 -0
  399. package/backend/dist/utils/icsCalendar.js +313 -0
  400. package/backend/dist/utils/icsCalendar.js.map +1 -0
  401. package/backend/dist/utils/imageEditUpload.d.ts +29 -0
  402. package/backend/dist/utils/imageEditUpload.d.ts.map +1 -0
  403. package/backend/dist/utils/imageEditUpload.js +82 -0
  404. package/backend/dist/utils/imageEditUpload.js.map +1 -0
  405. package/backend/dist/utils/logRedaction.d.ts +2 -0
  406. package/backend/dist/utils/logRedaction.d.ts.map +1 -0
  407. package/backend/dist/utils/logRedaction.js +66 -0
  408. package/backend/dist/utils/logRedaction.js.map +1 -0
  409. package/backend/dist/utils/logger.d.ts +3 -0
  410. package/backend/dist/utils/logger.d.ts.map +1 -1
  411. package/backend/dist/utils/logger.js +63 -20
  412. package/backend/dist/utils/logger.js.map +1 -1
  413. package/backend/dist/utils/pluginChatAdapter.d.ts +5 -1
  414. package/backend/dist/utils/pluginChatAdapter.d.ts.map +1 -1
  415. package/backend/dist/utils/pluginChatAdapter.js +83 -0
  416. package/backend/dist/utils/pluginChatAdapter.js.map +1 -1
  417. package/backend/dist/utils/pluginDefinitionTrust.js +1 -1
  418. package/backend/dist/utils/resourceLimits.d.ts +61 -2
  419. package/backend/dist/utils/resourceLimits.d.ts.map +1 -1
  420. package/backend/dist/utils/resourceLimits.js +59 -0
  421. package/backend/dist/utils/resourceLimits.js.map +1 -1
  422. package/backend/dist/utils/toolEgress.d.ts +23 -0
  423. package/backend/dist/utils/toolEgress.d.ts.map +1 -0
  424. package/backend/dist/utils/toolEgress.js +169 -0
  425. package/backend/dist/utils/toolEgress.js.map +1 -0
  426. package/backend/dist/utils/totp.d.ts +19 -0
  427. package/backend/dist/utils/totp.d.ts.map +1 -0
  428. package/backend/dist/utils/totp.js +117 -0
  429. package/backend/dist/utils/totp.js.map +1 -0
  430. package/backend/dist/utils/webauthn.d.ts +43 -0
  431. package/backend/dist/utils/webauthn.d.ts.map +1 -0
  432. package/backend/dist/utils/webauthn.js +294 -0
  433. package/backend/dist/utils/webauthn.js.map +1 -0
  434. package/backend/dist/utils/websocketMessages.d.ts +6 -0
  435. package/backend/dist/utils/websocketMessages.d.ts.map +1 -1
  436. package/backend/dist/utils/websocketMessages.js +8 -0
  437. package/backend/dist/utils/websocketMessages.js.map +1 -1
  438. package/backend/dist/utils/zipArchive.d.ts +32 -0
  439. package/backend/dist/utils/zipArchive.d.ts.map +1 -0
  440. package/backend/dist/utils/zipArchive.js +171 -0
  441. package/backend/dist/utils/zipArchive.js.map +1 -0
  442. package/backend/dist/websocketServer.d.ts.map +1 -1
  443. package/backend/dist/websocketServer.js +142 -35
  444. package/backend/dist/websocketServer.js.map +1 -1
  445. package/backend/package.json +1 -1
  446. package/frontend/dist/artifact-runtime/.build-fingerprint +1 -1
  447. package/frontend/dist/assets/index-DBAf_VaS.js +47 -0
  448. package/frontend/dist/assets/index-kn_Axjts.css +1 -0
  449. package/frontend/dist/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  450. package/frontend/dist/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
  451. package/frontend/dist/fonts/jetbrains-mono.css +24 -0
  452. package/frontend/dist/index.html +27 -20
  453. package/frontend/dist/js/ArtifactContainer-C9zNtCay.js +1 -0
  454. package/frontend/dist/js/{ArtifactDemoPage-BeNs3gHp.js → ArtifactDemoPage-C-EBFBCt.js} +1 -1
  455. package/frontend/dist/js/ArtifactSandboxFrame-CTA1j_d1.js +111 -0
  456. package/frontend/dist/js/ArtifactSlideOutPanel-BOENbOiv.js +1 -0
  457. package/frontend/dist/js/AutomationsPage-C58MQ4te.js +1 -0
  458. package/frontend/dist/js/{Button-BPf_pOxH.js → Button-C0r3J9G_.js} +1 -1
  459. package/frontend/dist/js/CalendarPage-BcdAZ292.js +1 -0
  460. package/frontend/dist/js/ChannelsPage-DPr9Yajs.js +1 -0
  461. package/frontend/dist/js/ChatPage-DsbB748s.js +14 -0
  462. package/frontend/dist/js/EvaluationsPage-FWIJu2fI.js +2 -0
  463. package/frontend/dist/js/FirstTimeSetup-Cy-w6xko.js +1 -0
  464. package/frontend/dist/js/GalleryPage-DDAtxM-x.js +1 -0
  465. package/frontend/dist/js/HomePage-DLKwmE7n.js +1 -0
  466. package/frontend/dist/js/LibreClawPage-vKOj-9w9.js +1 -0
  467. package/frontend/dist/js/{ModelManagerModals-DcLGYN3l.js → ModelManagerModals-BY5TK38_.js} +1 -1
  468. package/frontend/dist/js/ModelSelector-CoCS6MZ9.js +1 -0
  469. package/frontend/dist/js/NotesPage-BElIpXgA.js +3 -0
  470. package/frontend/dist/js/OptimizedSyntaxHighlighter-xgyE3dRA.js +1 -0
  471. package/frontend/dist/js/PersonasPage-P40jVbfU.js +13 -0
  472. package/frontend/dist/js/PluginUsagePage-CKUEPCJN.js +1 -0
  473. package/frontend/dist/js/RichMessageContent-Bm2WaEGi.js +3 -0
  474. package/frontend/dist/js/SettingsModal-DxSGzx9A.js +19 -0
  475. package/frontend/dist/js/{SettingsToggle-woG_WebF.js → SettingsToggle-BIVyfGjl.js} +1 -1
  476. package/frontend/dist/js/SystemPage-BT07dTMT.js +1 -0
  477. package/frontend/dist/js/UserManagementPage-B9Vz5cm3.js +1 -0
  478. package/frontend/dist/js/WorkPage-qoN_e_jK.js +6 -0
  479. package/frontend/dist/js/api-D7Ff5M2Q.js +14 -0
  480. package/frontend/dist/js/appStore-DmV5wqDz.js +2 -0
  481. package/frontend/dist/js/ar-YHHTVP8f.js +2 -0
  482. package/frontend/dist/js/artifactHtml-DDmfpcB0.js +41 -0
  483. package/frontend/dist/js/{authStore-DbMdvNgJ.js → authStore-BYW8-kP6.js} +3 -3
  484. package/frontend/dist/js/{babel-BsYoxD-7.js → babel-DSqqk5kt.js} +1 -1
  485. package/frontend/dist/js/bn-C8VTxBVl.js +2 -0
  486. package/frontend/dist/js/chatStore-D-NTlArO.js +3 -0
  487. package/frontend/dist/js/{config-DSOYQ9a3.js → config-BqZ5304Q.js} +1 -1
  488. package/frontend/dist/js/cs-CCngB5fC.js +2 -0
  489. package/frontend/dist/js/da-CM2PP2ma.js +2 -0
  490. package/frontend/dist/js/de-BqVkD-dw.js +2 -0
  491. package/frontend/dist/js/es-BtWsWBjZ.js +2 -0
  492. package/frontend/dist/js/{estree-BcnBcOAx.js → estree-DPkpBuOU.js} +3 -3
  493. package/frontend/dist/js/evaluationsApi-Dnf2dTd-.js +1 -0
  494. package/frontend/dist/js/fr-C68w9abT.js +2 -0
  495. package/frontend/dist/js/hi-DtBlcyXZ.js +2 -0
  496. package/frontend/dist/js/{html-DkY55Jys.js → html-BpGUXG8E.js} +2 -2
  497. package/frontend/dist/js/id-BWzIu8tn.js +2 -0
  498. package/frontend/dist/js/is-yeFwu4dJ.js +2 -0
  499. package/frontend/dist/js/it-AxEMG8Jg.js +2 -0
  500. package/frontend/dist/js/ja-B-VyokB8.js +2 -0
  501. package/frontend/dist/js/ko-CvwzYr9k.js +2 -0
  502. package/frontend/dist/js/{markdown-IZSI9DXw.js → markdown-DCBRWJSK.js} +3 -3
  503. package/frontend/dist/js/markdown-core-BK7WVOb7.js +32 -0
  504. package/frontend/dist/js/{markdown-math-DK846gXU.js → markdown-math-e1ByS3KY.js} +5 -5
  505. package/frontend/dist/js/messageCodeStyles-BQwf7sSz.js +2 -0
  506. package/frontend/dist/js/ms-BhEwb4b7.js +2 -0
  507. package/frontend/dist/js/nl-Dq-QRx1L.js +2 -0
  508. package/frontend/dist/js/pl-BNEy8mQm.js +2 -0
  509. package/frontend/dist/js/{postcss-BJ0okz7n.js → postcss-Bg15EcFE.js} +1 -1
  510. package/frontend/dist/js/pt-_3Jl3FdY.js +2 -0
  511. package/frontend/dist/js/{react-vendor-C5yShNlM.js → react-vendor-BG_HPV6X.js} +1 -1
  512. package/frontend/dist/js/router-vendor-CbexJIsv.js +1 -0
  513. package/frontend/dist/js/ru-IhQiJ8ta.js +2 -0
  514. package/frontend/dist/js/{standalone-Csw3jfJ_.js → standalone-BFrK0R2A.js} +3 -3
  515. package/frontend/dist/js/sv-DWunXUNz.js +2 -0
  516. package/frontend/dist/js/{syntax-highlight-3EEX6CGL.js → syntax-highlight-P2zo9o06.js} +4 -4
  517. package/frontend/dist/js/th-DVnW2yyh.js +2 -0
  518. package/frontend/dist/js/tr-Do8CFmhR.js +2 -0
  519. package/frontend/dist/js/{typescript-CnF4UVx7.js → typescript-DYVyvmm_.js} +1 -1
  520. package/frontend/dist/js/ui-db9meqAB.js +2 -0
  521. package/frontend/dist/js/ui-vendor-C0DZ8-Cm.js +177 -0
  522. package/frontend/dist/js/uk-BNPKIZga.js +2 -0
  523. package/frontend/dist/js/{useTranslation-Bho4HCyg.js → useTranslation-5b5UhAc9.js} +1 -1
  524. package/frontend/dist/js/{utils-D9W1Lnkn.js → utils-BGrdX9ie.js} +1 -1
  525. package/frontend/dist/js/{utils-vendor-DWh39_3x.js → utils-vendor-9bQ62WeD.js} +1 -1
  526. package/frontend/dist/js/vi-CZMFo9je.js +2 -0
  527. package/frontend/dist/js/{workApi-C3uy9EDT.js → workApi-CcprMH_1.js} +3 -3
  528. package/frontend/dist/js/{yaml-DzT5hBSH.js → yaml-DUeWcOOH.js} +2 -2
  529. package/frontend/dist/js/zh-Pg0881v0.js +2 -0
  530. package/frontend/dist/manifest.webmanifest +32 -0
  531. package/frontend/dist/sw.js +125 -0
  532. package/package.json +2 -2
  533. package/plugins/openai.json +52 -9
  534. package/frontend/dist/assets/index-6o-Hc2wS.js +0 -41
  535. package/frontend/dist/assets/index-D-pM957b.css +0 -1
  536. package/frontend/dist/js/ArtifactContainer-CpUi6ekF.js +0 -1
  537. package/frontend/dist/js/ArtifactSandboxFrame-qsj_Y3LV.js +0 -151
  538. package/frontend/dist/js/ArtifactSlideOutPanel-CSFRluWA.js +0 -1
  539. package/frontend/dist/js/AutomationsPage-KUeWoZNv.js +0 -1
  540. package/frontend/dist/js/CalendarPage-D9I972w8.js +0 -1
  541. package/frontend/dist/js/ChatPage-CmBx4oAL.js +0 -16
  542. package/frontend/dist/js/FirstTimeSetup-Df0YJni3.js +0 -1
  543. package/frontend/dist/js/GalleryPage-bLCrdJN_.js +0 -1
  544. package/frontend/dist/js/HomePage-Uc6PEg-U.js +0 -1
  545. package/frontend/dist/js/LibreClawPage-CG63DHg1.js +0 -1
  546. package/frontend/dist/js/ModelSelector-yxJC64rM.js +0 -1
  547. package/frontend/dist/js/ModelsPage-1R_TxgmJ.js +0 -2
  548. package/frontend/dist/js/NotesPage-L65j5HoW.js +0 -1
  549. package/frontend/dist/js/OptimizedSyntaxHighlighter-Dz7D1LPB.js +0 -1
  550. package/frontend/dist/js/PersonasPage-CKeBpHIH.js +0 -13
  551. package/frontend/dist/js/PluginUsagePage-BX1SKMbW.js +0 -1
  552. package/frontend/dist/js/RichMessageContent-DiySVYGB.js +0 -3
  553. package/frontend/dist/js/SettingsModal-68SG1uZ2.js +0 -1
  554. package/frontend/dist/js/SystemPage-CytpZiQV.js +0 -1
  555. package/frontend/dist/js/UserManagementPage-BzJBvZiS.js +0 -1
  556. package/frontend/dist/js/WorkPage-C8gr0QJe.js +0 -6
  557. package/frontend/dist/js/api-BDtqBtqw.js +0 -14
  558. package/frontend/dist/js/appStore-0JnCUlEq.js +0 -2
  559. package/frontend/dist/js/ar-CSpi37Gr.js +0 -2
  560. package/frontend/dist/js/bn-CyH1BeDD.js +0 -2
  561. package/frontend/dist/js/chatStore-CHQ2uINS.js +0 -3
  562. package/frontend/dist/js/cs-Cv2NfRK5.js +0 -2
  563. package/frontend/dist/js/da-D6D6IvPK.js +0 -2
  564. package/frontend/dist/js/de-BNoJJTrT.js +0 -2
  565. package/frontend/dist/js/es-kzo9C7q_.js +0 -2
  566. package/frontend/dist/js/fr-CjHYvJRp.js +0 -2
  567. package/frontend/dist/js/hi-BU98AdCQ.js +0 -2
  568. package/frontend/dist/js/id-CpdedejX.js +0 -2
  569. package/frontend/dist/js/is-Cp5dDWBH.js +0 -2
  570. package/frontend/dist/js/it-CuH_cK4U.js +0 -2
  571. package/frontend/dist/js/ja-BWXXXiHn.js +0 -2
  572. package/frontend/dist/js/ko-DQgv2M1t.js +0 -2
  573. package/frontend/dist/js/markdown-core-BmPPZiuD.js +0 -31
  574. package/frontend/dist/js/messageCodeStyles-BFNR9Wv6.js +0 -2
  575. package/frontend/dist/js/ms-DCcRoIcX.js +0 -2
  576. package/frontend/dist/js/nl-CbKR1_EU.js +0 -2
  577. package/frontend/dist/js/pl-CRyJZccn.js +0 -2
  578. package/frontend/dist/js/pt-BXYmHFGH.js +0 -2
  579. package/frontend/dist/js/router-vendor-DGFGM3EP.js +0 -1
  580. package/frontend/dist/js/ru-M9SyZG4b.js +0 -2
  581. package/frontend/dist/js/sv-DJ486LTU.js +0 -2
  582. package/frontend/dist/js/th-Ch7gKtZv.js +0 -2
  583. package/frontend/dist/js/tr-ClehWhSC.js +0 -2
  584. package/frontend/dist/js/ui-CC-K19wO.js +0 -2
  585. package/frontend/dist/js/ui-vendor-B1UgO3vd.js +0 -177
  586. package/frontend/dist/js/uk-BqAd5zHF.js +0 -2
  587. package/frontend/dist/js/vi-D9gjlDVB.js +0 -2
  588. package/frontend/dist/js/zh-DKTyHkiR.js +0 -2
@@ -34,17 +34,109 @@ const namedResource = (row) => ({
34
34
  });
35
35
  const note = (row) => ({
36
36
  ...row,
37
+ pinned: number(row.pinned ?? 0, 'note pinned'),
37
38
  created_at: number(row.created_at, 'note created_at'),
38
39
  updated_at: number(row.updated_at, 'note updated_at'),
39
40
  });
41
+ const noteRevision = (row) => ({
42
+ ...row,
43
+ created_at: number(row.created_at, 'note revision created_at'),
44
+ });
45
+ const noteAttachment = (row) => ({
46
+ ...row,
47
+ size: number(row.size, 'note attachment size'),
48
+ created_at: number(row.created_at, 'note attachment created_at'),
49
+ });
40
50
  const calendarEvent = (row) => ({
41
51
  ...row,
42
52
  start_at: number(row.start_at, 'calendar event start_at'),
43
53
  end_at: row.end_at === null ? null : number(row.end_at, 'calendar event end_at'),
44
54
  all_day: number(row.all_day, 'calendar event all_day'),
55
+ reminder_minutes: row.reminder_minutes === null
56
+ ? null
57
+ : number(row.reminder_minutes, 'calendar event reminder_minutes'),
58
+ last_reminded_occurrence: row.last_reminded_occurrence === null
59
+ ? null
60
+ : number(row.last_reminded_occurrence, 'calendar event last_reminded_occurrence'),
45
61
  created_at: number(row.created_at, 'calendar event created_at'),
46
62
  updated_at: number(row.updated_at, 'calendar event updated_at'),
47
63
  });
64
+ const calendar = (row) => ({
65
+ ...row,
66
+ created_at: number(row.created_at, 'calendar created_at'),
67
+ updated_at: number(row.updated_at, 'calendar updated_at'),
68
+ });
69
+ const channel = (row) => ({
70
+ ...row,
71
+ created_at: number(row.created_at, 'channel created_at'),
72
+ updated_at: number(row.updated_at, 'channel updated_at'),
73
+ archived_at: row.archived_at === null
74
+ ? null
75
+ : number(row.archived_at, 'channel archived_at'),
76
+ });
77
+ const channelMember = (row) => ({
78
+ ...row,
79
+ joined_at: number(row.joined_at, 'channel member joined_at'),
80
+ last_read_at: number(row.last_read_at, 'channel member last_read_at'),
81
+ });
82
+ const channelMessage = (row) => ({
83
+ ...row,
84
+ created_at: number(row.created_at, 'channel message created_at'),
85
+ updated_at: number(row.updated_at, 'channel message updated_at'),
86
+ edited_at: row.edited_at === null
87
+ ? null
88
+ : number(row.edited_at, 'channel message edited_at'),
89
+ deleted_at: row.deleted_at === null
90
+ ? null
91
+ : number(row.deleted_at, 'channel message deleted_at'),
92
+ pinned_at: row.pinned_at === null
93
+ ? null
94
+ : number(row.pinned_at, 'channel message pinned_at'),
95
+ });
96
+ const channelReaction = (row) => ({
97
+ ...row,
98
+ created_at: number(row.created_at, 'channel reaction created_at'),
99
+ });
100
+ const channelAttachment = (row) => ({
101
+ ...row,
102
+ size: number(row.size, 'channel attachment size'),
103
+ created_at: number(row.created_at, 'channel attachment created_at'),
104
+ });
105
+ const notification = (row) => ({
106
+ ...row,
107
+ created_at: number(row.created_at, 'notification created_at'),
108
+ read_at: row.read_at === null ? null : number(row.read_at, 'notification read_at'),
109
+ });
110
+ const webhookTarget = (row) => ({
111
+ ...row,
112
+ enabled: number(row.enabled, 'webhook target enabled'),
113
+ created_at: number(row.created_at, 'webhook target created_at'),
114
+ updated_at: number(row.updated_at, 'webhook target updated_at'),
115
+ });
116
+ const pushSubscription = (row) => ({
117
+ ...row,
118
+ created_at: number(row.created_at, 'push subscription created_at'),
119
+ last_used_at: row.last_used_at === null
120
+ ? null
121
+ : number(row.last_used_at, 'push subscription last_used_at'),
122
+ });
123
+ const recoveryDrill = (row) => ({
124
+ ...row,
125
+ started_at: number(row.started_at, 'recovery drill started_at'),
126
+ finished_at: row.finished_at === null
127
+ ? null
128
+ : number(row.finished_at, 'recovery drill finished_at'),
129
+ snapshot_bytes: row.snapshot_bytes === null
130
+ ? null
131
+ : number(row.snapshot_bytes, 'recovery drill snapshot_bytes'),
132
+ rpo_seconds: row.rpo_seconds === null
133
+ ? null
134
+ : number(row.rpo_seconds, 'recovery drill rpo_seconds'),
135
+ restore_ms: row.restore_ms === null
136
+ ? null
137
+ : number(row.restore_ms, 'recovery drill restore_ms'),
138
+ created_at: number(row.created_at, 'recovery drill created_at'),
139
+ });
48
140
  const automation = (row) => ({
49
141
  ...row,
50
142
  next_run_at: row.next_run_at === null
@@ -68,6 +160,66 @@ const automationRun = (row) => ({
68
160
  seen_at: row.seen_at === null ? null : number(row.seen_at, 'automation run seen_at'),
69
161
  created_at: number(row.created_at, 'automation run created_at'),
70
162
  });
163
+ const toolServer = (row) => ({
164
+ ...row,
165
+ spec_revision: number(row.spec_revision, 'tool server spec_revision'),
166
+ enabled: number(row.enabled, 'tool server enabled'),
167
+ timeout_ms: number(row.timeout_ms, 'tool server timeout_ms'),
168
+ max_response_bytes: number(row.max_response_bytes, 'tool server max_response_bytes'),
169
+ created_at: number(row.created_at, 'tool server created_at'),
170
+ updated_at: number(row.updated_at, 'tool server updated_at'),
171
+ });
172
+ const toolServerTool = (row) => ({
173
+ ...row,
174
+ side_effect: number(row.side_effect, 'tool side_effect'),
175
+ enabled: number(row.enabled, 'tool enabled'),
176
+ created_at: number(row.created_at, 'tool created_at'),
177
+ updated_at: number(row.updated_at, 'tool updated_at'),
178
+ });
179
+ const toolServerCredential = (row) => ({
180
+ ...row,
181
+ created_at: number(row.created_at, 'tool credential created_at'),
182
+ updated_at: number(row.updated_at, 'tool credential updated_at'),
183
+ });
184
+ const toolApproval = (row) => ({
185
+ ...row,
186
+ created_at: number(row.created_at, 'tool approval created_at'),
187
+ resolved_at: row.resolved_at === null
188
+ ? null
189
+ : number(row.resolved_at, 'tool approval resolved_at'),
190
+ expires_at: row.expires_at === null
191
+ ? null
192
+ : number(row.expires_at, 'tool approval expires_at'),
193
+ });
194
+ const prompt = (row) => ({
195
+ ...row,
196
+ version: number(row.version, 'prompt version'),
197
+ created_at: number(row.created_at, 'prompt created_at'),
198
+ updated_at: number(row.updated_at, 'prompt updated_at'),
199
+ });
200
+ const promptVersion = (row) => ({
201
+ ...row,
202
+ version: number(row.version, 'prompt revision version'),
203
+ created_at: number(row.created_at, 'prompt revision created_at'),
204
+ });
205
+ const skill = (row) => ({
206
+ ...row,
207
+ enabled: number(row.enabled, 'skill enabled'),
208
+ version: number(row.version, 'skill version'),
209
+ created_at: number(row.created_at, 'skill created_at'),
210
+ updated_at: number(row.updated_at, 'skill updated_at'),
211
+ });
212
+ const skillVersion = (row) => ({
213
+ ...row,
214
+ version: number(row.version, 'skill revision version'),
215
+ created_at: number(row.created_at, 'skill revision created_at'),
216
+ });
217
+ const skillFile = (row) => ({
218
+ ...row,
219
+ size: number(row.size, 'skill file size'),
220
+ created_at: number(row.created_at, 'skill file created_at'),
221
+ updated_at: number(row.updated_at, 'skill file updated_at'),
222
+ });
71
223
  const changes = (rowCount) => rowCount ?? 0;
72
224
  const lockOwner = async (client, userId) => {
73
225
  const result = await client.query('SELECT id FROM users WHERE id = $1 FOR UPDATE', [userId]);
@@ -251,6 +403,10 @@ class PostgresKnowledgeCollectionRepository {
251
403
  ORDER BY lower(name), name`, [userId]);
252
404
  return result.rows.map(namedResource);
253
405
  }
406
+ async findById(collectionId) {
407
+ const result = await this.database.query('SELECT * FROM knowledge_collections WHERE id = $1', [collectionId]);
408
+ return result.rows[0] ? namedResource(result.rows[0]) : null;
409
+ }
254
410
  async replace(collection) {
255
411
  await this.database.transaction(async (client) => {
256
412
  await lockOwner(client, collection.user_id);
@@ -282,7 +438,7 @@ class PostgresNoteRepository {
282
438
  async listByOwner(userId, maximum) {
283
439
  const result = await this.database.query(`SELECT * FROM notes
284
440
  WHERE user_id = $1
285
- ORDER BY updated_at DESC
441
+ ORDER BY pinned DESC, updated_at DESC
286
442
  LIMIT $2`, [userId, maximum]);
287
443
  return result.rows.map(note);
288
444
  }
@@ -303,17 +459,19 @@ class PostgresNoteRepository {
303
459
  throw new PersistenceResourceLimitError('note', maximum);
304
460
  }
305
461
  }
306
- const result = await client.query(`INSERT INTO notes (id, user_id, title, content, created_at, updated_at)
307
- VALUES ($1, $2, $3, $4, $5, $6)
462
+ const result = await client.query(`INSERT INTO notes (id, user_id, title, content, pinned, created_at, updated_at)
463
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
308
464
  ON CONFLICT (id) DO UPDATE SET
309
465
  title = EXCLUDED.title,
310
466
  content = EXCLUDED.content,
467
+ pinned = EXCLUDED.pinned,
311
468
  updated_at = EXCLUDED.updated_at
312
469
  WHERE notes.user_id = EXCLUDED.user_id`, [
313
470
  value.id,
314
471
  value.user_id,
315
472
  value.title,
316
473
  value.content,
474
+ value.pinned,
317
475
  value.created_at,
318
476
  value.updated_at,
319
477
  ]);
@@ -332,6 +490,8 @@ class PostgresNoteRepository {
332
490
  assign('title', patch.title);
333
491
  if (patch.content !== undefined)
334
492
  assign('content', patch.content);
493
+ if (patch.pinned !== undefined)
494
+ assign('pinned', patch.pinned);
335
495
  assign('updated_at', patch.updated_at);
336
496
  values.push(noteId, userId);
337
497
  const result = await this.database.query(`UPDATE notes SET ${assignments.join(', ')}
@@ -342,6 +502,100 @@ class PostgresNoteRepository {
342
502
  async deleteByOwner(noteId, userId) {
343
503
  return (changes((await this.database.query('DELETE FROM notes WHERE id = $1 AND user_id = $2', [noteId, userId])).rowCount) > 0);
344
504
  }
505
+ async findById(noteId) {
506
+ const result = await this.database.query('SELECT * FROM notes WHERE id = $1', [noteId]);
507
+ return result.rows[0] ? note(result.rows[0]) : null;
508
+ }
509
+ async patchById(noteId, patch) {
510
+ const assignments = [];
511
+ const values = [];
512
+ const assign = (column, value) => {
513
+ values.push(value);
514
+ assignments.push(`${column} = $${values.length}`);
515
+ };
516
+ if (patch.title !== undefined)
517
+ assign('title', patch.title);
518
+ if (patch.content !== undefined)
519
+ assign('content', patch.content);
520
+ if (patch.pinned !== undefined)
521
+ assign('pinned', patch.pinned);
522
+ assign('updated_at', patch.updated_at);
523
+ values.push(noteId);
524
+ const result = await this.database.query(`UPDATE notes SET ${assignments.join(', ')}
525
+ WHERE id = $${values.length}
526
+ RETURNING *`, values);
527
+ return result.rows[0] ? note(result.rows[0]) : null;
528
+ }
529
+ async listRevisions(noteId, maximum) {
530
+ const result = await this.database.query(`SELECT * FROM note_revisions
531
+ WHERE note_id = $1
532
+ ORDER BY created_at DESC, id DESC
533
+ LIMIT $2`, [noteId, maximum]);
534
+ return result.rows.map(noteRevision);
535
+ }
536
+ async findRevision(revisionId) {
537
+ const result = await this.database.query('SELECT * FROM note_revisions WHERE id = $1', [revisionId]);
538
+ return result.rows[0] ? noteRevision(result.rows[0]) : null;
539
+ }
540
+ async insertRevision(revision, maximum) {
541
+ await this.database.transaction(async (client) => {
542
+ // Strictly increasing per note so same-millisecond snapshots keep a
543
+ // deterministic order for listing, restore, and pruning.
544
+ await client.query(`INSERT INTO note_revisions (id, note_id, title, content, created_at)
545
+ SELECT $1, $2, $3, $4,
546
+ GREATEST($5::bigint, COALESCE(MAX(created_at), 0) + 1)
547
+ FROM note_revisions WHERE note_id = $2`, [
548
+ revision.id,
549
+ revision.note_id,
550
+ revision.title,
551
+ revision.content,
552
+ revision.created_at,
553
+ ]);
554
+ await client.query(`DELETE FROM note_revisions
555
+ WHERE note_id = $1
556
+ AND id NOT IN (
557
+ SELECT id FROM note_revisions
558
+ WHERE note_id = $1
559
+ ORDER BY created_at DESC, id DESC
560
+ LIMIT $2
561
+ )`, [revision.note_id, maximum]);
562
+ });
563
+ }
564
+ async listAttachments(noteId) {
565
+ const result = await this.database.query(`SELECT * FROM note_attachments
566
+ WHERE note_id = $1
567
+ ORDER BY created_at ASC, id ASC`, [noteId]);
568
+ return result.rows.map(noteAttachment);
569
+ }
570
+ async findAttachment(attachmentId) {
571
+ const result = await this.database.query('SELECT * FROM note_attachments WHERE id = $1', [attachmentId]);
572
+ return result.rows[0] ? noteAttachment(result.rows[0]) : null;
573
+ }
574
+ async insertAttachmentWithLimit(attachment, maximum) {
575
+ await this.database.transaction(async (client) => {
576
+ await client.query('SELECT id FROM notes WHERE id = $1 FOR UPDATE', [
577
+ attachment.note_id,
578
+ ]);
579
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM note_attachments WHERE note_id = $1', [attachment.note_id]);
580
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
581
+ throw new PersistenceResourceLimitError('note-attachment', maximum);
582
+ }
583
+ await client.query(`INSERT INTO note_attachments
584
+ (id, note_id, blob_id, filename, content_type, size, created_at)
585
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
586
+ attachment.id,
587
+ attachment.note_id,
588
+ attachment.blob_id,
589
+ attachment.filename,
590
+ attachment.content_type,
591
+ attachment.size,
592
+ attachment.created_at,
593
+ ]);
594
+ });
595
+ }
596
+ async deleteAttachment(attachmentId) {
597
+ return (changes((await this.database.query('DELETE FROM note_attachments WHERE id = $1', [attachmentId])).rowCount) > 0);
598
+ }
345
599
  }
346
600
  class PostgresCalendarEventRepository {
347
601
  constructor(database) {
@@ -361,10 +615,48 @@ class PostgresCalendarEventRepository {
361
615
  LIMIT $2`, [userId, maximum]);
362
616
  return result.rows.map(calendarEvent);
363
617
  }
618
+ async listByCalendarsBetween(calendarIds, from, to, maximum) {
619
+ if (calendarIds.length === 0)
620
+ return [];
621
+ const result = await this.database.query(`SELECT * FROM calendar_events
622
+ WHERE calendar_id = ANY($1::text[])
623
+ AND start_at >= $2 AND start_at < $3
624
+ ORDER BY start_at ASC
625
+ LIMIT $4`, [[...calendarIds], from, to, maximum]);
626
+ return result.rows.map(calendarEvent);
627
+ }
628
+ async listRecurringByCalendars(calendarIds, maximum) {
629
+ if (calendarIds.length === 0)
630
+ return [];
631
+ const result = await this.database.query(`SELECT * FROM calendar_events
632
+ WHERE calendar_id = ANY($1::text[])
633
+ AND recurrence IS NOT NULL
634
+ ORDER BY start_at ASC
635
+ LIMIT $2`, [[...calendarIds], maximum]);
636
+ return result.rows.map(calendarEvent);
637
+ }
364
638
  async findByOwner(eventId, userId) {
365
639
  const result = await this.database.query('SELECT * FROM calendar_events WHERE id = $1 AND user_id = $2', [eventId, userId]);
366
640
  return result.rows[0] ? calendarEvent(result.rows[0]) : null;
367
641
  }
642
+ async findById(eventId) {
643
+ const result = await this.database.query('SELECT * FROM calendar_events WHERE id = $1', [eventId]);
644
+ return result.rows[0] ? calendarEvent(result.rows[0]) : null;
645
+ }
646
+ async listWithReminders(maximum) {
647
+ const result = await this.database.query(`SELECT * FROM calendar_events
648
+ WHERE reminder_minutes IS NOT NULL
649
+ ORDER BY start_at ASC
650
+ LIMIT $1`, [maximum]);
651
+ return result.rows.map(calendarEvent);
652
+ }
653
+ async markReminded(eventId, occurrenceStart) {
654
+ return (changes((await this.database.query(`UPDATE calendar_events
655
+ SET last_reminded_occurrence = $1
656
+ WHERE id = $2
657
+ AND (last_reminded_occurrence IS NULL
658
+ OR last_reminded_occurrence < $1)`, [occurrenceStart, eventId])).rowCount) > 0);
659
+ }
368
660
  async replaceWithLimit(value, maximum) {
369
661
  await this.database.transaction(async (client) => {
370
662
  await lockOwner(client, value.user_id);
@@ -380,8 +672,9 @@ class PostgresCalendarEventRepository {
380
672
  }
381
673
  const result = await client.query(`INSERT INTO calendar_events
382
674
  (id, user_id, title, notes, start_at, end_at, all_day,
383
- recurrence, created_at, updated_at)
384
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
675
+ recurrence, calendar_id, reminder_minutes,
676
+ last_reminded_occurrence, created_at, updated_at)
677
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
385
678
  ON CONFLICT (id) DO UPDATE SET
386
679
  title = EXCLUDED.title,
387
680
  notes = EXCLUDED.notes,
@@ -389,6 +682,9 @@ class PostgresCalendarEventRepository {
389
682
  end_at = EXCLUDED.end_at,
390
683
  all_day = EXCLUDED.all_day,
391
684
  recurrence = EXCLUDED.recurrence,
685
+ calendar_id = EXCLUDED.calendar_id,
686
+ reminder_minutes = EXCLUDED.reminder_minutes,
687
+ last_reminded_occurrence = EXCLUDED.last_reminded_occurrence,
392
688
  updated_at = EXCLUDED.updated_at
393
689
  WHERE calendar_events.user_id = EXCLUDED.user_id`, [
394
690
  value.id,
@@ -399,6 +695,64 @@ class PostgresCalendarEventRepository {
399
695
  value.end_at,
400
696
  value.all_day,
401
697
  value.recurrence,
698
+ value.calendar_id,
699
+ value.reminder_minutes,
700
+ value.last_reminded_occurrence,
701
+ value.created_at,
702
+ value.updated_at,
703
+ ]);
704
+ if (result.rowCount !== 1)
705
+ throw new PersistenceResourceConflictError();
706
+ });
707
+ }
708
+ async deleteByOwner(eventId, userId) {
709
+ return (changes((await this.database.query('DELETE FROM calendar_events WHERE id = $1 AND user_id = $2', [eventId, userId])).rowCount) > 0);
710
+ }
711
+ }
712
+ class PostgresCalendarRepository {
713
+ constructor(database) {
714
+ this.database = database;
715
+ }
716
+ async listByOwner(userId, maximum) {
717
+ const result = await this.database.query(`SELECT * FROM calendars
718
+ WHERE user_id = $1
719
+ ORDER BY created_at ASC
720
+ LIMIT $2`, [userId, maximum]);
721
+ return result.rows.map(calendar);
722
+ }
723
+ async findByOwner(calendarId, userId) {
724
+ const result = await this.database.query('SELECT * FROM calendars WHERE id = $1 AND user_id = $2', [calendarId, userId]);
725
+ return result.rows[0] ? calendar(result.rows[0]) : null;
726
+ }
727
+ async findById(calendarId) {
728
+ const result = await this.database.query('SELECT * FROM calendars WHERE id = $1', [calendarId]);
729
+ return result.rows[0] ? calendar(result.rows[0]) : null;
730
+ }
731
+ async replaceWithLimit(value, maximum) {
732
+ await this.database.transaction(async (client) => {
733
+ await lockOwner(client, value.user_id);
734
+ const existing = await client.query('SELECT user_id FROM calendars WHERE id = $1 FOR UPDATE', [value.id]);
735
+ if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
736
+ throw new PersistenceResourceConflictError();
737
+ }
738
+ if (!existing.rows[0]) {
739
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM calendars WHERE user_id = $1', [value.user_id]);
740
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
741
+ throw new PersistenceResourceLimitError('calendar', maximum);
742
+ }
743
+ }
744
+ const result = await client.query(`INSERT INTO calendars
745
+ (id, user_id, name, color, created_at, updated_at)
746
+ VALUES ($1, $2, $3, $4, $5, $6)
747
+ ON CONFLICT (id) DO UPDATE SET
748
+ name = EXCLUDED.name,
749
+ color = EXCLUDED.color,
750
+ updated_at = EXCLUDED.updated_at
751
+ WHERE calendars.user_id = EXCLUDED.user_id`, [
752
+ value.id,
753
+ value.user_id,
754
+ value.name,
755
+ value.color,
402
756
  value.created_at,
403
757
  value.updated_at,
404
758
  ]);
@@ -406,181 +760,1298 @@ class PostgresCalendarEventRepository {
406
760
  throw new PersistenceResourceConflictError();
407
761
  });
408
762
  }
409
- async deleteByOwner(eventId, userId) {
410
- return (changes((await this.database.query('DELETE FROM calendar_events WHERE id = $1 AND user_id = $2', [eventId, userId])).rowCount) > 0);
763
+ async deleteAndDetach(calendarId, userId) {
764
+ return this.database.transaction(async (client) => {
765
+ const deleted = changes((await client.query('DELETE FROM calendars WHERE id = $1 AND user_id = $2', [calendarId, userId])).rowCount) > 0;
766
+ if (deleted) {
767
+ await client.query(`UPDATE calendar_events
768
+ SET calendar_id = NULL
769
+ WHERE calendar_id = $1 AND user_id = $2`, [calendarId, userId]);
770
+ }
771
+ return deleted;
772
+ });
773
+ }
774
+ }
775
+ class PostgresChannelRepository {
776
+ constructor(database) {
777
+ this.database = database;
778
+ }
779
+ async insertWithOwner(value, owner, maximumPerUser) {
780
+ await this.database.transaction(async (client) => {
781
+ await lockOwner(client, owner.user_id);
782
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM channels WHERE created_by = $1', [owner.user_id]);
783
+ if (Number(count.rows[0]?.count || 0) >= maximumPerUser) {
784
+ throw new PersistenceResourceLimitError('channel', maximumPerUser);
785
+ }
786
+ await client.query(`INSERT INTO channels
787
+ (id, type, name, description, dm_key, created_by,
788
+ created_at, updated_at, archived_at)
789
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
790
+ value.id,
791
+ value.type,
792
+ value.name,
793
+ value.description,
794
+ value.dm_key,
795
+ value.created_by,
796
+ value.created_at,
797
+ value.updated_at,
798
+ value.archived_at,
799
+ ]);
800
+ await client.query(`INSERT INTO channel_members
801
+ (channel_id, user_id, role, joined_at, last_read_at)
802
+ VALUES ($1, $2, $3, $4, $5)`, [
803
+ owner.channel_id,
804
+ owner.user_id,
805
+ owner.role,
806
+ owner.joined_at,
807
+ owner.last_read_at,
808
+ ]);
809
+ });
810
+ }
811
+ async findById(channelId) {
812
+ const result = await this.database.query('SELECT * FROM channels WHERE id = $1', [channelId]);
813
+ return result.rows[0] ? channel(result.rows[0]) : null;
814
+ }
815
+ async findByDmKey(dmKey) {
816
+ const result = await this.database.query('SELECT * FROM channels WHERE dm_key = $1', [dmKey]);
817
+ return result.rows[0] ? channel(result.rows[0]) : null;
818
+ }
819
+ async listForUser(userId, maximum) {
820
+ const result = await this.database.query(`SELECT
821
+ c.id, c.type, c.name, c.description, c.dm_key, c.created_by,
822
+ c.created_at, c.updated_at, c.archived_at,
823
+ m.channel_id AS member_channel_id, m.user_id AS member_user_id,
824
+ m.role AS member_role, m.joined_at AS member_joined_at,
825
+ m.last_read_at AS member_last_read_at
826
+ FROM channel_members m
827
+ JOIN channels c ON c.id = m.channel_id
828
+ WHERE m.user_id = $1
829
+ ORDER BY c.updated_at DESC
830
+ LIMIT $2`, [userId, maximum]);
831
+ return result.rows.map(row => ({
832
+ channel: channel(row),
833
+ member: channelMember({
834
+ channel_id: row.member_channel_id,
835
+ user_id: row.member_user_id,
836
+ role: row.member_role,
837
+ joined_at: row.member_joined_at,
838
+ last_read_at: row.member_last_read_at,
839
+ }),
840
+ }));
841
+ }
842
+ async listPublic(maximum) {
843
+ const result = await this.database.query(`SELECT * FROM channels
844
+ WHERE type = 'public' AND archived_at IS NULL
845
+ ORDER BY updated_at DESC
846
+ LIMIT $1`, [maximum]);
847
+ return result.rows.map(channel);
848
+ }
849
+ async update(value) {
850
+ await this.database.query(`UPDATE channels SET
851
+ name = $1,
852
+ description = $2,
853
+ updated_at = $3,
854
+ archived_at = $4
855
+ WHERE id = $5`, [
856
+ value.name,
857
+ value.description,
858
+ value.updated_at,
859
+ value.archived_at,
860
+ value.id,
861
+ ]);
862
+ }
863
+ async delete(channelId) {
864
+ return (changes((await this.database.query('DELETE FROM channels WHERE id = $1', [
865
+ channelId,
866
+ ])).rowCount) > 0);
867
+ }
868
+ async upsertMember(member, maximumMembers) {
869
+ await this.database.transaction(async (client) => {
870
+ const existing = await client.query(`SELECT 1 FROM channel_members
871
+ WHERE channel_id = $1 AND user_id = $2
872
+ FOR UPDATE`, [member.channel_id, member.user_id]);
873
+ if (!existing.rows[0]) {
874
+ const count = await client.query(`SELECT COUNT(*)::text AS count FROM channel_members
875
+ WHERE channel_id = $1`, [member.channel_id]);
876
+ if (Number(count.rows[0]?.count || 0) >= maximumMembers) {
877
+ throw new PersistenceResourceLimitError('channel-member', maximumMembers);
878
+ }
879
+ }
880
+ await client.query(`INSERT INTO channel_members
881
+ (channel_id, user_id, role, joined_at, last_read_at)
882
+ VALUES ($1, $2, $3, $4, $5)
883
+ ON CONFLICT (channel_id, user_id) DO UPDATE SET
884
+ role = EXCLUDED.role`, [
885
+ member.channel_id,
886
+ member.user_id,
887
+ member.role,
888
+ member.joined_at,
889
+ member.last_read_at,
890
+ ]);
891
+ });
892
+ }
893
+ async findMember(channelId, userId) {
894
+ const result = await this.database.query('SELECT * FROM channel_members WHERE channel_id = $1 AND user_id = $2', [channelId, userId]);
895
+ return result.rows[0] ? channelMember(result.rows[0]) : null;
896
+ }
897
+ async listMembers(channelId, maximum) {
898
+ const result = await this.database.query(`SELECT * FROM channel_members
899
+ WHERE channel_id = $1
900
+ ORDER BY joined_at ASC
901
+ LIMIT $2`, [channelId, maximum]);
902
+ return result.rows.map(channelMember);
903
+ }
904
+ async removeMember(channelId, userId) {
905
+ return (changes((await this.database.query('DELETE FROM channel_members WHERE channel_id = $1 AND user_id = $2', [channelId, userId])).rowCount) > 0);
906
+ }
907
+ async advanceLastRead(channelId, userId, lastReadAt) {
908
+ return (changes((await this.database.query(`UPDATE channel_members
909
+ SET last_read_at = $1
910
+ WHERE channel_id = $2 AND user_id = $3 AND last_read_at < $1`, [lastReadAt, channelId, userId])).rowCount) > 0);
911
+ }
912
+ async unreadSummaryForUser(userId) {
913
+ const result = await this.database.query(`SELECT
914
+ m.channel_id,
915
+ COUNT(msg.id)::text AS unread_count,
916
+ MAX(msg.created_at) AS latest_message_at
917
+ FROM channel_members m
918
+ LEFT JOIN channel_messages msg
919
+ ON msg.channel_id = m.channel_id
920
+ AND msg.created_at > m.last_read_at
921
+ AND msg.deleted_at IS NULL
922
+ AND (msg.user_id IS NULL OR msg.user_id != m.user_id
923
+ OR msg.author_kind != 'user')
924
+ WHERE m.user_id = $1
925
+ GROUP BY m.channel_id`, [userId]);
926
+ return result.rows.map(row => ({
927
+ channel_id: row.channel_id,
928
+ unread_count: number(row.unread_count, 'channel unread_count'),
929
+ latest_message_at: row.latest_message_at === null
930
+ ? null
931
+ : number(row.latest_message_at, 'channel latest_message_at'),
932
+ }));
933
+ }
934
+ }
935
+ class PostgresChannelMessageRepository {
936
+ constructor(database) {
937
+ this.database = database;
938
+ }
939
+ async insertIfAbsent(message, attachments, maximumPerChannel) {
940
+ return this.database.transaction(async (client) => {
941
+ const existing = await client.query('SELECT * FROM channel_messages WHERE id = $1', [message.id]);
942
+ if (existing.rows[0]) {
943
+ return { stored: channelMessage(existing.rows[0]), inserted: false };
944
+ }
945
+ const count = await client.query(`SELECT COUNT(*)::text AS count FROM channel_messages
946
+ WHERE channel_id = $1`, [message.channel_id]);
947
+ if (Number(count.rows[0]?.count || 0) >= maximumPerChannel) {
948
+ throw new PersistenceResourceLimitError('channel-message', maximumPerChannel);
949
+ }
950
+ await client.query(`INSERT INTO channel_messages
951
+ (id, channel_id, user_id, parent_id, author_kind, model, content,
952
+ metadata, created_at, updated_at, edited_at, deleted_at,
953
+ pinned_at, pinned_by)
954
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`, [
955
+ message.id,
956
+ message.channel_id,
957
+ message.user_id,
958
+ message.parent_id,
959
+ message.author_kind,
960
+ message.model,
961
+ message.content,
962
+ message.metadata,
963
+ message.created_at,
964
+ message.updated_at,
965
+ message.edited_at,
966
+ message.deleted_at,
967
+ message.pinned_at,
968
+ message.pinned_by,
969
+ ]);
970
+ for (const attachment of attachments) {
971
+ if (attachment.message_id !== message.id) {
972
+ throw new Error('A channel attachment does not belong to its message');
973
+ }
974
+ await client.query(`INSERT INTO channel_attachments
975
+ (id, message_id, channel_id, blob_id, filename, content_type,
976
+ size, created_by, created_at)
977
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
978
+ attachment.id,
979
+ attachment.message_id,
980
+ attachment.channel_id,
981
+ attachment.blob_id,
982
+ attachment.filename,
983
+ attachment.content_type,
984
+ attachment.size,
985
+ attachment.created_by,
986
+ attachment.created_at,
987
+ ]);
988
+ }
989
+ await client.query('UPDATE channels SET updated_at = $1 WHERE id = $2', [
990
+ message.created_at,
991
+ message.channel_id,
992
+ ]);
993
+ return { stored: message, inserted: true };
994
+ });
995
+ }
996
+ async findById(messageId) {
997
+ const result = await this.database.query('SELECT * FROM channel_messages WHERE id = $1', [messageId]);
998
+ return result.rows[0] ? channelMessage(result.rows[0]) : null;
999
+ }
1000
+ async listPage(channelId, options) {
1001
+ const clauses = ['channel_id = $1'];
1002
+ const parameters = [channelId];
1003
+ if (options.parentId === null) {
1004
+ clauses.push('parent_id IS NULL');
1005
+ }
1006
+ else if (options.parentId !== undefined) {
1007
+ parameters.push(options.parentId);
1008
+ clauses.push(`parent_id = $${parameters.length}`);
1009
+ }
1010
+ if (options.before) {
1011
+ parameters.push(options.before.created_at);
1012
+ const createdIndex = parameters.length;
1013
+ parameters.push(options.before.id);
1014
+ clauses.push(`(created_at < $${createdIndex} OR ` +
1015
+ `(created_at = $${createdIndex} AND id < $${parameters.length}))`);
1016
+ }
1017
+ if (options.after) {
1018
+ parameters.push(options.after.created_at);
1019
+ const createdIndex = parameters.length;
1020
+ parameters.push(options.after.id);
1021
+ clauses.push(`(created_at > $${createdIndex} OR ` +
1022
+ `(created_at = $${createdIndex} AND id > $${parameters.length}))`);
1023
+ }
1024
+ const direction = options.after ? 'ASC' : 'DESC';
1025
+ parameters.push(options.limit);
1026
+ const result = await this.database.query(`SELECT * FROM channel_messages
1027
+ WHERE ${clauses.join(' AND ')}
1028
+ ORDER BY created_at ${direction}, id ${direction}
1029
+ LIMIT $${parameters.length}`, parameters);
1030
+ return result.rows.map(channelMessage);
1031
+ }
1032
+ async listThread(parentId, maximum) {
1033
+ const result = await this.database.query(`SELECT * FROM channel_messages
1034
+ WHERE parent_id = $1
1035
+ ORDER BY created_at ASC, id ASC
1036
+ LIMIT $2`, [parentId, maximum]);
1037
+ return result.rows.map(channelMessage);
1038
+ }
1039
+ async countThreadReplies(parentIds) {
1040
+ if (parentIds.length === 0)
1041
+ return {};
1042
+ const result = await this.database.query(`SELECT parent_id, COUNT(*)::text AS count
1043
+ FROM channel_messages
1044
+ WHERE parent_id = ANY($1::text[]) AND deleted_at IS NULL
1045
+ GROUP BY parent_id`, [[...parentIds]]);
1046
+ const counts = {};
1047
+ for (const row of result.rows) {
1048
+ counts[row.parent_id] = number(row.count, 'channel thread reply count');
1049
+ }
1050
+ return counts;
1051
+ }
1052
+ async update(message) {
1053
+ await this.database.query(`UPDATE channel_messages SET
1054
+ content = $1,
1055
+ metadata = $2,
1056
+ updated_at = $3,
1057
+ edited_at = $4,
1058
+ deleted_at = $5,
1059
+ pinned_at = $6,
1060
+ pinned_by = $7
1061
+ WHERE id = $8`, [
1062
+ message.content,
1063
+ message.metadata,
1064
+ message.updated_at,
1065
+ message.edited_at,
1066
+ message.deleted_at,
1067
+ message.pinned_at,
1068
+ message.pinned_by,
1069
+ message.id,
1070
+ ]);
1071
+ }
1072
+ async listPinned(channelId, maximum) {
1073
+ const result = await this.database.query(`SELECT * FROM channel_messages
1074
+ WHERE channel_id = $1 AND pinned_at IS NOT NULL
1075
+ AND deleted_at IS NULL
1076
+ ORDER BY pinned_at DESC
1077
+ LIMIT $2`, [channelId, maximum]);
1078
+ return result.rows.map(channelMessage);
1079
+ }
1080
+ async listRecentForChannels(channelIds, maximum) {
1081
+ if (channelIds.length === 0)
1082
+ return [];
1083
+ const result = await this.database.query(`SELECT * FROM channel_messages
1084
+ WHERE channel_id = ANY($1::text[]) AND deleted_at IS NULL
1085
+ ORDER BY created_at DESC
1086
+ LIMIT $2`, [[...channelIds], maximum]);
1087
+ return result.rows.map(channelMessage);
1088
+ }
1089
+ async addReaction(reaction, maximumPerMessage) {
1090
+ return this.database.transaction(async (client) => {
1091
+ const existing = await client.query(`SELECT 1 FROM channel_reactions
1092
+ WHERE message_id = $1 AND user_id = $2 AND emoji = $3`, [reaction.message_id, reaction.user_id, reaction.emoji]);
1093
+ if (existing.rows[0])
1094
+ return false;
1095
+ const count = await client.query(`SELECT COUNT(*)::text AS count FROM channel_reactions
1096
+ WHERE message_id = $1`, [reaction.message_id]);
1097
+ if (Number(count.rows[0]?.count || 0) >= maximumPerMessage) {
1098
+ throw new PersistenceResourceLimitError('channel-reaction', maximumPerMessage);
1099
+ }
1100
+ const result = await client.query(`INSERT INTO channel_reactions
1101
+ (id, message_id, user_id, emoji, created_at)
1102
+ VALUES ($1, $2, $3, $4, $5)
1103
+ ON CONFLICT (message_id, user_id, emoji) DO NOTHING`, [
1104
+ reaction.id,
1105
+ reaction.message_id,
1106
+ reaction.user_id,
1107
+ reaction.emoji,
1108
+ reaction.created_at,
1109
+ ]);
1110
+ return changes(result.rowCount) > 0;
1111
+ });
1112
+ }
1113
+ async removeReaction(messageId, userId, emoji) {
1114
+ return (changes((await this.database.query(`DELETE FROM channel_reactions
1115
+ WHERE message_id = $1 AND user_id = $2 AND emoji = $3`, [messageId, userId, emoji])).rowCount) > 0);
1116
+ }
1117
+ async listReactionsForMessages(messageIds) {
1118
+ if (messageIds.length === 0)
1119
+ return [];
1120
+ const result = await this.database.query(`SELECT * FROM channel_reactions
1121
+ WHERE message_id = ANY($1::text[])
1122
+ ORDER BY created_at ASC`, [[...messageIds]]);
1123
+ return result.rows.map(channelReaction);
1124
+ }
1125
+ async findAttachment(attachmentId) {
1126
+ const result = await this.database.query('SELECT * FROM channel_attachments WHERE id = $1', [attachmentId]);
1127
+ return result.rows[0] ? channelAttachment(result.rows[0]) : null;
1128
+ }
1129
+ async listAttachmentsForMessages(messageIds) {
1130
+ if (messageIds.length === 0)
1131
+ return [];
1132
+ const result = await this.database.query(`SELECT * FROM channel_attachments
1133
+ WHERE message_id = ANY($1::text[])
1134
+ ORDER BY created_at ASC`, [[...messageIds]]);
1135
+ return result.rows.map(channelAttachment);
1136
+ }
1137
+ async listAttachmentBlobIds(channelId) {
1138
+ const result = await this.database.query('SELECT blob_id, created_by FROM channel_attachments WHERE channel_id = $1', [channelId]);
1139
+ return result.rows;
1140
+ }
1141
+ }
1142
+ class PostgresNotificationRepository {
1143
+ constructor(database) {
1144
+ this.database = database;
1145
+ }
1146
+ async insertWithLimit(value, maximumPerUser) {
1147
+ return this.database.transaction(async (client) => {
1148
+ await lockOwner(client, value.user_id);
1149
+ const result = await client.query(`INSERT INTO notifications
1150
+ (id, user_id, type, title, body, href, source_key,
1151
+ created_at, read_at)
1152
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
1153
+ ON CONFLICT (user_id, source_key) DO NOTHING`, [
1154
+ value.id,
1155
+ value.user_id,
1156
+ value.type,
1157
+ value.title,
1158
+ value.body,
1159
+ value.href,
1160
+ value.source_key,
1161
+ value.created_at,
1162
+ value.read_at,
1163
+ ]);
1164
+ if (changes(result.rowCount) === 0)
1165
+ return false;
1166
+ await client.query(`DELETE FROM notifications
1167
+ WHERE user_id = $1
1168
+ AND id NOT IN (
1169
+ SELECT id FROM notifications
1170
+ WHERE user_id = $1
1171
+ ORDER BY created_at DESC, id DESC
1172
+ LIMIT $2
1173
+ )`, [value.user_id, maximumPerUser]);
1174
+ return true;
1175
+ });
1176
+ }
1177
+ async listByOwner(userId, options) {
1178
+ const clauses = ['user_id = $1'];
1179
+ const parameters = [userId];
1180
+ if (options.before !== undefined) {
1181
+ parameters.push(options.before);
1182
+ clauses.push(`created_at < $${parameters.length}`);
1183
+ }
1184
+ if (options.unreadOnly) {
1185
+ clauses.push('read_at IS NULL');
1186
+ }
1187
+ parameters.push(options.limit);
1188
+ const result = await this.database.query(`SELECT * FROM notifications
1189
+ WHERE ${clauses.join(' AND ')}
1190
+ ORDER BY created_at DESC, id DESC
1191
+ LIMIT $${parameters.length}`, parameters);
1192
+ return result.rows.map(notification);
1193
+ }
1194
+ async countUnread(userId) {
1195
+ const result = await this.database.query(`SELECT COUNT(*)::text AS count FROM notifications
1196
+ WHERE user_id = $1 AND read_at IS NULL`, [userId]);
1197
+ return Number(result.rows[0]?.count || 0);
1198
+ }
1199
+ async markRead(notificationId, userId, readAt) {
1200
+ return (changes((await this.database.query(`UPDATE notifications SET read_at = $1
1201
+ WHERE id = $2 AND user_id = $3 AND read_at IS NULL`, [readAt, notificationId, userId])).rowCount) > 0);
1202
+ }
1203
+ async markAllRead(userId, readAt) {
1204
+ return changes((await this.database.query(`UPDATE notifications SET read_at = $1
1205
+ WHERE user_id = $2 AND read_at IS NULL`, [readAt, userId])).rowCount);
1206
+ }
1207
+ async deleteByOwner(notificationId, userId) {
1208
+ return (changes((await this.database.query('DELETE FROM notifications WHERE id = $1 AND user_id = $2', [notificationId, userId])).rowCount) > 0);
1209
+ }
1210
+ }
1211
+ class PostgresWebhookTargetRepository {
1212
+ constructor(database) {
1213
+ this.database = database;
1214
+ }
1215
+ async list(maximum) {
1216
+ const result = await this.database.query('SELECT * FROM webhook_targets ORDER BY created_at ASC LIMIT $1', [maximum]);
1217
+ return result.rows.map(webhookTarget);
1218
+ }
1219
+ async findById(targetId) {
1220
+ const result = await this.database.query('SELECT * FROM webhook_targets WHERE id = $1', [targetId]);
1221
+ return result.rows[0] ? webhookTarget(result.rows[0]) : null;
1222
+ }
1223
+ async replaceWithLimit(value, maximum) {
1224
+ await this.database.transaction(async (client) => {
1225
+ const existing = await client.query('SELECT 1 FROM webhook_targets WHERE id = $1 FOR UPDATE', [value.id]);
1226
+ if (!existing.rows[0]) {
1227
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM webhook_targets');
1228
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
1229
+ throw new PersistenceResourceLimitError('webhook-target', maximum);
1230
+ }
1231
+ }
1232
+ await client.query(`INSERT INTO webhook_targets
1233
+ (id, name, url, secret, events, enabled, created_by,
1234
+ created_at, updated_at)
1235
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
1236
+ ON CONFLICT (id) DO UPDATE SET
1237
+ name = EXCLUDED.name,
1238
+ url = EXCLUDED.url,
1239
+ secret = EXCLUDED.secret,
1240
+ events = EXCLUDED.events,
1241
+ enabled = EXCLUDED.enabled,
1242
+ updated_at = EXCLUDED.updated_at`, [
1243
+ value.id,
1244
+ value.name,
1245
+ value.url,
1246
+ value.secret,
1247
+ value.events,
1248
+ value.enabled,
1249
+ value.created_by,
1250
+ value.created_at,
1251
+ value.updated_at,
1252
+ ]);
1253
+ });
1254
+ }
1255
+ async delete(targetId) {
1256
+ return (changes((await this.database.query('DELETE FROM webhook_targets WHERE id = $1', [targetId])).rowCount) > 0);
1257
+ }
1258
+ }
1259
+ class PostgresPushSubscriptionRepository {
1260
+ constructor(database) {
1261
+ this.database = database;
1262
+ }
1263
+ async upsertByEndpoint(record) {
1264
+ await this.database.query(`INSERT INTO push_subscriptions
1265
+ (id, user_id, session_id, endpoint_lookup, subscription,
1266
+ user_agent, created_at, last_used_at)
1267
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
1268
+ ON CONFLICT (endpoint_lookup) DO UPDATE SET
1269
+ user_id = excluded.user_id,
1270
+ session_id = excluded.session_id,
1271
+ subscription = excluded.subscription,
1272
+ user_agent = excluded.user_agent,
1273
+ last_used_at = excluded.last_used_at`, [
1274
+ record.id,
1275
+ record.user_id,
1276
+ record.session_id,
1277
+ record.endpoint_lookup,
1278
+ record.subscription,
1279
+ record.user_agent,
1280
+ record.created_at,
1281
+ record.last_used_at,
1282
+ ]);
1283
+ }
1284
+ async listByUser(userId) {
1285
+ const result = await this.database.query(`SELECT * FROM push_subscriptions
1286
+ WHERE user_id = $1
1287
+ ORDER BY created_at ASC`, [userId]);
1288
+ return result.rows.map(pushSubscription);
1289
+ }
1290
+ async findByLookup(endpointLookup) {
1291
+ const result = await this.database.query('SELECT * FROM push_subscriptions WHERE endpoint_lookup = $1', [endpointLookup]);
1292
+ return result.rows[0] ? pushSubscription(result.rows[0]) : null;
1293
+ }
1294
+ async findById(id) {
1295
+ const result = await this.database.query('SELECT * FROM push_subscriptions WHERE id = $1', [id]);
1296
+ return result.rows[0] ? pushSubscription(result.rows[0]) : null;
1297
+ }
1298
+ async touch(id, lastUsedAt) {
1299
+ const result = await this.database.query('UPDATE push_subscriptions SET last_used_at = $1 WHERE id = $2', [lastUsedAt, id]);
1300
+ return changes(result.rowCount) > 0;
1301
+ }
1302
+ async delete(id) {
1303
+ const result = await this.database.query('DELETE FROM push_subscriptions WHERE id = $1', [id]);
1304
+ return changes(result.rowCount) > 0;
1305
+ }
1306
+ async deleteByLookup(userId, endpointLookup) {
1307
+ const result = await this.database.query(`DELETE FROM push_subscriptions
1308
+ WHERE user_id = $1 AND endpoint_lookup = $2`, [userId, endpointLookup]);
1309
+ return changes(result.rowCount) > 0;
1310
+ }
1311
+ async deleteForSession(sessionId) {
1312
+ const result = await this.database.query('DELETE FROM push_subscriptions WHERE session_id = $1', [sessionId]);
1313
+ return changes(result.rowCount);
1314
+ }
1315
+ async deleteForUser(userId) {
1316
+ const result = await this.database.query('DELETE FROM push_subscriptions WHERE user_id = $1', [userId]);
1317
+ return changes(result.rowCount);
1318
+ }
1319
+ }
1320
+ class PostgresRecoveryDrillRepository {
1321
+ constructor(database) {
1322
+ this.database = database;
1323
+ }
1324
+ async insert(record) {
1325
+ await this.database.query(`INSERT INTO recovery_drills
1326
+ (id, status, origin, started_at, finished_at, snapshot_bytes,
1327
+ rpo_seconds, restore_ms, error, report, created_by, created_at)
1328
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
1329
+ record.id,
1330
+ record.status,
1331
+ record.origin,
1332
+ record.started_at,
1333
+ record.finished_at,
1334
+ record.snapshot_bytes,
1335
+ record.rpo_seconds,
1336
+ record.restore_ms,
1337
+ record.error,
1338
+ record.report,
1339
+ record.created_by,
1340
+ record.created_at,
1341
+ ]);
1342
+ }
1343
+ async update(id, patch) {
1344
+ const assignments = [];
1345
+ const parameters = [];
1346
+ for (const [column, value] of Object.entries(patch)) {
1347
+ if (value === undefined)
1348
+ continue;
1349
+ parameters.push(value);
1350
+ assignments.push(`${column} = $${parameters.length}`);
1351
+ }
1352
+ if (assignments.length === 0)
1353
+ return false;
1354
+ parameters.push(id);
1355
+ const result = await this.database.query(`UPDATE recovery_drills SET ${assignments.join(', ')}
1356
+ WHERE id = $${parameters.length}`, parameters);
1357
+ return changes(result.rowCount) > 0;
1358
+ }
1359
+ async findById(id) {
1360
+ const result = await this.database.query('SELECT * FROM recovery_drills WHERE id = $1', [id]);
1361
+ return result.rows[0] ? recoveryDrill(result.rows[0]) : null;
1362
+ }
1363
+ async list(limit) {
1364
+ const result = await this.database.query(`SELECT * FROM recovery_drills
1365
+ ORDER BY started_at DESC, id DESC
1366
+ LIMIT $1`, [limit]);
1367
+ return result.rows.map(recoveryDrill);
1368
+ }
1369
+ async findLatestFinished() {
1370
+ const result = await this.database.query(`SELECT * FROM recovery_drills
1371
+ WHERE status != 'running'
1372
+ ORDER BY started_at DESC, id DESC
1373
+ LIMIT 1`);
1374
+ return result.rows[0] ? recoveryDrill(result.rows[0]) : null;
1375
+ }
1376
+ async findRunning() {
1377
+ const result = await this.database.query(`SELECT * FROM recovery_drills
1378
+ WHERE status = 'running'
1379
+ ORDER BY started_at DESC, id DESC
1380
+ LIMIT 1`);
1381
+ return result.rows[0] ? recoveryDrill(result.rows[0]) : null;
1382
+ }
1383
+ async pruneToLimit(limit) {
1384
+ const result = await this.database.query(`DELETE FROM recovery_drills
1385
+ WHERE status != 'running'
1386
+ AND id NOT IN (
1387
+ SELECT id FROM recovery_drills
1388
+ WHERE status != 'running'
1389
+ ORDER BY started_at DESC, id DESC
1390
+ LIMIT $1
1391
+ )`, [limit]);
1392
+ return changes(result.rowCount);
1393
+ }
1394
+ }
1395
+ class PostgresAutomationRepository {
1396
+ constructor(database) {
1397
+ this.database = database;
1398
+ }
1399
+ async listByOwner(userId, maximum) {
1400
+ const result = await this.database.query(`SELECT * FROM automations
1401
+ WHERE user_id = $1
1402
+ ORDER BY updated_at DESC
1403
+ LIMIT $2`, [userId, maximum]);
1404
+ return result.rows.map(automation);
1405
+ }
1406
+ async findByOwner(automationId, userId) {
1407
+ const result = await this.database.query('SELECT * FROM automations WHERE id = $1 AND user_id = $2', [automationId, userId]);
1408
+ return result.rows[0] ? automation(result.rows[0]) : null;
1409
+ }
1410
+ async findById(automationId) {
1411
+ const result = await this.database.query('SELECT * FROM automations WHERE id = $1', [automationId]);
1412
+ return result.rows[0] ? automation(result.rows[0]) : null;
1413
+ }
1414
+ async replaceWithLimit(value, maximum) {
1415
+ await this.database.transaction(async (client) => {
1416
+ await lockOwner(client, value.user_id);
1417
+ const existing = await client.query('SELECT user_id FROM automations WHERE id = $1 FOR UPDATE', [value.id]);
1418
+ if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
1419
+ throw new PersistenceResourceConflictError();
1420
+ }
1421
+ if (!existing.rows[0]) {
1422
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM automations WHERE user_id = $1', [value.user_id]);
1423
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
1424
+ throw new PersistenceResourceLimitError('automation', maximum);
1425
+ }
1426
+ }
1427
+ const result = await client.query(`INSERT INTO automations
1428
+ (id, user_id, name, instructions, triggers, provider, model,
1429
+ notify, status, next_run_at, last_run_at, created_at, updated_at)
1430
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
1431
+ ON CONFLICT (id) DO UPDATE SET
1432
+ name = EXCLUDED.name,
1433
+ instructions = EXCLUDED.instructions,
1434
+ triggers = EXCLUDED.triggers,
1435
+ provider = EXCLUDED.provider,
1436
+ model = EXCLUDED.model,
1437
+ notify = EXCLUDED.notify,
1438
+ status = EXCLUDED.status,
1439
+ next_run_at = EXCLUDED.next_run_at,
1440
+ last_run_at = EXCLUDED.last_run_at,
1441
+ updated_at = EXCLUDED.updated_at
1442
+ WHERE automations.user_id = EXCLUDED.user_id`, [
1443
+ value.id,
1444
+ value.user_id,
1445
+ value.name,
1446
+ value.instructions,
1447
+ value.triggers,
1448
+ value.provider,
1449
+ value.model,
1450
+ value.notify,
1451
+ value.status,
1452
+ value.next_run_at,
1453
+ value.last_run_at,
1454
+ value.created_at,
1455
+ value.updated_at,
1456
+ ]);
1457
+ if (result.rowCount !== 1)
1458
+ throw new PersistenceResourceConflictError();
1459
+ });
1460
+ }
1461
+ async listDue(now, maximum) {
1462
+ const result = await this.database.query(`SELECT * FROM automations
1463
+ WHERE status = 'active'
1464
+ AND next_run_at IS NOT NULL
1465
+ AND next_run_at <= $1
1466
+ ORDER BY next_run_at ASC
1467
+ LIMIT $2`, [now, maximum]);
1468
+ return result.rows.map(automation);
1469
+ }
1470
+ async advanceNextRun(automationId, observedNextRunAt, nextRunAt, lastRunAt) {
1471
+ return (changes((await this.database.query(`UPDATE automations
1472
+ SET next_run_at = $1, last_run_at = $2
1473
+ WHERE id = $3 AND next_run_at = $4`, [nextRunAt, lastRunAt, automationId, observedNextRunAt])).rowCount) > 0);
1474
+ }
1475
+ async setStatus(automationId, userId, status, nextRunAt, updatedAt) {
1476
+ return (changes((await this.database.query(`UPDATE automations
1477
+ SET status = $1, next_run_at = $2, updated_at = $3
1478
+ WHERE id = $4 AND user_id = $5`, [status, nextRunAt, updatedAt, automationId, userId])).rowCount) > 0);
1479
+ }
1480
+ async deleteByOwner(automationId, userId) {
1481
+ return (changes((await this.database.query('DELETE FROM automations WHERE id = $1 AND user_id = $2', [automationId, userId])).rowCount) > 0);
1482
+ }
1483
+ }
1484
+ class PostgresAutomationRunRepository {
1485
+ constructor(database) {
1486
+ this.database = database;
1487
+ }
1488
+ async insert(run) {
1489
+ await this.database.query(`INSERT INTO automation_runs
1490
+ (id, automation_id, user_id, scheduled_for, started_at, finished_at,
1491
+ status, session_id, assistant_message_id, error, seen_at, created_at)
1492
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
1493
+ run.id,
1494
+ run.automation_id,
1495
+ run.user_id,
1496
+ run.scheduled_for,
1497
+ run.started_at,
1498
+ run.finished_at,
1499
+ run.status,
1500
+ run.session_id,
1501
+ run.assistant_message_id,
1502
+ run.error,
1503
+ run.seen_at,
1504
+ run.created_at,
1505
+ ]);
1506
+ }
1507
+ async findByOwner(runId, userId) {
1508
+ const result = await this.database.query('SELECT * FROM automation_runs WHERE id = $1 AND user_id = $2', [runId, userId]);
1509
+ return result.rows[0] ? automationRun(result.rows[0]) : null;
1510
+ }
1511
+ async listByOwner(userId, options) {
1512
+ const clauses = ['user_id = $1'];
1513
+ const values = [userId];
1514
+ if (options.automationId !== undefined) {
1515
+ values.push(options.automationId);
1516
+ clauses.push(`automation_id = $${values.length}`);
1517
+ }
1518
+ if (options.from !== undefined) {
1519
+ values.push(options.from);
1520
+ clauses.push(`scheduled_for >= $${values.length}`);
1521
+ }
1522
+ if (options.to !== undefined) {
1523
+ values.push(options.to);
1524
+ clauses.push(`scheduled_for < $${values.length}`);
1525
+ }
1526
+ values.push(options.maximum);
1527
+ const result = await this.database.query(`SELECT * FROM automation_runs
1528
+ WHERE ${clauses.join(' AND ')}
1529
+ ORDER BY scheduled_for DESC
1530
+ LIMIT $${values.length}`, values);
1531
+ return result.rows.map(automationRun);
1532
+ }
1533
+ async listUnfinished(maximum) {
1534
+ const result = await this.database.query(`SELECT * FROM automation_runs
1535
+ WHERE status IN ('queued', 'running')
1536
+ ORDER BY scheduled_for ASC
1537
+ LIMIT $1`, [maximum]);
1538
+ return result.rows.map(automationRun);
1539
+ }
1540
+ async markStarted(runId, sessionId, assistantMessageId, startedAt) {
1541
+ return (changes((await this.database.query(`UPDATE automation_runs
1542
+ SET session_id = $1, assistant_message_id = $2, started_at = $3,
1543
+ status = 'running'
1544
+ WHERE id = $4 AND status = 'queued'`, [sessionId, assistantMessageId, startedAt, runId])).rowCount) > 0);
1545
+ }
1546
+ async finalize(runId, status, finishedAt, error) {
1547
+ return (changes((await this.database.query(`UPDATE automation_runs
1548
+ SET status = $1, finished_at = $2, error = $3
1549
+ WHERE id = $4 AND status IN ('queued', 'running')`, [status, finishedAt, error, runId])).rowCount) > 0);
1550
+ }
1551
+ async countUnseenFinished(userId) {
1552
+ const result = await this.database.query(`SELECT COUNT(*)::text AS count FROM automation_runs
1553
+ WHERE user_id = $1
1554
+ AND status IN ('succeeded', 'failed')
1555
+ AND seen_at IS NULL`, [userId]);
1556
+ return Number(result.rows[0]?.count || 0);
1557
+ }
1558
+ async markSeenBefore(userId, seenAt) {
1559
+ return changes((await this.database.query(`UPDATE automation_runs
1560
+ SET seen_at = $1
1561
+ WHERE user_id = $2
1562
+ AND status IN ('succeeded', 'failed')
1563
+ AND seen_at IS NULL
1564
+ AND finished_at IS NOT NULL
1565
+ AND finished_at <= $3`, [seenAt, userId, seenAt])).rowCount);
1566
+ }
1567
+ }
1568
+ class PostgresToolServerRepository {
1569
+ constructor(database) {
1570
+ this.database = database;
1571
+ }
1572
+ async list(maximum) {
1573
+ const result = await this.database.query(`SELECT * FROM tool_servers
1574
+ ORDER BY updated_at DESC
1575
+ LIMIT $1`, [maximum]);
1576
+ return result.rows.map(toolServer);
1577
+ }
1578
+ async findById(serverId) {
1579
+ const result = await this.database.query('SELECT * FROM tool_servers WHERE id = $1', [serverId]);
1580
+ return result.rows[0] ? toolServer(result.rows[0]) : null;
1581
+ }
1582
+ async replaceWithLimit(value, maximum) {
1583
+ await this.database.transaction(async (client) => {
1584
+ await lockOwner(client, value.user_id);
1585
+ const existing = await client.query('SELECT id FROM tool_servers WHERE id = $1 FOR UPDATE', [value.id]);
1586
+ if (!existing.rows[0]) {
1587
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM tool_servers');
1588
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
1589
+ throw new PersistenceResourceLimitError('tool-server', maximum);
1590
+ }
1591
+ }
1592
+ await client.query(`INSERT INTO tool_servers
1593
+ (id, user_id, name, description, kind, base_url, spec, spec_digest,
1594
+ spec_revision, auth_mode, auth_header, access_mode, enabled,
1595
+ timeout_ms, max_response_bytes, created_at, updated_at)
1596
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14,
1597
+ $15, $16, $17)
1598
+ ON CONFLICT (id) DO UPDATE SET
1599
+ name = EXCLUDED.name,
1600
+ description = EXCLUDED.description,
1601
+ base_url = EXCLUDED.base_url,
1602
+ spec = EXCLUDED.spec,
1603
+ spec_digest = EXCLUDED.spec_digest,
1604
+ spec_revision = EXCLUDED.spec_revision,
1605
+ auth_mode = EXCLUDED.auth_mode,
1606
+ auth_header = EXCLUDED.auth_header,
1607
+ access_mode = EXCLUDED.access_mode,
1608
+ enabled = EXCLUDED.enabled,
1609
+ timeout_ms = EXCLUDED.timeout_ms,
1610
+ max_response_bytes = EXCLUDED.max_response_bytes,
1611
+ updated_at = EXCLUDED.updated_at`, [
1612
+ value.id,
1613
+ value.user_id,
1614
+ value.name,
1615
+ value.description,
1616
+ value.kind,
1617
+ value.base_url,
1618
+ value.spec,
1619
+ value.spec_digest,
1620
+ value.spec_revision,
1621
+ value.auth_mode,
1622
+ value.auth_header,
1623
+ value.access_mode,
1624
+ value.enabled,
1625
+ value.timeout_ms,
1626
+ value.max_response_bytes,
1627
+ value.created_at,
1628
+ value.updated_at,
1629
+ ]);
1630
+ });
1631
+ }
1632
+ async delete(serverId) {
1633
+ return (changes((await this.database.query('DELETE FROM tool_servers WHERE id = $1', [
1634
+ serverId,
1635
+ ])).rowCount) > 0);
1636
+ }
1637
+ }
1638
+ class PostgresToolServerToolRepository {
1639
+ constructor(database) {
1640
+ this.database = database;
1641
+ }
1642
+ async listByServer(serverId) {
1643
+ const result = await this.database.query(`SELECT * FROM tool_server_tools
1644
+ WHERE server_id = $1
1645
+ ORDER BY name ASC`, [serverId]);
1646
+ return result.rows.map(toolServerTool);
1647
+ }
1648
+ async replaceAllForServer(serverId, tools) {
1649
+ await this.database.transaction(async (client) => {
1650
+ const previousResult = await client.query(`SELECT name, enabled, side_effect FROM tool_server_tools
1651
+ WHERE server_id = $1 FOR UPDATE`, [serverId]);
1652
+ const previous = new Map(previousResult.rows.map(row => [
1653
+ row.name,
1654
+ {
1655
+ enabled: number(row.enabled, 'tool enabled'),
1656
+ side_effect: number(row.side_effect, 'tool side_effect'),
1657
+ },
1658
+ ]));
1659
+ await client.query('DELETE FROM tool_server_tools WHERE server_id = $1', [
1660
+ serverId,
1661
+ ]);
1662
+ for (const tool of tools) {
1663
+ if (tool.server_id !== serverId) {
1664
+ throw new Error('A tool row does not belong to its server');
1665
+ }
1666
+ const kept = previous.get(tool.name);
1667
+ await client.query(`INSERT INTO tool_server_tools
1668
+ (id, server_id, name, description, params_schema, detail,
1669
+ side_effect, enabled, created_at, updated_at)
1670
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
1671
+ tool.id,
1672
+ tool.server_id,
1673
+ tool.name,
1674
+ tool.description,
1675
+ tool.params_schema,
1676
+ tool.detail,
1677
+ kept ? kept.side_effect : tool.side_effect,
1678
+ kept ? kept.enabled : tool.enabled,
1679
+ tool.created_at,
1680
+ tool.updated_at,
1681
+ ]);
1682
+ }
1683
+ });
1684
+ }
1685
+ async updateOverrides(serverId, toolName, overrides, updatedAt) {
1686
+ const assignments = ['updated_at = $1'];
1687
+ const values = [updatedAt];
1688
+ if (overrides.enabled !== undefined) {
1689
+ values.push(overrides.enabled);
1690
+ assignments.push(`enabled = $${values.length}`);
1691
+ }
1692
+ if (overrides.side_effect !== undefined) {
1693
+ values.push(overrides.side_effect);
1694
+ assignments.push(`side_effect = $${values.length}`);
1695
+ }
1696
+ values.push(serverId, toolName);
1697
+ const result = await this.database.query(`UPDATE tool_server_tools
1698
+ SET ${assignments.join(', ')}
1699
+ WHERE server_id = $${values.length - 1} AND name = $${values.length}
1700
+ RETURNING *`, values);
1701
+ return result.rows[0] ? toolServerTool(result.rows[0]) : null;
1702
+ }
1703
+ }
1704
+ class PostgresToolServerCredentialRepository {
1705
+ constructor(database) {
1706
+ this.database = database;
1707
+ }
1708
+ async find(serverId, userId) {
1709
+ const result = await this.database.query(`SELECT * FROM tool_server_credentials
1710
+ WHERE server_id = $1 AND user_id = $2`, [serverId, userId]);
1711
+ return result.rows[0] ? toolServerCredential(result.rows[0]) : null;
1712
+ }
1713
+ async upsert(credential) {
1714
+ await this.database.query(`INSERT INTO tool_server_credentials
1715
+ (id, server_id, user_id, secret, created_at, updated_at)
1716
+ VALUES ($1, $2, $3, $4, $5, $6)
1717
+ ON CONFLICT (server_id, user_id) DO UPDATE SET
1718
+ secret = EXCLUDED.secret,
1719
+ updated_at = EXCLUDED.updated_at`, [
1720
+ credential.id,
1721
+ credential.server_id,
1722
+ credential.user_id,
1723
+ credential.secret,
1724
+ credential.created_at,
1725
+ credential.updated_at,
1726
+ ]);
1727
+ }
1728
+ async delete(serverId, userId) {
1729
+ return (changes((await this.database.query(`DELETE FROM tool_server_credentials
1730
+ WHERE server_id = $1 AND user_id = $2`, [serverId, userId])).rowCount) > 0);
1731
+ }
1732
+ }
1733
+ class PostgresToolApprovalRepository {
1734
+ constructor(database) {
1735
+ this.database = database;
1736
+ }
1737
+ async insert(approval) {
1738
+ await this.database.query(`INSERT INTO tool_approvals
1739
+ (id, user_id, session_id, server_id, tool_name, call_id,
1740
+ arguments_digest, scope, status, created_at, resolved_at, expires_at)
1741
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
1742
+ approval.id,
1743
+ approval.user_id,
1744
+ approval.session_id,
1745
+ approval.server_id,
1746
+ approval.tool_name,
1747
+ approval.call_id,
1748
+ approval.arguments_digest,
1749
+ approval.scope,
1750
+ approval.status,
1751
+ approval.created_at,
1752
+ approval.resolved_at,
1753
+ approval.expires_at,
1754
+ ]);
1755
+ }
1756
+ async findByOwner(approvalId, userId) {
1757
+ const result = await this.database.query('SELECT * FROM tool_approvals WHERE id = $1 AND user_id = $2', [approvalId, userId]);
1758
+ return result.rows[0] ? toolApproval(result.rows[0]) : null;
1759
+ }
1760
+ async findStanding(userId, serverId, toolName, sessionId) {
1761
+ const result = await this.database.query(`SELECT * FROM tool_approvals
1762
+ WHERE user_id = $1
1763
+ AND status = 'approved'
1764
+ AND tool_name = $2
1765
+ AND ((server_id IS NULL AND $3::text IS NULL) OR server_id = $3)
1766
+ AND (scope = 'always'
1767
+ OR (scope = 'session' AND session_id IS NOT NULL AND session_id = $4))
1768
+ ORDER BY resolved_at DESC
1769
+ LIMIT 1`, [userId, toolName, serverId, sessionId]);
1770
+ return result.rows[0] ? toolApproval(result.rows[0]) : null;
1771
+ }
1772
+ async resolvePending(approvalId, userId, status, scope, resolvedAt) {
1773
+ const result = await this.database.query(`UPDATE tool_approvals
1774
+ SET status = $1, scope = $2, resolved_at = $3
1775
+ WHERE id = $4 AND user_id = $5 AND status = 'pending'
1776
+ AND (expires_at IS NULL OR expires_at > $3)
1777
+ RETURNING *`, [status, scope, resolvedAt, approvalId, userId]);
1778
+ return result.rows[0] ? toolApproval(result.rows[0]) : null;
1779
+ }
1780
+ async listPendingByOwner(userId, maximum) {
1781
+ const result = await this.database.query(`SELECT * FROM tool_approvals
1782
+ WHERE user_id = $1 AND status = 'pending'
1783
+ ORDER BY created_at ASC
1784
+ LIMIT $2`, [userId, maximum]);
1785
+ return result.rows.map(toolApproval);
1786
+ }
1787
+ async listStandingByOwner(userId, maximum) {
1788
+ const result = await this.database.query(`SELECT * FROM tool_approvals
1789
+ WHERE user_id = $1 AND status = 'approved'
1790
+ AND scope IN ('session', 'always')
1791
+ ORDER BY resolved_at DESC
1792
+ LIMIT $2`, [userId, maximum]);
1793
+ return result.rows.map(toolApproval);
1794
+ }
1795
+ async expirePending(now) {
1796
+ return changes((await this.database.query(`UPDATE tool_approvals
1797
+ SET status = 'expired', resolved_at = $1
1798
+ WHERE status = 'pending' AND expires_at IS NOT NULL
1799
+ AND expires_at <= $1`, [now])).rowCount);
1800
+ }
1801
+ async deleteByOwner(approvalId, userId) {
1802
+ return (changes((await this.database.query('DELETE FROM tool_approvals WHERE id = $1 AND user_id = $2', [approvalId, userId])).rowCount) > 0);
1803
+ }
1804
+ }
1805
+ class PostgresPromptRepository {
1806
+ constructor(database) {
1807
+ this.database = database;
1808
+ }
1809
+ async listByOwner(userId, maximum) {
1810
+ const result = await this.database.query(`SELECT * FROM prompts
1811
+ WHERE user_id = $1
1812
+ ORDER BY updated_at DESC
1813
+ LIMIT $2`, [userId, maximum]);
1814
+ return result.rows.map(prompt);
1815
+ }
1816
+ async findByOwner(promptId, userId) {
1817
+ const result = await this.database.query('SELECT * FROM prompts WHERE id = $1 AND user_id = $2', [promptId, userId]);
1818
+ return result.rows[0] ? prompt(result.rows[0]) : null;
1819
+ }
1820
+ async findById(promptId) {
1821
+ const result = await this.database.query('SELECT * FROM prompts WHERE id = $1', [promptId]);
1822
+ return result.rows[0] ? prompt(result.rows[0]) : null;
1823
+ }
1824
+ async findBySlug(userId, slug) {
1825
+ const result = await this.database.query('SELECT * FROM prompts WHERE user_id = $1 AND slug = $2', [userId, slug]);
1826
+ return result.rows[0] ? prompt(result.rows[0]) : null;
1827
+ }
1828
+ async replaceWithLimit(value, maximum, archivedVersion) {
1829
+ await this.database.transaction(async (client) => {
1830
+ await lockOwner(client, value.user_id);
1831
+ const existing = await client.query('SELECT user_id FROM prompts WHERE id = $1 FOR UPDATE', [value.id]);
1832
+ if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
1833
+ throw new PersistenceResourceConflictError();
1834
+ }
1835
+ if (!existing.rows[0]) {
1836
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM prompts WHERE user_id = $1', [value.user_id]);
1837
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
1838
+ throw new PersistenceResourceLimitError('prompt', maximum);
1839
+ }
1840
+ }
1841
+ const result = await client.query(`INSERT INTO prompts
1842
+ (id, user_id, slug, title, description, content, variables, tags,
1843
+ version, created_at, updated_at)
1844
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
1845
+ ON CONFLICT (id) DO UPDATE SET
1846
+ slug = EXCLUDED.slug,
1847
+ title = EXCLUDED.title,
1848
+ description = EXCLUDED.description,
1849
+ content = EXCLUDED.content,
1850
+ variables = EXCLUDED.variables,
1851
+ tags = EXCLUDED.tags,
1852
+ version = EXCLUDED.version,
1853
+ updated_at = EXCLUDED.updated_at
1854
+ WHERE prompts.user_id = EXCLUDED.user_id`, [
1855
+ value.id,
1856
+ value.user_id,
1857
+ value.slug,
1858
+ value.title,
1859
+ value.description,
1860
+ value.content,
1861
+ value.variables,
1862
+ value.tags,
1863
+ value.version,
1864
+ value.created_at,
1865
+ value.updated_at,
1866
+ ]);
1867
+ if (result.rowCount !== 1)
1868
+ throw new PersistenceResourceConflictError();
1869
+ if (archivedVersion) {
1870
+ await client.query(`INSERT INTO prompt_versions
1871
+ (id, prompt_id, version, content, variables, created_at)
1872
+ VALUES ($1, $2, $3, $4, $5, $6)
1873
+ ON CONFLICT (prompt_id, version) DO NOTHING`, [
1874
+ archivedVersion.id,
1875
+ archivedVersion.prompt_id,
1876
+ archivedVersion.version,
1877
+ archivedVersion.content,
1878
+ archivedVersion.variables,
1879
+ archivedVersion.created_at,
1880
+ ]);
1881
+ }
1882
+ });
1883
+ }
1884
+ async listVersions(promptId, maximum) {
1885
+ const result = await this.database.query(`SELECT * FROM prompt_versions
1886
+ WHERE prompt_id = $1
1887
+ ORDER BY version DESC
1888
+ LIMIT $2`, [promptId, maximum]);
1889
+ return result.rows.map(promptVersion);
1890
+ }
1891
+ async findVersion(promptId, version) {
1892
+ const result = await this.database.query('SELECT * FROM prompt_versions WHERE prompt_id = $1 AND version = $2', [promptId, version]);
1893
+ return result.rows[0] ? promptVersion(result.rows[0]) : null;
1894
+ }
1895
+ async deleteByOwner(promptId, userId) {
1896
+ return (changes((await this.database.query('DELETE FROM prompts WHERE id = $1 AND user_id = $2', [promptId, userId])).rowCount) > 0);
411
1897
  }
412
1898
  }
413
- class PostgresAutomationRepository {
1899
+ class PostgresSkillRepository {
414
1900
  constructor(database) {
415
1901
  this.database = database;
416
1902
  }
417
1903
  async listByOwner(userId, maximum) {
418
- const result = await this.database.query(`SELECT * FROM automations
1904
+ const result = await this.database.query(`SELECT * FROM skills
419
1905
  WHERE user_id = $1
420
1906
  ORDER BY updated_at DESC
421
1907
  LIMIT $2`, [userId, maximum]);
422
- return result.rows.map(automation);
1908
+ return result.rows.map(skill);
423
1909
  }
424
- async findByOwner(automationId, userId) {
425
- const result = await this.database.query('SELECT * FROM automations WHERE id = $1 AND user_id = $2', [automationId, userId]);
426
- return result.rows[0] ? automation(result.rows[0]) : null;
1910
+ async findByOwner(skillId, userId) {
1911
+ const result = await this.database.query('SELECT * FROM skills WHERE id = $1 AND user_id = $2', [skillId, userId]);
1912
+ return result.rows[0] ? skill(result.rows[0]) : null;
427
1913
  }
428
- async findById(automationId) {
429
- const result = await this.database.query('SELECT * FROM automations WHERE id = $1', [automationId]);
430
- return result.rows[0] ? automation(result.rows[0]) : null;
1914
+ async findById(skillId) {
1915
+ const result = await this.database.query('SELECT * FROM skills WHERE id = $1', [skillId]);
1916
+ return result.rows[0] ? skill(result.rows[0]) : null;
431
1917
  }
432
- async replaceWithLimit(value, maximum) {
1918
+ async findBySlug(userId, slug) {
1919
+ const result = await this.database.query('SELECT * FROM skills WHERE user_id = $1 AND slug = $2', [userId, slug]);
1920
+ return result.rows[0] ? skill(result.rows[0]) : null;
1921
+ }
1922
+ async replaceWithLimit(value, maximum, archivedVersion) {
433
1923
  await this.database.transaction(async (client) => {
434
1924
  await lockOwner(client, value.user_id);
435
- const existing = await client.query('SELECT user_id FROM automations WHERE id = $1 FOR UPDATE', [value.id]);
1925
+ const existing = await client.query('SELECT user_id FROM skills WHERE id = $1 FOR UPDATE', [value.id]);
436
1926
  if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
437
1927
  throw new PersistenceResourceConflictError();
438
1928
  }
439
1929
  if (!existing.rows[0]) {
440
- const count = await client.query('SELECT COUNT(*)::text AS count FROM automations WHERE user_id = $1', [value.user_id]);
1930
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM skills WHERE user_id = $1', [value.user_id]);
441
1931
  if (Number(count.rows[0]?.count || 0) >= maximum) {
442
- throw new PersistenceResourceLimitError('automation', maximum);
1932
+ throw new PersistenceResourceLimitError('skill', maximum);
443
1933
  }
444
1934
  }
445
- const result = await client.query(`INSERT INTO automations
446
- (id, user_id, name, instructions, triggers, provider, model,
447
- notify, status, next_run_at, last_run_at, created_at, updated_at)
448
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
1935
+ const result = await client.query(`INSERT INTO skills
1936
+ (id, user_id, slug, name, description, instructions, enabled,
1937
+ version, created_at, updated_at)
1938
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
449
1939
  ON CONFLICT (id) DO UPDATE SET
1940
+ slug = EXCLUDED.slug,
450
1941
  name = EXCLUDED.name,
1942
+ description = EXCLUDED.description,
451
1943
  instructions = EXCLUDED.instructions,
452
- triggers = EXCLUDED.triggers,
453
- provider = EXCLUDED.provider,
454
- model = EXCLUDED.model,
455
- notify = EXCLUDED.notify,
456
- status = EXCLUDED.status,
457
- next_run_at = EXCLUDED.next_run_at,
458
- last_run_at = EXCLUDED.last_run_at,
1944
+ enabled = EXCLUDED.enabled,
1945
+ version = EXCLUDED.version,
459
1946
  updated_at = EXCLUDED.updated_at
460
- WHERE automations.user_id = EXCLUDED.user_id`, [
1947
+ WHERE skills.user_id = EXCLUDED.user_id`, [
461
1948
  value.id,
462
1949
  value.user_id,
1950
+ value.slug,
463
1951
  value.name,
1952
+ value.description,
464
1953
  value.instructions,
465
- value.triggers,
466
- value.provider,
467
- value.model,
468
- value.notify,
469
- value.status,
470
- value.next_run_at,
471
- value.last_run_at,
1954
+ value.enabled,
1955
+ value.version,
472
1956
  value.created_at,
473
1957
  value.updated_at,
474
1958
  ]);
475
1959
  if (result.rowCount !== 1)
476
1960
  throw new PersistenceResourceConflictError();
1961
+ if (archivedVersion) {
1962
+ await client.query(`INSERT INTO skill_versions
1963
+ (id, skill_id, version, instructions, created_at)
1964
+ VALUES ($1, $2, $3, $4, $5)
1965
+ ON CONFLICT (skill_id, version) DO NOTHING`, [
1966
+ archivedVersion.id,
1967
+ archivedVersion.skill_id,
1968
+ archivedVersion.version,
1969
+ archivedVersion.instructions,
1970
+ archivedVersion.created_at,
1971
+ ]);
1972
+ }
477
1973
  });
478
1974
  }
479
- async listDue(now, maximum) {
480
- const result = await this.database.query(`SELECT * FROM automations
481
- WHERE status = 'active'
482
- AND next_run_at IS NOT NULL
483
- AND next_run_at <= $1
484
- ORDER BY next_run_at ASC
485
- LIMIT $2`, [now, maximum]);
486
- return result.rows.map(automation);
487
- }
488
- async advanceNextRun(automationId, observedNextRunAt, nextRunAt, lastRunAt) {
489
- return (changes((await this.database.query(`UPDATE automations
490
- SET next_run_at = $1, last_run_at = $2
491
- WHERE id = $3 AND next_run_at = $4`, [nextRunAt, lastRunAt, automationId, observedNextRunAt])).rowCount) > 0);
1975
+ async listVersions(skillId, maximum) {
1976
+ const result = await this.database.query(`SELECT * FROM skill_versions
1977
+ WHERE skill_id = $1
1978
+ ORDER BY version DESC
1979
+ LIMIT $2`, [skillId, maximum]);
1980
+ return result.rows.map(skillVersion);
492
1981
  }
493
- async setStatus(automationId, userId, status, nextRunAt, updatedAt) {
494
- return (changes((await this.database.query(`UPDATE automations
495
- SET status = $1, next_run_at = $2, updated_at = $3
496
- WHERE id = $4 AND user_id = $5`, [status, nextRunAt, updatedAt, automationId, userId])).rowCount) > 0);
1982
+ async findVersion(skillId, version) {
1983
+ const result = await this.database.query('SELECT * FROM skill_versions WHERE skill_id = $1 AND version = $2', [skillId, version]);
1984
+ return result.rows[0] ? skillVersion(result.rows[0]) : null;
497
1985
  }
498
- async deleteByOwner(automationId, userId) {
499
- return (changes((await this.database.query('DELETE FROM automations WHERE id = $1 AND user_id = $2', [automationId, userId])).rowCount) > 0);
1986
+ async deleteByOwner(skillId, userId) {
1987
+ return (changes((await this.database.query('DELETE FROM skills WHERE id = $1 AND user_id = $2', [skillId, userId])).rowCount) > 0);
500
1988
  }
501
1989
  }
502
- class PostgresAutomationRunRepository {
1990
+ class PostgresSkillFileRepository {
503
1991
  constructor(database) {
504
1992
  this.database = database;
505
1993
  }
506
- async insert(run) {
507
- await this.database.query(`INSERT INTO automation_runs
508
- (id, automation_id, user_id, scheduled_for, started_at, finished_at,
509
- status, session_id, assistant_message_id, error, seen_at, created_at)
510
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
511
- run.id,
512
- run.automation_id,
513
- run.user_id,
514
- run.scheduled_for,
515
- run.started_at,
516
- run.finished_at,
517
- run.status,
518
- run.session_id,
519
- run.assistant_message_id,
520
- run.error,
521
- run.seen_at,
522
- run.created_at,
523
- ]);
524
- }
525
- async findByOwner(runId, userId) {
526
- const result = await this.database.query('SELECT * FROM automation_runs WHERE id = $1 AND user_id = $2', [runId, userId]);
527
- return result.rows[0] ? automationRun(result.rows[0]) : null;
528
- }
529
- async listByOwner(userId, options) {
530
- const clauses = ['user_id = $1'];
531
- const values = [userId];
532
- if (options.automationId !== undefined) {
533
- values.push(options.automationId);
534
- clauses.push(`automation_id = $${values.length}`);
535
- }
536
- if (options.from !== undefined) {
537
- values.push(options.from);
538
- clauses.push(`scheduled_for >= $${values.length}`);
539
- }
540
- if (options.to !== undefined) {
541
- values.push(options.to);
542
- clauses.push(`scheduled_for < $${values.length}`);
543
- }
544
- values.push(options.maximum);
545
- const result = await this.database.query(`SELECT * FROM automation_runs
546
- WHERE ${clauses.join(' AND ')}
547
- ORDER BY scheduled_for DESC
548
- LIMIT $${values.length}`, values);
549
- return result.rows.map(automationRun);
550
- }
551
- async listUnfinished(maximum) {
552
- const result = await this.database.query(`SELECT * FROM automation_runs
553
- WHERE status IN ('queued', 'running')
554
- ORDER BY scheduled_for ASC
555
- LIMIT $1`, [maximum]);
556
- return result.rows.map(automationRun);
1994
+ async listBySkill(skillId) {
1995
+ const result = await this.database.query(`SELECT * FROM skill_files
1996
+ WHERE skill_id = $1
1997
+ ORDER BY path ASC`, [skillId]);
1998
+ return result.rows.map(skillFile);
557
1999
  }
558
- async markStarted(runId, sessionId, assistantMessageId, startedAt) {
559
- return (changes((await this.database.query(`UPDATE automation_runs
560
- SET session_id = $1, assistant_message_id = $2, started_at = $3,
561
- status = 'running'
562
- WHERE id = $4 AND status = 'queued'`, [sessionId, assistantMessageId, startedAt, runId])).rowCount) > 0);
2000
+ async find(skillId, path) {
2001
+ const result = await this.database.query('SELECT * FROM skill_files WHERE skill_id = $1 AND path = $2', [skillId, path]);
2002
+ return result.rows[0] ? skillFile(result.rows[0]) : null;
563
2003
  }
564
- async finalize(runId, status, finishedAt, error) {
565
- return (changes((await this.database.query(`UPDATE automation_runs
566
- SET status = $1, finished_at = $2, error = $3
567
- WHERE id = $4 AND status IN ('queued', 'running')`, [status, finishedAt, error, runId])).rowCount) > 0);
2004
+ async upsert(file, maximumPerSkill) {
2005
+ await this.database.transaction(async (client) => {
2006
+ const existing = await client.query('SELECT id FROM skill_files WHERE skill_id = $1 AND path = $2 FOR UPDATE', [file.skill_id, file.path]);
2007
+ if (!existing.rows[0]) {
2008
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM skill_files WHERE skill_id = $1', [file.skill_id]);
2009
+ if (Number(count.rows[0]?.count || 0) >= maximumPerSkill) {
2010
+ throw new PersistenceResourceLimitError('skill-file', maximumPerSkill);
2011
+ }
2012
+ }
2013
+ await client.query(`INSERT INTO skill_files
2014
+ (id, skill_id, path, content, size, created_at, updated_at)
2015
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
2016
+ ON CONFLICT (skill_id, path) DO UPDATE SET
2017
+ content = EXCLUDED.content,
2018
+ size = EXCLUDED.size,
2019
+ updated_at = EXCLUDED.updated_at`, [
2020
+ file.id,
2021
+ file.skill_id,
2022
+ file.path,
2023
+ file.content,
2024
+ file.size,
2025
+ file.created_at,
2026
+ file.updated_at,
2027
+ ]);
2028
+ });
568
2029
  }
569
- async countUnseenFinished(userId) {
570
- const result = await this.database.query(`SELECT COUNT(*)::text AS count FROM automation_runs
571
- WHERE user_id = $1
572
- AND status IN ('succeeded', 'failed')
573
- AND seen_at IS NULL`, [userId]);
574
- return Number(result.rows[0]?.count || 0);
2030
+ async replaceAllForSkill(skillId, files) {
2031
+ await this.database.transaction(async (client) => {
2032
+ await client.query('DELETE FROM skill_files WHERE skill_id = $1', [
2033
+ skillId,
2034
+ ]);
2035
+ for (const file of files) {
2036
+ if (file.skill_id !== skillId) {
2037
+ throw new Error('A skill file does not belong to its skill');
2038
+ }
2039
+ await client.query(`INSERT INTO skill_files
2040
+ (id, skill_id, path, content, size, created_at, updated_at)
2041
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
2042
+ file.id,
2043
+ file.skill_id,
2044
+ file.path,
2045
+ file.content,
2046
+ file.size,
2047
+ file.created_at,
2048
+ file.updated_at,
2049
+ ]);
2050
+ }
2051
+ });
575
2052
  }
576
- async markSeenBefore(userId, seenAt) {
577
- return changes((await this.database.query(`UPDATE automation_runs
578
- SET seen_at = $1
579
- WHERE user_id = $2
580
- AND status IN ('succeeded', 'failed')
581
- AND seen_at IS NULL
582
- AND finished_at IS NOT NULL
583
- AND finished_at <= $3`, [seenAt, userId, seenAt])).rowCount);
2053
+ async delete(skillId, path) {
2054
+ return (changes((await this.database.query('DELETE FROM skill_files WHERE skill_id = $1 AND path = $2', [skillId, path])).rowCount) > 0);
584
2055
  }
585
2056
  }
586
2057
  class PostgresSessionFolderRepository {
@@ -736,6 +2207,10 @@ const ARCHIVE_TABLES = {
736
2207
  'knowledge-collection': 'knowledge_collections',
737
2208
  document: 'documents',
738
2209
  persona: 'personas',
2210
+ prompt: 'prompts',
2211
+ skill: 'skills',
2212
+ 'tool-server': 'tool_servers',
2213
+ calendar: 'calendars',
739
2214
  };
740
2215
  class PostgresDataArchiveRepository {
741
2216
  constructor(database) {
@@ -1027,17 +2502,339 @@ class PostgresDataArchiveRepository {
1027
2502
  });
1028
2503
  }
1029
2504
  }
2505
+ const modelTariff = (row) => ({
2506
+ ...row,
2507
+ input_per_million: row.input_per_million === null ? null : Number(row.input_per_million),
2508
+ output_per_million: row.output_per_million === null ? null : Number(row.output_per_million),
2509
+ unit_price: row.unit_price === null ? null : Number(row.unit_price),
2510
+ effective_from: number(row.effective_from, 'tariff effective_from'),
2511
+ created_at: number(row.created_at, 'tariff created_at'),
2512
+ });
2513
+ const usageBudget = (row) => ({
2514
+ ...row,
2515
+ amount_usd: Number(row.amount_usd),
2516
+ created_at: number(row.created_at, 'budget created_at'),
2517
+ updated_at: number(row.updated_at, 'budget updated_at'),
2518
+ });
2519
+ const messageFeedback = (row) => ({
2520
+ ...row,
2521
+ rating: number(row.rating, 'feedback rating'),
2522
+ created_at: number(row.created_at, 'feedback created_at'),
2523
+ updated_at: number(row.updated_at, 'feedback updated_at'),
2524
+ });
2525
+ const arenaVote = (row) => ({
2526
+ ...row,
2527
+ created_at: number(row.created_at, 'arena vote created_at'),
2528
+ });
2529
+ const evalSet = (row) => ({
2530
+ ...row,
2531
+ created_at: number(row.created_at, 'eval set created_at'),
2532
+ updated_at: number(row.updated_at, 'eval set updated_at'),
2533
+ });
2534
+ const evalRun = (row) => ({
2535
+ ...row,
2536
+ created_at: number(row.created_at, 'eval run created_at'),
2537
+ updated_at: number(row.updated_at, 'eval run updated_at'),
2538
+ completed_at: row.completed_at === null
2539
+ ? null
2540
+ : number(row.completed_at, 'eval run completed_at'),
2541
+ });
2542
+ class PostgresModelTariffRepository {
2543
+ constructor(database) {
2544
+ this.database = database;
2545
+ }
2546
+ async listAll(maximum) {
2547
+ const result = await this.database.query(`SELECT * FROM model_tariffs
2548
+ ORDER BY plugin_id ASC, model ASC, effective_from DESC
2549
+ LIMIT $1`, [maximum]);
2550
+ return result.rows.map(modelTariff);
2551
+ }
2552
+ async insert(tariff) {
2553
+ await this.database.query(`INSERT INTO model_tariffs
2554
+ (id, plugin_id, model, input_per_million, output_per_million,
2555
+ unit_price, currency, effective_from, created_by, created_at)
2556
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
2557
+ tariff.id,
2558
+ tariff.plugin_id,
2559
+ tariff.model,
2560
+ tariff.input_per_million,
2561
+ tariff.output_per_million,
2562
+ tariff.unit_price,
2563
+ tariff.currency,
2564
+ tariff.effective_from,
2565
+ tariff.created_by,
2566
+ tariff.created_at,
2567
+ ]);
2568
+ }
2569
+ async deleteById(tariffId) {
2570
+ const result = await this.database.query('DELETE FROM model_tariffs WHERE id = $1', [tariffId]);
2571
+ return changes(result.rowCount) > 0;
2572
+ }
2573
+ }
2574
+ class PostgresUsageBudgetRepository {
2575
+ constructor(database) {
2576
+ this.database = database;
2577
+ }
2578
+ async listAll(maximum) {
2579
+ const result = await this.database.query(`SELECT * FROM usage_budgets
2580
+ ORDER BY created_at ASC, id ASC
2581
+ LIMIT $1`, [maximum]);
2582
+ return result.rows.map(usageBudget);
2583
+ }
2584
+ async findById(budgetId) {
2585
+ const result = await this.database.query('SELECT * FROM usage_budgets WHERE id = $1', [budgetId]);
2586
+ return result.rows[0] ? usageBudget(result.rows[0]) : null;
2587
+ }
2588
+ async replace(budget) {
2589
+ await this.database.query(`INSERT INTO usage_budgets
2590
+ (id, name, principal_type, principal_id, period, amount_usd,
2591
+ mode, created_by, created_at, updated_at)
2592
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
2593
+ ON CONFLICT (id) DO UPDATE SET
2594
+ name = EXCLUDED.name,
2595
+ principal_type = EXCLUDED.principal_type,
2596
+ principal_id = EXCLUDED.principal_id,
2597
+ period = EXCLUDED.period,
2598
+ amount_usd = EXCLUDED.amount_usd,
2599
+ mode = EXCLUDED.mode,
2600
+ updated_at = EXCLUDED.updated_at`, [
2601
+ budget.id,
2602
+ budget.name,
2603
+ budget.principal_type,
2604
+ budget.principal_id,
2605
+ budget.period,
2606
+ budget.amount_usd,
2607
+ budget.mode,
2608
+ budget.created_by,
2609
+ budget.created_at,
2610
+ budget.updated_at,
2611
+ ]);
2612
+ }
2613
+ async deleteById(budgetId) {
2614
+ const result = await this.database.query('DELETE FROM usage_budgets WHERE id = $1', [budgetId]);
2615
+ return changes(result.rowCount) > 0;
2616
+ }
2617
+ }
2618
+ class PostgresMessageFeedbackRepository {
2619
+ constructor(database) {
2620
+ this.database = database;
2621
+ }
2622
+ async upsertByMessage(feedback) {
2623
+ await this.database.query(`INSERT INTO message_feedback
2624
+ (id, user_id, session_id, message_id, rating, tags, comment,
2625
+ model, plugin_id, snapshot, created_at, updated_at)
2626
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
2627
+ ON CONFLICT (user_id, message_id) DO UPDATE SET
2628
+ rating = EXCLUDED.rating,
2629
+ tags = EXCLUDED.tags,
2630
+ comment = EXCLUDED.comment,
2631
+ model = EXCLUDED.model,
2632
+ plugin_id = EXCLUDED.plugin_id,
2633
+ snapshot = EXCLUDED.snapshot,
2634
+ updated_at = EXCLUDED.updated_at`, [
2635
+ feedback.id,
2636
+ feedback.user_id,
2637
+ feedback.session_id,
2638
+ feedback.message_id,
2639
+ feedback.rating,
2640
+ feedback.tags,
2641
+ feedback.comment,
2642
+ feedback.model,
2643
+ feedback.plugin_id,
2644
+ feedback.snapshot,
2645
+ feedback.created_at,
2646
+ feedback.updated_at,
2647
+ ]);
2648
+ }
2649
+ async deleteByMessage(userId, messageId) {
2650
+ const result = await this.database.query('DELETE FROM message_feedback WHERE user_id = $1 AND message_id = $2', [userId, messageId]);
2651
+ return changes(result.rowCount) > 0;
2652
+ }
2653
+ async listByOwner(userId, maximum) {
2654
+ const result = await this.database.query(`SELECT * FROM message_feedback
2655
+ WHERE user_id = $1
2656
+ ORDER BY created_at DESC, id DESC
2657
+ LIMIT $2`, [userId, maximum]);
2658
+ return result.rows.map(messageFeedback);
2659
+ }
2660
+ async listAll(maximum) {
2661
+ const result = await this.database.query(`SELECT * FROM message_feedback
2662
+ ORDER BY created_at DESC, id DESC
2663
+ LIMIT $1`, [maximum]);
2664
+ return result.rows.map(messageFeedback);
2665
+ }
2666
+ }
2667
+ class PostgresArenaVoteRepository {
2668
+ constructor(database) {
2669
+ this.database = database;
2670
+ }
2671
+ async insertOnce(vote) {
2672
+ const result = await this.database.query(`INSERT INTO arena_votes
2673
+ (id, user_id, compare_group, model_a, model_b, winner, created_at)
2674
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
2675
+ ON CONFLICT (user_id, compare_group) DO NOTHING`, [
2676
+ vote.id,
2677
+ vote.user_id,
2678
+ vote.compare_group,
2679
+ vote.model_a,
2680
+ vote.model_b,
2681
+ vote.winner,
2682
+ vote.created_at,
2683
+ ]);
2684
+ return changes(result.rowCount) > 0;
2685
+ }
2686
+ async listAllOrdered(maximum) {
2687
+ const result = await this.database.query(`SELECT * FROM arena_votes
2688
+ ORDER BY created_at ASC, id ASC
2689
+ LIMIT $1`, [maximum]);
2690
+ return result.rows.map(arenaVote);
2691
+ }
2692
+ }
2693
+ class PostgresEvalSetRepository {
2694
+ constructor(database) {
2695
+ this.database = database;
2696
+ }
2697
+ async listByOwner(userId, maximum) {
2698
+ const result = await this.database.query(`SELECT * FROM eval_sets
2699
+ WHERE user_id = $1
2700
+ ORDER BY updated_at DESC, id DESC
2701
+ LIMIT $2`, [userId, maximum]);
2702
+ return result.rows.map(evalSet);
2703
+ }
2704
+ async findByOwner(setId, userId) {
2705
+ const result = await this.database.query('SELECT * FROM eval_sets WHERE id = $1 AND user_id = $2', [setId, userId]);
2706
+ return result.rows[0] ? evalSet(result.rows[0]) : null;
2707
+ }
2708
+ async replaceWithLimit(value, maximum) {
2709
+ await this.database.transaction(async (client) => {
2710
+ await lockOwner(client, value.user_id);
2711
+ const existing = await client.query('SELECT user_id FROM eval_sets WHERE id = $1 FOR UPDATE', [value.id]);
2712
+ if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
2713
+ throw new PersistenceResourceConflictError();
2714
+ }
2715
+ if (!existing.rows[0]) {
2716
+ const count = await client.query('SELECT COUNT(*)::text AS count FROM eval_sets WHERE user_id = $1', [value.user_id]);
2717
+ if (Number(count.rows[0]?.count || 0) >= maximum) {
2718
+ throw new PersistenceResourceLimitError('eval-set', maximum);
2719
+ }
2720
+ }
2721
+ const result = await client.query(`INSERT INTO eval_sets
2722
+ (id, user_id, name, description, items, created_at, updated_at)
2723
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
2724
+ ON CONFLICT (id) DO UPDATE SET
2725
+ name = EXCLUDED.name,
2726
+ description = EXCLUDED.description,
2727
+ items = EXCLUDED.items,
2728
+ updated_at = EXCLUDED.updated_at
2729
+ WHERE eval_sets.user_id = EXCLUDED.user_id`, [
2730
+ value.id,
2731
+ value.user_id,
2732
+ value.name,
2733
+ value.description,
2734
+ value.items,
2735
+ value.created_at,
2736
+ value.updated_at,
2737
+ ]);
2738
+ if (result.rowCount !== 1)
2739
+ throw new PersistenceResourceConflictError();
2740
+ });
2741
+ }
2742
+ async deleteByOwner(setId, userId) {
2743
+ const result = await this.database.query('DELETE FROM eval_sets WHERE id = $1 AND user_id = $2', [setId, userId]);
2744
+ return changes(result.rowCount) > 0;
2745
+ }
2746
+ }
2747
+ class PostgresEvalRunRepository {
2748
+ constructor(database) {
2749
+ this.database = database;
2750
+ }
2751
+ async listByOwner(userId, maximum) {
2752
+ const result = await this.database.query(`SELECT * FROM eval_runs
2753
+ WHERE user_id = $1
2754
+ ORDER BY created_at DESC, id DESC
2755
+ LIMIT $2`, [userId, maximum]);
2756
+ return result.rows.map(evalRun);
2757
+ }
2758
+ async listBySet(setId, userId, maximum) {
2759
+ const result = await this.database.query(`SELECT * FROM eval_runs
2760
+ WHERE set_id = $1 AND user_id = $2
2761
+ ORDER BY created_at DESC, id DESC
2762
+ LIMIT $3`, [setId, userId, maximum]);
2763
+ return result.rows.map(evalRun);
2764
+ }
2765
+ async findByOwner(runId, userId) {
2766
+ const result = await this.database.query('SELECT * FROM eval_runs WHERE id = $1 AND user_id = $2', [runId, userId]);
2767
+ return result.rows[0] ? evalRun(result.rows[0]) : null;
2768
+ }
2769
+ async insert(run) {
2770
+ await this.database.query(`INSERT INTO eval_runs
2771
+ (id, set_id, user_id, label, plugin_id, model, status, results,
2772
+ error, created_at, updated_at, completed_at)
2773
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
2774
+ run.id,
2775
+ run.set_id,
2776
+ run.user_id,
2777
+ run.label,
2778
+ run.plugin_id,
2779
+ run.model,
2780
+ run.status,
2781
+ run.results,
2782
+ run.error,
2783
+ run.created_at,
2784
+ run.updated_at,
2785
+ run.completed_at,
2786
+ ]);
2787
+ }
2788
+ async update(runId, userId, updates) {
2789
+ const result = await this.database.query(`UPDATE eval_runs SET
2790
+ status = $1,
2791
+ results = COALESCE($2, results),
2792
+ error = $3,
2793
+ updated_at = $4,
2794
+ completed_at = COALESCE($5, completed_at)
2795
+ WHERE id = $6 AND user_id = $7`, [
2796
+ updates.status,
2797
+ updates.results ?? null,
2798
+ updates.error ?? null,
2799
+ updates.updated_at,
2800
+ updates.completed_at ?? null,
2801
+ runId,
2802
+ userId,
2803
+ ]);
2804
+ return changes(result.rowCount) > 0;
2805
+ }
2806
+ }
1030
2807
  export const createPostgresResourceRepositories = (database) => ({
1031
2808
  chatSessions: new PostgresChatSessionRepository(database),
1032
2809
  knowledgeCollections: new PostgresKnowledgeCollectionRepository(database),
1033
2810
  notes: new PostgresNoteRepository(database),
2811
+ calendars: new PostgresCalendarRepository(database),
1034
2812
  calendarEvents: new PostgresCalendarEventRepository(database),
2813
+ channels: new PostgresChannelRepository(database),
2814
+ channelMessages: new PostgresChannelMessageRepository(database),
2815
+ notifications: new PostgresNotificationRepository(database),
2816
+ webhookTargets: new PostgresWebhookTargetRepository(database),
2817
+ pushSubscriptions: new PostgresPushSubscriptionRepository(database),
2818
+ recoveryDrills: new PostgresRecoveryDrillRepository(database),
1035
2819
  automations: new PostgresAutomationRepository(database),
1036
2820
  automationRuns: new PostgresAutomationRunRepository(database),
2821
+ toolServers: new PostgresToolServerRepository(database),
2822
+ toolServerTools: new PostgresToolServerToolRepository(database),
2823
+ toolServerCredentials: new PostgresToolServerCredentialRepository(database),
2824
+ toolApprovals: new PostgresToolApprovalRepository(database),
2825
+ prompts: new PostgresPromptRepository(database),
2826
+ skills: new PostgresSkillRepository(database),
2827
+ skillFiles: new PostgresSkillFileRepository(database),
1037
2828
  sessionFolders: new PostgresSessionFolderRepository(database),
1038
2829
  preferences: new PostgresPreferenceRepository(database),
1039
2830
  systemSettings: new PostgresSystemSettingRepository(database),
1040
2831
  archive: new PostgresDataArchiveRepository(database),
2832
+ modelTariffs: new PostgresModelTariffRepository(database),
2833
+ usageBudgets: new PostgresUsageBudgetRepository(database),
2834
+ messageFeedback: new PostgresMessageFeedbackRepository(database),
2835
+ arenaVotes: new PostgresArenaVoteRepository(database),
2836
+ evalSets: new PostgresEvalSetRepository(database),
2837
+ evalRuns: new PostgresEvalRunRepository(database),
1041
2838
  });
1042
2839
  /** Create transaction-scoped repositories over one pinned pooled client. */
1043
2840
  export const createPostgresTransactionalResourceRepositories = (database, client) => {