@agenticmail/enterprise 0.5.319 → 0.5.321

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 (330) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/CODE_OF_CONDUCT.md +31 -0
  3. package/README.md +118 -38
  4. package/SECURITY.md +42 -0
  5. package/dist/agent-heartbeat-3FWNHZFX.js +510 -0
  6. package/dist/agent-heartbeat-4RWHZR7H.js +510 -0
  7. package/dist/agent-heartbeat-6ZGB5ILY.js +510 -0
  8. package/dist/agent-heartbeat-BIVHLKFM.js +510 -0
  9. package/dist/agent-heartbeat-HRKVFK2T.js +510 -0
  10. package/dist/agent-heartbeat-JC5GWVXD.js +510 -0
  11. package/dist/agent-heartbeat-K6A4HMHB.js +510 -0
  12. package/dist/agent-heartbeat-LCDXWFVB.js +510 -0
  13. package/dist/agent-heartbeat-P7HZCZAQ.js +510 -0
  14. package/dist/agent-heartbeat-PUIRSNIO.js +510 -0
  15. package/dist/agent-heartbeat-SN5ILQ6Y.js +510 -0
  16. package/dist/agent-heartbeat-TW5YTDYC.js +510 -0
  17. package/dist/agent-heartbeat-Z2QQXROL.js +510 -0
  18. package/dist/agent-notify-OEQBCZLN.js +43 -0
  19. package/dist/{agent-tools-263HM5QU.js → agent-tools-3W7XLUYA.js} +1 -1
  20. package/dist/agent-tools-4QK7LLNP.js +9 -0
  21. package/dist/agent-tools-54VZGT6L.js +9 -0
  22. package/dist/{agent-tools-AT4D276V.js → agent-tools-AYYDPO27.js} +7 -7
  23. package/dist/{agent-tools-MSTAPX2I.js → agent-tools-F2X47FKF.js} +7 -7
  24. package/dist/{agent-tools-FA26SY5O.js → agent-tools-O6W3QAZL.js} +11 -6
  25. package/dist/agent-tools-OAWVZBMW.js +9 -0
  26. package/dist/agent-tools-QCCU74PN.js +13949 -0
  27. package/dist/chunk-2LHUARN6.js +4929 -0
  28. package/dist/chunk-2WVCNCYC.js +5087 -0
  29. package/dist/{chunk-6PWDS7KY.js → chunk-3FM6YQUK.js} +20 -20
  30. package/dist/chunk-3UAFHUEC.js +212 -0
  31. package/dist/{chunk-WJO57PMO.js → chunk-46GOWZT4.js} +20 -20
  32. package/dist/{chunk-BNRE7TSX.js → chunk-5KYJAUZV.js} +3 -3
  33. package/dist/chunk-6C5PKREN.js +467 -0
  34. package/dist/{chunk-447MTPZF.js → chunk-6ZMLNEHB.js} +3 -3
  35. package/dist/chunk-BPZQT5N5.js +25652 -0
  36. package/dist/chunk-BQM7MBPS.js +1380 -0
  37. package/dist/{chunk-ZRFKGPIU.js → chunk-C52OQNNY.js} +20 -20
  38. package/dist/chunk-C7HGQF4Y.js +25652 -0
  39. package/dist/chunk-CAHNZGGK.js +25656 -0
  40. package/dist/{chunk-FL3CH3ET.js → chunk-CK7R6UHE.js} +51 -27
  41. package/dist/chunk-D36RPWB7.js +25652 -0
  42. package/dist/{chunk-36NM2B4C.js → chunk-DJK2UPFH.js} +63 -93
  43. package/dist/chunk-DM7FTF7W.js +4929 -0
  44. package/dist/chunk-DMD24UFZ.js +5101 -0
  45. package/dist/{chunk-36XNMIHA.js → chunk-DXZGPUAF.js} +20 -20
  46. package/dist/chunk-F46WB5IL.js +5087 -0
  47. package/dist/chunk-F5QG5SQH.js +5087 -0
  48. package/dist/{chunk-JGEVQZDR.js → chunk-FLQ5FLHW.js} +13 -16
  49. package/dist/chunk-H7GP733U.js +5087 -0
  50. package/dist/{chunk-OZSQLOV6.js → chunk-HHBXWB5U.js} +415 -19
  51. package/dist/{chunk-D24JY75H.js → chunk-IMXS4N6W.js} +3 -3
  52. package/dist/{chunk-6PVBV6ZP.js → chunk-JNMDD7JY.js} +3 -3
  53. package/dist/chunk-JTV5LA47.js +1519 -0
  54. package/dist/chunk-KV6G7NZX.js +1519 -0
  55. package/dist/chunk-MU5MEBIK.js +1519 -0
  56. package/dist/chunk-NLT5MC7X.js +465 -0
  57. package/dist/{chunk-GTFZZUXX.js → chunk-NVLYIM4J.js} +51 -27
  58. package/dist/{chunk-6G5SXLXC.js → chunk-NZY2BIZH.js} +63 -93
  59. package/dist/chunk-O42L6G67.js +1519 -0
  60. package/dist/chunk-OCNERGGM.js +4891 -0
  61. package/dist/chunk-OJSNHONE.js +1519 -0
  62. package/dist/{chunk-2TAZJWJN.js → chunk-OWL3QVH7.js} +18 -0
  63. package/dist/{chunk-P3HVY2HS.js → chunk-OWTLNV4Q.js} +382 -7
  64. package/dist/chunk-PCNYEP6T.js +4891 -0
  65. package/dist/{chunk-YL3Z5KPR.js → chunk-PI4AQ4Z6.js} +438 -15
  66. package/dist/chunk-PN3EGTCA.js +194 -0
  67. package/dist/chunk-Q37UKNRC.js +1519 -0
  68. package/dist/chunk-QXTC6J7H.js +5087 -0
  69. package/dist/{chunk-SPBQVNDI.js → chunk-RKERL5LZ.js} +25 -21
  70. package/dist/chunk-RVBK2IOX.js +25652 -0
  71. package/dist/chunk-SAKODCZ5.js +4891 -0
  72. package/dist/{chunk-XV4TU65E.js → chunk-SALGFC5L.js} +51 -27
  73. package/dist/chunk-STGWZ2MS.js +1519 -0
  74. package/dist/chunk-UY3ZVQDP.js +25652 -0
  75. package/dist/chunk-V6OSD62M.js +5087 -0
  76. package/dist/chunk-VP6YAHX4.js +1519 -0
  77. package/dist/chunk-WDYJOEAI.js +5087 -0
  78. package/dist/chunk-WEAFQNOS.js +195 -0
  79. package/dist/chunk-XKUSAZGP.js +5087 -0
  80. package/dist/chunk-Z6K5FKAB.js +548 -0
  81. package/dist/chunk-ZGE3XAXY.js +1519 -0
  82. package/dist/chunk-ZGYVXYQQ.js +3296 -0
  83. package/dist/cli-agent-7TB2BWS6.js +2370 -0
  84. package/dist/cli-agent-AKXFFST2.js +2370 -0
  85. package/dist/cli-agent-DZTKLITB.js +2357 -0
  86. package/dist/cli-agent-FOF7PFEP.js +2357 -0
  87. package/dist/cli-agent-H74M2ZYN.js +2357 -0
  88. package/dist/cli-agent-HORWVPHB.js +2370 -0
  89. package/dist/cli-agent-HSZT6SKF.js +2423 -0
  90. package/dist/cli-agent-JLUQ4ZU6.js +2424 -0
  91. package/dist/cli-agent-MVCDH4HV.js +2370 -0
  92. package/dist/cli-agent-NZXOEPJ2.js +2357 -0
  93. package/dist/cli-agent-PADN3QRC.js +2357 -0
  94. package/dist/cli-agent-QAYEX3BE.js +2441 -0
  95. package/dist/cli-agent-QT64DT5J.js +2370 -0
  96. package/dist/cli-agent-TFL2M6UK.js +2424 -0
  97. package/dist/cli-agent-UIKXATTD.js +2357 -0
  98. package/dist/cli-agent-UJN6FYTO.js +2370 -0
  99. package/dist/cli-agent-VIQAYVY4.js +2357 -0
  100. package/dist/cli-agent-WNWFVOFM.js +2370 -0
  101. package/dist/cli-agent-XBQX67VJ.js +2423 -0
  102. package/dist/cli-agent-ZLSC6FF4.js +2357 -0
  103. package/dist/cli-serve-2IL5DTEY.js +153 -0
  104. package/dist/cli-serve-47N5UKKW.js +153 -0
  105. package/dist/cli-serve-4XGZFUV2.js +140 -0
  106. package/dist/cli-serve-6OT3UEAN.js +140 -0
  107. package/dist/cli-serve-7L6EY5UH.js +153 -0
  108. package/dist/cli-serve-BDGOOOKQ.js +260 -0
  109. package/dist/cli-serve-BFNIW2LF.js +153 -0
  110. package/dist/cli-serve-C7MN6U5Q.js +153 -0
  111. package/dist/cli-serve-CR3OY3IM.js +153 -0
  112. package/dist/cli-serve-DAJFRWQ7.js +153 -0
  113. package/dist/cli-serve-FW6FHFW4.js +153 -0
  114. package/dist/cli-serve-GEEOQS77.js +153 -0
  115. package/dist/cli-serve-H562I3ZK.js +153 -0
  116. package/dist/cli-serve-HDQZF4C4.js +153 -0
  117. package/dist/cli-serve-LICAOMEB.js +140 -0
  118. package/dist/cli-serve-LLGYLWFS.js +153 -0
  119. package/dist/cli-serve-N3OISDNB.js +153 -0
  120. package/dist/cli-serve-TIZ27EVR.js +153 -0
  121. package/dist/cli-serve-TUNI2RCN.js +153 -0
  122. package/dist/cli-serve-WNOZMAWD.js +153 -0
  123. package/dist/cli-validate-Z726VJCN.js +150 -0
  124. package/dist/cli.js +4 -4
  125. package/dist/connection-manager-KAWEUWUR.js +9 -0
  126. package/dist/dashboard/app.js +9 -3
  127. package/dist/dashboard/components/knowledge-link.js +15 -0
  128. package/dist/dashboard/components/settings-help.js +4 -2
  129. package/dist/dashboard/docs/agent-deployment.html +33 -1
  130. package/dist/dashboard/docs/settings-network.html +321 -0
  131. package/dist/dashboard/docs/settings-security.html +347 -0
  132. package/dist/dashboard/docs/settings-tool-security.html +176 -0
  133. package/dist/dashboard/docs/settings.html +36 -16
  134. package/dist/dashboard/pages/agent-detail/deployment.js +39 -6
  135. package/dist/dashboard/pages/agent-detail/tools.js +10 -0
  136. package/dist/dashboard/pages/database-access.js +4 -3
  137. package/dist/dashboard/pages/settings.js +174 -37
  138. package/dist/dashboard/pages/task-pipeline.js +400 -843
  139. package/dist/db-adapter-2T56ORSD.js +7 -0
  140. package/dist/db-adapter-IRHOUMVC.js +7 -0
  141. package/dist/index.js +41 -41
  142. package/dist/microsoft-VREAZ7M2.js +3955 -0
  143. package/dist/routes-3MMLQTB6.js +90 -0
  144. package/dist/routes-4ZUIJ4HE.js +90 -0
  145. package/dist/routes-5MXHKKH4.js +90 -0
  146. package/dist/routes-64NJFK3B.js +90 -0
  147. package/dist/routes-6AKQ2LBV.js +90 -0
  148. package/dist/routes-CRRBUDO4.js +90 -0
  149. package/dist/routes-DIAF3MC3.js +90 -0
  150. package/dist/routes-KMUNU6CY.js +90 -0
  151. package/dist/routes-LRRLXIZR.js +90 -0
  152. package/dist/routes-N647AJYG.js +90 -0
  153. package/dist/routes-SSSELAAR.js +90 -0
  154. package/dist/routes-STERVGKJ.js +90 -0
  155. package/dist/routes-ZEZZACZP.js +90 -0
  156. package/dist/runtime-5EQN4GFM.js +45 -0
  157. package/dist/runtime-5LP7PUD4.js +45 -0
  158. package/dist/runtime-6BULDBR3.js +45 -0
  159. package/dist/runtime-6YEENDN3.js +45 -0
  160. package/dist/runtime-7LQFRG3B.js +45 -0
  161. package/dist/runtime-AMXJU2MB.js +45 -0
  162. package/dist/runtime-D6WSE7FG.js +45 -0
  163. package/dist/runtime-EYVN7NFJ.js +45 -0
  164. package/dist/runtime-F6RPWQVW.js +45 -0
  165. package/dist/runtime-FYMJURFC.js +45 -0
  166. package/dist/runtime-JRNBL4O4.js +45 -0
  167. package/dist/runtime-OM2NIBMI.js +45 -0
  168. package/dist/runtime-QWPVD7CY.js +45 -0
  169. package/dist/runtime-YLIIPTE4.js +45 -0
  170. package/dist/runtime-YU6P22CG.js +45 -0
  171. package/dist/screen-unlock-4RPZBHOI.js +118 -0
  172. package/dist/server-AMCSXINC.js +28 -0
  173. package/dist/server-CU6LVQS4.js +28 -0
  174. package/dist/server-DFYGH2CV.js +28 -0
  175. package/dist/server-EELWOC3X.js +28 -0
  176. package/dist/server-EN5E2OWQ.js +28 -0
  177. package/dist/server-GW2HYJYI.js +28 -0
  178. package/dist/server-J25NCRWJ.js +28 -0
  179. package/dist/server-JDGNOTFV.js +28 -0
  180. package/dist/server-NE5HD5DJ.js +28 -0
  181. package/dist/server-NQOT7W77.js +28 -0
  182. package/dist/server-PWE5PQTR.js +28 -0
  183. package/dist/server-Q2Q32H2B.js +28 -0
  184. package/dist/server-Q77ME7TL.js +28 -0
  185. package/dist/server-WLLH4WST.js +28 -0
  186. package/dist/server-WTUJ2O3F.js +28 -0
  187. package/dist/server-X4CJTHHF.js +28 -0
  188. package/dist/server-XK3ILCJC.js +28 -0
  189. package/dist/server-ZRD3NDJE.js +28 -0
  190. package/dist/setup-44VBAO4J.js +20 -0
  191. package/dist/setup-4ONNQBWB.js +20 -0
  192. package/dist/setup-4OSBXSCL.js +20 -0
  193. package/dist/setup-4QFGRBLZ.js +20 -0
  194. package/dist/setup-6766SGAR.js +20 -0
  195. package/dist/setup-AYY24DKM.js +20 -0
  196. package/dist/setup-B34N4HPU.js +20 -0
  197. package/dist/setup-E2YLC2EY.js +20 -0
  198. package/dist/setup-ER6NXTY5.js +20 -0
  199. package/dist/setup-H2AGCBW5.js +20 -0
  200. package/dist/setup-ICOZRKCX.js +20 -0
  201. package/dist/setup-JFTJH7UF.js +20 -0
  202. package/dist/setup-PRFNI6YW.js +20 -0
  203. package/dist/setup-RAHBMYHE.js +20 -0
  204. package/dist/setup-TXPR5UQX.js +20 -0
  205. package/dist/setup-XCJMELVU.js +20 -0
  206. package/dist/setup-XIYEIFVK.js +20 -0
  207. package/dist/setup-Z4PZSHBI.js +20 -0
  208. package/dist/skills-FR7I5V7H.js +16 -0
  209. package/dist/skills-HCVBA6PK.js +16 -0
  210. package/dist/system-prompts-TM7OA32C.js +913 -0
  211. package/dist/task-queue-O7IVZYUO.js +9 -0
  212. package/dist/transport-encryption-2T7PIXKG.js +25 -0
  213. package/logs/cloudflared-error.log +61 -0
  214. package/logs/cloudflared-out.log +0 -0
  215. package/logs/enterprise-error.log +0 -0
  216. package/logs/enterprise-out.log +3 -0
  217. package/logs/fola-error.log +0 -0
  218. package/logs/fola-out.log +0 -0
  219. package/logs/john-error.log +8 -0
  220. package/logs/john-out.log +0 -0
  221. package/package.json +31 -3
  222. package/src/agent-tools/tool-resolver.ts +50 -61
  223. package/src/agent-tools/tools/enterprise-database.ts +5 -5
  224. package/src/agent-tools/tools/local/dependency-manager.ts +2 -2
  225. package/src/agent-tools/tools/microsoft/graph-api.ts +137 -26
  226. package/src/agent-tools/tools/microsoft/outlook-mail.ts +392 -100
  227. package/src/agent-tools/tools/microsoft/teams.ts +267 -48
  228. package/src/auth/routes.ts +4 -4
  229. package/src/cli-agent.ts +108 -8
  230. package/src/cli-serve.ts +140 -0
  231. package/src/dashboard/app.js +9 -3
  232. package/src/dashboard/components/knowledge-link.js +15 -0
  233. package/src/dashboard/components/settings-help.js +4 -2
  234. package/src/dashboard/docs/agent-deployment.html +33 -1
  235. package/src/dashboard/docs/settings-network.html +321 -0
  236. package/src/dashboard/docs/settings-security.html +347 -0
  237. package/src/dashboard/docs/settings-tool-security.html +176 -0
  238. package/src/dashboard/docs/settings.html +36 -16
  239. package/src/dashboard/pages/agent-detail/deployment.js +39 -6
  240. package/src/dashboard/pages/agent-detail/tools.js +10 -0
  241. package/src/dashboard/pages/database-access.js +4 -3
  242. package/src/dashboard/pages/settings.js +174 -37
  243. package/src/dashboard/pages/task-pipeline.js +400 -843
  244. package/src/database-access/agent-tools.ts +78 -63
  245. package/src/database-access/connection-manager.ts +13 -2
  246. package/src/database-access/routes.ts +13 -1
  247. package/src/db/adapter.ts +1 -0
  248. package/src/engine/agent-memory.ts +2 -1
  249. package/src/engine/agent-notify.ts +50 -0
  250. package/src/engine/agent-routes.ts +257 -4
  251. package/src/engine/db-adapter.ts +16 -0
  252. package/src/engine/lifecycle.ts +4 -0
  253. package/src/engine/routes.ts +4 -3
  254. package/src/engine/screen-unlock.ts +136 -0
  255. package/src/engine/skills/database-access.ts +78 -0
  256. package/src/engine/skills/index.ts +3 -2
  257. package/src/engine/skills.ts +2 -0
  258. package/src/engine/task-queue-routes.ts +18 -0
  259. package/src/engine/task-queue.ts +15 -2
  260. package/src/middleware/transport-encryption.ts +1 -4
  261. package/src/runtime/agent-loop.ts +4 -0
  262. package/src/runtime/index.ts +15 -6
  263. package/src/server.ts +14 -1
  264. package/src/system-prompts/google/index.ts +1 -2
  265. package/src/system-prompts/index.ts +1 -1
  266. package/src/system-prompts/microsoft/contacts.ts +34 -0
  267. package/src/system-prompts/microsoft/excel.ts +52 -0
  268. package/src/system-prompts/microsoft/index.ts +31 -0
  269. package/src/system-prompts/microsoft/onedrive.ts +41 -0
  270. package/src/system-prompts/microsoft/onenote.ts +36 -0
  271. package/src/system-prompts/microsoft/outlook-calendar.ts +37 -0
  272. package/src/system-prompts/microsoft/outlook-mail.ts +46 -0
  273. package/src/system-prompts/microsoft/planner.ts +37 -0
  274. package/src/system-prompts/microsoft/powerbi.ts +38 -0
  275. package/src/system-prompts/microsoft/powerpoint.ts +35 -0
  276. package/src/system-prompts/microsoft/sharepoint.ts +44 -0
  277. package/src/system-prompts/microsoft/teams.ts +49 -0
  278. package/src/system-prompts/microsoft/todo.ts +37 -0
  279. package/src/types/hono-env.ts +4 -0
  280. package/.github/CODEOWNERS +0 -23
  281. package/.github/workflows/publish-community-skills.yml +0 -121
  282. package/.github/workflows/validate-community-skills.yml +0 -172
  283. package/agriculture_southwest_nigeria_research.txt +0 -10
  284. package/boa_credit_cards_research.txt +0 -10
  285. package/customer_support_research_feb2026.txt +0 -10
  286. package/dist/agent-tools-LRA7PPXG.js +0 -13922
  287. package/dist/agent-tools-VAU5DOQB.js +0 -13910
  288. package/dist/agent-tools-VWV7OWXU.js +0 -13922
  289. package/dist/chunk-2Z7MWTCX.js +0 -4977
  290. package/dist/chunk-3T4XU3VV.js +0 -5010
  291. package/dist/chunk-445QM4NX.js +0 -5061
  292. package/dist/chunk-5TW3Y7DJ.js +0 -1519
  293. package/dist/chunk-6I7VY3LT.js +0 -5060
  294. package/dist/chunk-6W5EK3UP.js +0 -4977
  295. package/dist/chunk-AQMSHJQT.js +0 -5069
  296. package/dist/chunk-ASSQW7HX.js +0 -5051
  297. package/dist/chunk-CIN27FGC.js +0 -5037
  298. package/dist/chunk-CMXY3NUB.js +0 -4977
  299. package/dist/chunk-DRLMRUDP.js +0 -5052
  300. package/dist/chunk-EHI7Z446.js +0 -1519
  301. package/dist/chunk-FEAILFAQ.js +0 -1519
  302. package/dist/chunk-GA3PYBZL.js +0 -1519
  303. package/dist/chunk-GWX63G5J.js +0 -1519
  304. package/dist/chunk-HHMZ4UY6.js +0 -1519
  305. package/dist/chunk-HVQMNF7E.js +0 -4921
  306. package/dist/chunk-HXM7F3YN.js +0 -1519
  307. package/dist/chunk-K6NGOUXG.js +0 -5060
  308. package/dist/chunk-KPG5WINJ.js +0 -4977
  309. package/dist/chunk-LBCUBYDL.js +0 -1519
  310. package/dist/chunk-LIRQSWLR.js +0 -5014
  311. package/dist/chunk-LRCKO5KE.js +0 -1519
  312. package/dist/chunk-M7XL3DJD.js +0 -5069
  313. package/dist/chunk-MHJULEIQ.js +0 -1519
  314. package/dist/chunk-MJGGW6MC.js +0 -106
  315. package/dist/chunk-MMYBDHDB.js +0 -4921
  316. package/dist/chunk-MQT5FXKD.js +0 -1519
  317. package/dist/chunk-OIMPEQF5.js +0 -4977
  318. package/dist/chunk-OOU7JUYE.js +0 -542
  319. package/dist/chunk-OW4GLBHP.js +0 -1519
  320. package/dist/chunk-Q4K4MMLU.js +0 -4977
  321. package/dist/chunk-RUK4CRPF.js +0 -1519
  322. package/dist/chunk-T7H65XQY.js +0 -1519
  323. package/dist/chunk-TQVFWG57.js +0 -5064
  324. package/dist/chunk-UEPK3IMC.js +0 -1519
  325. package/dist/chunk-VUWTXJH6.js +0 -1519
  326. package/dist/chunk-WCPGGSAD.js +0 -1519
  327. package/dist/chunk-WO63NZOJ.js +0 -1519
  328. package/dist/chunk-YPJDRVUM.js +0 -5064
  329. package/dist/chunk-ZROMH5DL.js +0 -4921
  330. package/src/dashboard/docs/_template.txt +0 -92
@@ -0,0 +1,1380 @@
1
+ import {
2
+ __esm,
3
+ __require
4
+ } from "./chunk-KFQGP6VL.js";
5
+
6
+ // src/database-access/query-sanitizer.ts
7
+ function classifyQuery(sql) {
8
+ const trimmed = sql.trim().toUpperCase();
9
+ for (const pattern of BLOCKED_PATTERNS) {
10
+ if (pattern.test(sql)) return "blocked";
11
+ }
12
+ for (const pattern of EXECUTE_PATTERNS) {
13
+ if (pattern.test(sql)) return "execute";
14
+ }
15
+ if (trimmed.startsWith("SELECT") || trimmed.startsWith("WITH") || trimmed.startsWith("EXPLAIN")) return "read";
16
+ if (trimmed.startsWith("INSERT") || trimmed.startsWith("UPDATE") || trimmed.startsWith("UPSERT") || trimmed.startsWith("MERGE")) return "write";
17
+ if (trimmed.startsWith("DELETE")) return "delete";
18
+ if (trimmed.startsWith("CREATE") || trimmed.startsWith("ALTER") || trimmed.startsWith("DROP")) return "schema";
19
+ return "blocked";
20
+ }
21
+ function sanitizeQuery(sql, permissions, connectionConfig, agentAccess) {
22
+ const operation = classifyQuery(sql);
23
+ if (operation === "blocked") {
24
+ return { allowed: false, operation, reason: "Query contains blocked patterns (potential injection or dangerous operations)" };
25
+ }
26
+ if (!permissions.includes(operation)) {
27
+ return { allowed: false, operation, reason: `Agent lacks '${operation}' permission on this database` };
28
+ }
29
+ const tables = extractTableNames(sql);
30
+ const mergedBlocked = mergeBlockedTables(connectionConfig, agentAccess);
31
+ const mergedAllowed = mergeAllowedTables(connectionConfig, agentAccess);
32
+ for (const table of tables) {
33
+ if (mergedBlocked.has(table.toLowerCase())) {
34
+ return { allowed: false, operation, reason: `Access to table '${table}' is blocked` };
35
+ }
36
+ if (mergedAllowed.size > 0 && !mergedAllowed.has(table.toLowerCase())) {
37
+ return { allowed: false, operation, reason: `Table '${table}' is not in the allowed tables list` };
38
+ }
39
+ }
40
+ const columns = extractColumnNames(sql);
41
+ const blockedColumns = /* @__PURE__ */ new Set([
42
+ ...(connectionConfig.schemaAccess?.blockedColumns || []).map((c) => c.toLowerCase()),
43
+ ...(agentAccess.schemaAccess?.blockedColumns || []).map((c) => c.toLowerCase())
44
+ ]);
45
+ for (const col of columns) {
46
+ if (blockedColumns.has(col.toLowerCase())) {
47
+ return { allowed: false, operation, reason: `Access to column '${col}' is blocked (sensitive data)` };
48
+ }
49
+ }
50
+ let sanitizedQuery = sql.trim();
51
+ if (operation === "read" && !sanitizedQuery.toUpperCase().includes("LIMIT")) {
52
+ const maxRows = agentAccess.queryLimits?.maxRowsRead ?? connectionConfig.queryLimits?.maxRowsRead ?? 1e4;
53
+ sanitizedQuery = `${sanitizedQuery.replace(/;?\s*$/, "")} LIMIT ${maxRows}`;
54
+ }
55
+ return { allowed: true, operation, sanitizedQuery };
56
+ }
57
+ function extractTableNames(sql) {
58
+ const tables = /* @__PURE__ */ new Set();
59
+ const fromMatch = sql.match(/\bFROM\s+([`"[\]]?\w+[`"\]]?(?:\s*\.\s*[`"[\]]?\w+[`"\]]?)?)/gi);
60
+ if (fromMatch) {
61
+ for (const m of fromMatch) {
62
+ const name = m.replace(/^FROM\s+/i, "").replace(/[`"[\]]/g, "").trim();
63
+ tables.add(name.split(".").pop() || name);
64
+ }
65
+ }
66
+ const joinMatch = sql.match(/\bJOIN\s+([`"[\]]?\w+[`"\]]?(?:\s*\.\s*[`"[\]]?\w+[`"\]]?)?)/gi);
67
+ if (joinMatch) {
68
+ for (const m of joinMatch) {
69
+ const name = m.replace(/^JOIN\s+/i, "").replace(/[`"[\]]/g, "").trim();
70
+ tables.add(name.split(".").pop() || name);
71
+ }
72
+ }
73
+ const insertMatch = sql.match(/\bINSERT\s+INTO\s+([`"[\]]?\w+[`"\]]?)/i);
74
+ if (insertMatch) tables.add(insertMatch[1].replace(/[`"[\]]/g, ""));
75
+ const updateMatch = sql.match(/\bUPDATE\s+([`"[\]]?\w+[`"\]]?)/i);
76
+ if (updateMatch) tables.add(updateMatch[1].replace(/[`"[\]]/g, ""));
77
+ const deleteMatch = sql.match(/\bDELETE\s+FROM\s+([`"[\]]?\w+[`"\]]?)/i);
78
+ if (deleteMatch) tables.add(deleteMatch[1].replace(/[`"[\]]/g, ""));
79
+ return [...tables];
80
+ }
81
+ function extractColumnNames(sql) {
82
+ const columns = /* @__PURE__ */ new Set();
83
+ const selectMatch = sql.match(/\bSELECT\s+(.*?)\bFROM\b/is);
84
+ if (selectMatch && !selectMatch[1].includes("*")) {
85
+ const parts = selectMatch[1].split(",");
86
+ for (const p of parts) {
87
+ const col = p.trim().split(/\s+AS\s+/i)[0].trim().split(".").pop();
88
+ if (col && /^\w+$/.test(col)) columns.add(col);
89
+ }
90
+ }
91
+ return [...columns];
92
+ }
93
+ function mergeBlockedTables(config, access) {
94
+ return /* @__PURE__ */ new Set([
95
+ ...(config.schemaAccess?.blockedTables || []).map((t) => t.toLowerCase()),
96
+ ...(access.schemaAccess?.blockedTables || []).map((t) => t.toLowerCase())
97
+ ]);
98
+ }
99
+ function mergeAllowedTables(config, access) {
100
+ const connAllowed = config.schemaAccess?.allowedTables || [];
101
+ const agentAllowed = access.schemaAccess?.allowedTables || [];
102
+ if (agentAllowed.length > 0) return new Set(agentAllowed.map((t) => t.toLowerCase()));
103
+ if (connAllowed.length > 0) return new Set(connAllowed.map((t) => t.toLowerCase()));
104
+ return /* @__PURE__ */ new Set();
105
+ }
106
+ function sanitizeForLogging(sql) {
107
+ let sanitized = sql.replace(/'[^']*'/g, "'?'");
108
+ sanitized = sanitized.replace(/=\s*\d+/g, "= ?");
109
+ return sanitized;
110
+ }
111
+ var BLOCKED_PATTERNS, EXECUTE_PATTERNS;
112
+ var init_query_sanitizer = __esm({
113
+ "src/database-access/query-sanitizer.ts"() {
114
+ "use strict";
115
+ BLOCKED_PATTERNS = [
116
+ /;\s*DROP\s+/i,
117
+ /;\s*TRUNCATE\s+/i,
118
+ /;\s*ALTER\s+/i,
119
+ /;\s*CREATE\s+/i,
120
+ /;\s*GRANT\s+/i,
121
+ /;\s*REVOKE\s+/i,
122
+ /;\s*SET\s+ROLE/i,
123
+ /LOAD_FILE\s*\(/i,
124
+ /INTO\s+OUTFILE/i,
125
+ /INTO\s+DUMPFILE/i,
126
+ /INFORMATION_SCHEMA/i,
127
+ /pg_catalog\./i,
128
+ /sys\.\w+/i,
129
+ /xp_cmdshell/i,
130
+ /sp_execute/i,
131
+ /EXEC\s*\(/i,
132
+ /EXECUTE\s+IMMEDIATE/i,
133
+ /--\s*$/m,
134
+ // SQL comments at end of line (common injection)
135
+ /\/\*[\s\S]*?\*\//,
136
+ // Block comments (hiding malicious code)
137
+ /SLEEP\s*\(/i,
138
+ /BENCHMARK\s*\(/i,
139
+ /WAITFOR\s+DELAY/i,
140
+ /pg_sleep/i
141
+ ];
142
+ EXECUTE_PATTERNS = [
143
+ /\bCALL\s+/i,
144
+ /\bEXEC\s+/i,
145
+ /\bEXECUTE\s+/i
146
+ ];
147
+ }
148
+ });
149
+
150
+ // src/database-access/connection-manager.ts
151
+ import crypto from "crypto";
152
+ var RateLimiter, DatabaseConnectionManager;
153
+ var init_connection_manager = __esm({
154
+ "src/database-access/connection-manager.ts"() {
155
+ init_query_sanitizer();
156
+ RateLimiter = class {
157
+ windows = /* @__PURE__ */ new Map();
158
+ check(key, maxPerMinute) {
159
+ const now = Date.now();
160
+ const window = this.windows.get(key);
161
+ if (!window || window.resetAt <= now) {
162
+ this.windows.set(key, { count: 1, resetAt: now + 6e4 });
163
+ return true;
164
+ }
165
+ if (window.count >= maxPerMinute) return false;
166
+ window.count++;
167
+ return true;
168
+ }
169
+ };
170
+ DatabaseConnectionManager = class {
171
+ pools = /* @__PURE__ */ new Map();
172
+ configs = /* @__PURE__ */ new Map();
173
+ agentAccess = /* @__PURE__ */ new Map();
174
+ // agentId → accesses
175
+ drivers = /* @__PURE__ */ new Map();
176
+ rateLimiter = new RateLimiter();
177
+ stats = /* @__PURE__ */ new Map();
178
+ engineDb;
179
+ vault;
180
+ constructor(deps) {
181
+ this.engineDb = deps?.engineDb;
182
+ this.vault = deps?.vault;
183
+ this.registerBuiltinDrivers();
184
+ }
185
+ // ─── Initialization ──────────────────────────────────────────────────────
186
+ async setDb(db) {
187
+ this.engineDb = db;
188
+ await this.init();
189
+ }
190
+ /** Normalize DB execute — adapters may have run() or execute() */
191
+ async dbRun(sql, params) {
192
+ if (!this.engineDb) throw new Error("No database");
193
+ const fn = this.engineDb.run || this.engineDb.execute;
194
+ if (!fn) throw new Error("DB adapter has no run/execute method");
195
+ return fn.call(this.engineDb, sql, params);
196
+ }
197
+ async dbAll(sql, params) {
198
+ if (!this.engineDb) return [];
199
+ if (this.engineDb.all) return this.engineDb.all(sql, params);
200
+ if (this.engineDb.query) {
201
+ const result2 = await this.engineDb.query(sql, params);
202
+ return Array.isArray(result2) ? result2 : result2?.rows || [];
203
+ }
204
+ const result = await this.dbRun(sql, params);
205
+ return Array.isArray(result) ? result : result?.rows || [];
206
+ }
207
+ async dbGet(sql, params) {
208
+ if (!this.engineDb) return null;
209
+ if (this.engineDb.get) return this.engineDb.get(sql, params);
210
+ const rows = await this.dbAll(sql, params);
211
+ return rows?.[0] || null;
212
+ }
213
+ async init() {
214
+ try {
215
+ await this.ensureTable();
216
+ await this.loadConnections();
217
+ await this.loadAgentAccess();
218
+ console.log(`[db-access] Initialized: ${this.configs.size} connections, ${this.agentAccess.size} agent mappings`);
219
+ } catch (err) {
220
+ console.error(`[db-access] Init failed: ${err.message}`);
221
+ }
222
+ }
223
+ async ensureTable() {
224
+ if (!this.engineDb) return;
225
+ let isPostgres = false;
226
+ try {
227
+ await this.dbRun(`SELECT NOW()`);
228
+ isPostgres = true;
229
+ } catch {
230
+ isPostgres = false;
231
+ }
232
+ const now = isPostgres ? "NOW()" : "(datetime('now'))";
233
+ const jsonType = isPostgres ? "JSONB" : "TEXT";
234
+ const boolType = isPostgres ? "BOOLEAN" : "INTEGER";
235
+ const boolFalse = isPostgres ? "FALSE" : "0";
236
+ const boolTrue = isPostgres ? "TRUE" : "1";
237
+ try {
238
+ await this.dbRun(`
239
+ CREATE TABLE IF NOT EXISTS database_connections (
240
+ id TEXT PRIMARY KEY,
241
+ org_id TEXT NOT NULL,
242
+ name TEXT NOT NULL,
243
+ type TEXT NOT NULL,
244
+ config ${jsonType} NOT NULL DEFAULT '{}',
245
+ status TEXT NOT NULL DEFAULT 'inactive',
246
+ last_tested_at TEXT,
247
+ last_error TEXT,
248
+ created_at TEXT NOT NULL DEFAULT ${now},
249
+ updated_at TEXT NOT NULL DEFAULT ${now}
250
+ )
251
+ `);
252
+ await this.dbRun(`
253
+ CREATE TABLE IF NOT EXISTS agent_database_access (
254
+ id TEXT PRIMARY KEY,
255
+ org_id TEXT NOT NULL,
256
+ agent_id TEXT NOT NULL,
257
+ connection_id TEXT NOT NULL REFERENCES database_connections(id) ON DELETE CASCADE,
258
+ permissions TEXT NOT NULL DEFAULT '["read"]',
259
+ query_limits ${jsonType},
260
+ schema_access ${jsonType},
261
+ log_all_queries ${boolType} NOT NULL DEFAULT ${boolFalse},
262
+ require_approval ${boolType} NOT NULL DEFAULT ${boolFalse},
263
+ enabled ${boolType} NOT NULL DEFAULT ${boolTrue},
264
+ created_at TEXT NOT NULL DEFAULT ${now},
265
+ updated_at TEXT NOT NULL DEFAULT ${now},
266
+ UNIQUE(agent_id, connection_id)
267
+ )
268
+ `);
269
+ await this.dbRun(`
270
+ CREATE TABLE IF NOT EXISTS database_audit_log (
271
+ id TEXT PRIMARY KEY,
272
+ org_id TEXT NOT NULL,
273
+ agent_id TEXT NOT NULL,
274
+ agent_name TEXT,
275
+ connection_id TEXT NOT NULL,
276
+ connection_name TEXT,
277
+ operation TEXT NOT NULL,
278
+ query TEXT NOT NULL,
279
+ param_count INTEGER NOT NULL DEFAULT 0,
280
+ rows_affected INTEGER NOT NULL DEFAULT 0,
281
+ execution_time_ms INTEGER NOT NULL DEFAULT 0,
282
+ success ${boolType} NOT NULL DEFAULT ${boolTrue},
283
+ error TEXT,
284
+ timestamp TEXT NOT NULL DEFAULT ${now}
285
+ )
286
+ `);
287
+ await this.dbRun(`CREATE INDEX IF NOT EXISTS idx_db_audit_agent ON database_audit_log(agent_id, timestamp)`).catch(() => {
288
+ });
289
+ await this.dbRun(`CREATE INDEX IF NOT EXISTS idx_db_audit_conn ON database_audit_log(connection_id, timestamp)`).catch(() => {
290
+ });
291
+ } catch (err) {
292
+ console.error(`[db-access] Table creation failed:`, err.message);
293
+ }
294
+ }
295
+ async loadConnections() {
296
+ if (!this.engineDb) {
297
+ console.warn("[db-access] No engineDb, skipping loadConnections");
298
+ return;
299
+ }
300
+ try {
301
+ const rows = await this.dbAll("SELECT * FROM database_connections");
302
+ for (const row of rows || []) {
303
+ const config = this.rowToConfig(row);
304
+ this.configs.set(config.id, config);
305
+ }
306
+ } catch (err) {
307
+ console.warn(`[db-access] Failed to load connections: ${err.message}`);
308
+ }
309
+ }
310
+ async loadAgentAccess() {
311
+ if (!this.engineDb) return;
312
+ try {
313
+ const rows = await this.dbAll("SELECT * FROM agent_database_access WHERE enabled IS NOT FALSE");
314
+ for (const row of rows || []) {
315
+ const access = this.rowToAccess(row);
316
+ const list = this.agentAccess.get(access.agentId) || [];
317
+ list.push(access);
318
+ this.agentAccess.set(access.agentId, list);
319
+ }
320
+ } catch (err) {
321
+ console.warn(`[db-access] Failed to load agent access: ${err.message}`);
322
+ }
323
+ }
324
+ // ─── CRUD: Connections ─────────────────────────────────────────────────────
325
+ async createConnection(config, credentials) {
326
+ const id = crypto.randomUUID();
327
+ const now = (/* @__PURE__ */ new Date()).toISOString();
328
+ const full = { ...config, id, createdAt: now, updatedAt: now };
329
+ if (credentials?.password && this.vault) {
330
+ await this.vault.storeSecret(config.orgId, `db:${id}:password`, "database_credential", credentials.password);
331
+ }
332
+ if (credentials?.connectionString && this.vault) {
333
+ await this.vault.storeSecret(config.orgId, `db:${id}:connection_string`, "database_credential", credentials.connectionString);
334
+ }
335
+ if (this.engineDb) {
336
+ await this.dbRun(
337
+ `INSERT INTO database_connections (id, org_id, name, type, config, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
338
+ [id, full.orgId, full.name, full.type, JSON.stringify(this.configToStorable(full)), full.status, now, now]
339
+ );
340
+ }
341
+ this.configs.set(id, full);
342
+ console.log(`[db-access] Connection created: ${full.name} (${full.type})`);
343
+ return full;
344
+ }
345
+ async updateConnection(id, updates, credentials) {
346
+ const existing = this.configs.get(id);
347
+ if (!existing) return null;
348
+ const updated = { ...existing, ...updates, id, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
349
+ if (this.vault) {
350
+ if (credentials?.password) {
351
+ const old = this.vault.findByName(`db:${id}:password`);
352
+ if (old) try {
353
+ await this.vault.deleteSecret(old.id);
354
+ } catch {
355
+ }
356
+ await this.vault.storeSecret(existing.orgId, `db:${id}:password`, "database_credential", credentials.password);
357
+ }
358
+ if (credentials?.connectionString) {
359
+ const old = this.vault.findByName(`db:${id}:connection_string`);
360
+ if (old) try {
361
+ await this.vault.deleteSecret(old.id);
362
+ } catch {
363
+ }
364
+ await this.vault.storeSecret(existing.orgId, `db:${id}:connection_string`, "database_credential", credentials.connectionString);
365
+ }
366
+ }
367
+ if (this.engineDb) {
368
+ await this.dbRun(
369
+ `UPDATE database_connections SET name = ?, type = ?, config = ?, status = ?, updated_at = ? WHERE id = ?`,
370
+ [updated.name, updated.type, JSON.stringify(this.configToStorable(updated)), updated.status, updated.updatedAt, id]
371
+ );
372
+ }
373
+ this.configs.set(id, updated);
374
+ await this.closePool(id);
375
+ return updated;
376
+ }
377
+ async deleteConnection(id) {
378
+ const config = this.configs.get(id);
379
+ if (!config) return false;
380
+ await this.closePool(id);
381
+ if (this.vault) {
382
+ try {
383
+ for (const suffix of ["password", "connection_string", "ssh_key"]) {
384
+ const entry = this.vault.findByName(`db:${id}:${suffix}`);
385
+ if (entry) await this.vault.deleteSecret(entry.id);
386
+ }
387
+ } catch {
388
+ }
389
+ }
390
+ if (this.engineDb) {
391
+ await this.dbRun("DELETE FROM agent_database_access WHERE connection_id = ?", [id]);
392
+ await this.dbRun("DELETE FROM database_connections WHERE id = ?", [id]);
393
+ }
394
+ this.configs.delete(id);
395
+ for (const [agentId, list] of this.agentAccess) {
396
+ const filtered = list.filter((a) => a.connectionId !== id);
397
+ if (filtered.length === 0) this.agentAccess.delete(agentId);
398
+ else this.agentAccess.set(agentId, filtered);
399
+ }
400
+ console.log(`[db-access] Connection deleted: ${config.name}`);
401
+ return true;
402
+ }
403
+ getConnection(id) {
404
+ return this.configs.get(id);
405
+ }
406
+ /** Get a human-readable summary of an agent's database connections for system prompts */
407
+ getAgentConnectionSummary(agentId) {
408
+ const accesses = this.getAgentAccess(agentId).filter((a) => a.enabled);
409
+ return accesses.map((a) => {
410
+ const config = this.configs.get(a.connectionId);
411
+ if (!config) return "";
412
+ const perms = a.permissions?.join(", ") || "read";
413
+ return `"${config.name}" (${config.type}) \u2014 permissions: ${perms}`;
414
+ }).filter(Boolean);
415
+ }
416
+ listConnections(orgId) {
417
+ return [...this.configs.values()].filter((c) => c.orgId === orgId);
418
+ }
419
+ // ─── CRUD: Agent Access ────────────────────────────────────────────────────
420
+ async grantAccess(access) {
421
+ const id = crypto.randomUUID();
422
+ const now = (/* @__PURE__ */ new Date()).toISOString();
423
+ const full = { ...access, id, createdAt: now, updatedAt: now };
424
+ if (this.engineDb) {
425
+ await this.dbRun(
426
+ `INSERT INTO agent_database_access (id, org_id, agent_id, connection_id, permissions, query_limits, schema_access, log_all_queries, require_approval, enabled, created_at, updated_at)
427
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
428
+ [
429
+ id,
430
+ full.orgId,
431
+ full.agentId,
432
+ full.connectionId,
433
+ JSON.stringify(full.permissions),
434
+ JSON.stringify(full.queryLimits || null),
435
+ JSON.stringify(full.schemaAccess || null),
436
+ full.logAllQueries,
437
+ full.requireApproval,
438
+ full.enabled,
439
+ now,
440
+ now
441
+ ]
442
+ );
443
+ }
444
+ const list = this.agentAccess.get(full.agentId) || [];
445
+ list.push(full);
446
+ this.agentAccess.set(full.agentId, list);
447
+ return full;
448
+ }
449
+ async revokeAccess(agentId, connectionId) {
450
+ if (this.engineDb) {
451
+ await this.dbRun(
452
+ "DELETE FROM agent_database_access WHERE agent_id = ? AND connection_id = ?",
453
+ [agentId, connectionId]
454
+ );
455
+ }
456
+ const list = this.agentAccess.get(agentId) || [];
457
+ const filtered = list.filter((a) => a.connectionId !== connectionId);
458
+ if (filtered.length === 0) this.agentAccess.delete(agentId);
459
+ else this.agentAccess.set(agentId, filtered);
460
+ return true;
461
+ }
462
+ async updateAccess(agentId, connectionId, updates) {
463
+ const list = this.agentAccess.get(agentId) || [];
464
+ const idx = list.findIndex((a) => a.connectionId === connectionId);
465
+ if (idx === -1) return null;
466
+ const updated = { ...list[idx], ...updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
467
+ list[idx] = updated;
468
+ this.agentAccess.set(agentId, list);
469
+ if (this.engineDb) {
470
+ await this.dbRun(
471
+ `UPDATE agent_database_access SET permissions = ?, query_limits = ?, schema_access = ?, log_all_queries = ?, require_approval = ?, enabled = ?, updated_at = ? WHERE agent_id = ? AND connection_id = ?`,
472
+ [
473
+ JSON.stringify(updated.permissions),
474
+ JSON.stringify(updated.queryLimits || null),
475
+ JSON.stringify(updated.schemaAccess || null),
476
+ updated.logAllQueries,
477
+ updated.requireApproval,
478
+ updated.enabled,
479
+ updated.updatedAt,
480
+ agentId,
481
+ connectionId
482
+ ]
483
+ );
484
+ }
485
+ return updated;
486
+ }
487
+ getAgentAccess(agentId) {
488
+ return this.agentAccess.get(agentId) || [];
489
+ }
490
+ getConnectionAgents(connectionId) {
491
+ const result = [];
492
+ for (const list of this.agentAccess.values()) {
493
+ for (const a of list) {
494
+ if (a.connectionId === connectionId) result.push(a);
495
+ }
496
+ }
497
+ return result;
498
+ }
499
+ // ─── Query Execution ──────────────────────────────────────────────────────
500
+ async executeQuery(query) {
501
+ const startMs = Date.now();
502
+ const queryId = crypto.randomUUID();
503
+ const access = this.getAgentAccess(query.agentId).find((a) => a.connectionId === query.connectionId && a.enabled);
504
+ if (!access) {
505
+ return { success: false, error: "Agent does not have access to this database", executionTimeMs: 0, queryId };
506
+ }
507
+ const config = this.configs.get(query.connectionId);
508
+ if (!config || config.status !== "active") {
509
+ return { success: false, error: "Database connection is not active", executionTimeMs: 0, queryId };
510
+ }
511
+ const rateLimit = access.queryLimits?.maxQueriesPerMinute ?? 60;
512
+ const rateKey = `${query.agentId}:${query.connectionId}`;
513
+ if (!this.rateLimiter.check(rateKey, rateLimit)) {
514
+ return { success: false, error: `Rate limit exceeded (${rateLimit}/min)`, executionTimeMs: 0, queryId };
515
+ }
516
+ if (!query.sql) {
517
+ return { success: false, error: "No SQL query provided", executionTimeMs: 0, queryId };
518
+ }
519
+ const sanitizeResult = sanitizeQuery(query.sql, access.permissions, config, access);
520
+ if (!sanitizeResult.allowed) {
521
+ await this.logAudit(query, config, access, "read", 0, false, sanitizeResult.reason || "Query blocked", startMs, queryId);
522
+ return { success: false, error: sanitizeResult.reason, executionTimeMs: Date.now() - startMs, queryId };
523
+ }
524
+ const finalSql = sanitizeResult.sanitizedQuery || query.sql;
525
+ const maxConcurrent = access.queryLimits?.maxConcurrentQueries ?? config.queryLimits?.maxConcurrentQueries ?? 5;
526
+ const timeout = access.queryLimits?.queryTimeoutMs ?? config.queryLimits?.queryTimeoutMs ?? 3e4;
527
+ try {
528
+ const conn = await this.getPooledConnection(query.connectionId);
529
+ const result = await Promise.race([
530
+ conn.query(finalSql, query.params),
531
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Query timeout")), timeout))
532
+ ]);
533
+ const execMs = Date.now() - startMs;
534
+ const rows = result.rows || [];
535
+ const affectedRows = result.affectedRows ?? rows.length;
536
+ const maxRows = sanitizeResult.operation === "read" ? access.queryLimits?.maxRowsRead ?? config.queryLimits?.maxRowsRead ?? 1e4 : sanitizeResult.operation === "write" ? access.queryLimits?.maxRowsWrite ?? config.queryLimits?.maxRowsWrite ?? 1e3 : access.queryLimits?.maxRowsDelete ?? config.queryLimits?.maxRowsDelete ?? 100;
537
+ const truncated = rows.length > maxRows;
538
+ const limitedRows = truncated ? rows.slice(0, maxRows) : rows;
539
+ this.updateStats(query.connectionId, execMs, false);
540
+ const shouldLog = access.logAllQueries || sanitizeResult.operation !== "read";
541
+ if (shouldLog) {
542
+ await this.logAudit(query, config, access, sanitizeResult.operation, affectedRows, true, void 0, startMs, queryId);
543
+ }
544
+ return {
545
+ success: true,
546
+ rows: limitedRows,
547
+ rowCount: limitedRows.length,
548
+ affectedRows,
549
+ fields: result.fields,
550
+ executionTimeMs: execMs,
551
+ truncated,
552
+ queryId
553
+ };
554
+ } catch (err) {
555
+ const execMs = Date.now() - startMs;
556
+ this.updateStats(query.connectionId, execMs, true);
557
+ await this.logAudit(query, config, access, sanitizeResult.operation, 0, false, err.message, startMs, queryId);
558
+ return { success: false, error: err.message, executionTimeMs: execMs, queryId };
559
+ }
560
+ }
561
+ // ─── Connection Testing ────────────────────────────────────────────────────
562
+ async testConnection(connectionId) {
563
+ const config = this.configs.get(connectionId);
564
+ if (!config) return { success: false, latencyMs: 0, error: "Connection not found" };
565
+ const startMs = Date.now();
566
+ try {
567
+ const conn = await this.getPooledConnection(connectionId);
568
+ const alive = await conn.ping();
569
+ const latencyMs = Date.now() - startMs;
570
+ await this.updateConnection(connectionId, {
571
+ status: alive ? "active" : "error",
572
+ lastTestedAt: (/* @__PURE__ */ new Date()).toISOString(),
573
+ lastError: alive ? void 0 : "Ping failed"
574
+ });
575
+ return { success: alive, latencyMs, error: alive ? void 0 : "Ping failed \u2014 connection may have been closed or timed out. Try reconnecting." };
576
+ } catch (err) {
577
+ try {
578
+ await this.closePool(connectionId);
579
+ const conn = await this.getPooledConnection(connectionId);
580
+ const alive = await conn.ping();
581
+ const latencyMs = Date.now() - startMs;
582
+ await this.updateConnection(connectionId, { status: alive ? "active" : "error", lastTestedAt: (/* @__PURE__ */ new Date()).toISOString(), lastError: alive ? void 0 : "Ping failed on retry" });
583
+ return { success: alive, latencyMs, error: alive ? void 0 : "Ping failed on retry" };
584
+ } catch (retryErr) {
585
+ }
586
+ const msg = (err instanceof AggregateError ? err.errors?.map?.((x) => x.message).join("; ") || "Multiple connection failures" : err.message || String(err)) || "Unknown connection error";
587
+ await this.updateConnection(connectionId, {
588
+ status: "error",
589
+ lastTestedAt: (/* @__PURE__ */ new Date()).toISOString(),
590
+ lastError: msg
591
+ });
592
+ return { success: false, latencyMs: Date.now() - startMs, error: msg };
593
+ }
594
+ }
595
+ /**
596
+ * Test connection parameters without saving — creates a temporary connection,
597
+ * pings it, and immediately destroys it.
598
+ */
599
+ async testConnectionParams(params) {
600
+ const startMs = Date.now();
601
+ const driver = this.drivers.get(params.type);
602
+ if (!driver) return { success: false, latencyMs: 0, error: `No driver for database type: ${params.type}` };
603
+ let connection;
604
+ try {
605
+ connection = await driver.connect(
606
+ { type: params.type, host: params.host, port: params.port, database: params.database, ssl: params.ssl },
607
+ { password: params.password, connectionString: params.connectionString }
608
+ );
609
+ const alive = await connection.ping();
610
+ const latencyMs = Date.now() - startMs;
611
+ return { success: alive, latencyMs, error: alive ? void 0 : "Ping failed \u2014 database responded but health check did not pass" };
612
+ } catch (err) {
613
+ return { success: false, latencyMs: Date.now() - startMs, error: err.message || String(err) || "Connection failed" };
614
+ } finally {
615
+ try {
616
+ if (connection?.close) await connection.close();
617
+ } catch {
618
+ }
619
+ }
620
+ }
621
+ // ─── Pool Management ───────────────────────────────────────────────────────
622
+ async getPooledConnection(connectionId) {
623
+ const pool = this.pools.get(connectionId) || [];
624
+ const idle = pool.find((p) => !p.inUse);
625
+ if (idle) {
626
+ idle.inUse = true;
627
+ idle.lastUsedAt = Date.now();
628
+ return this.wrapPooledConnection(idle);
629
+ }
630
+ const config = this.configs.get(connectionId);
631
+ if (!config) throw new Error("Connection not found");
632
+ const maxPool = config.pool?.max ?? 10;
633
+ if (pool.length >= maxPool) throw new Error("Connection pool exhausted");
634
+ const driver = this.drivers.get(config.type);
635
+ if (!driver) throw new Error(`No driver for database type: ${config.type}`);
636
+ const credentials = await this.loadCredentials(config);
637
+ const connection = await driver.connect(config, credentials);
638
+ const entry = {
639
+ connection,
640
+ connectionId,
641
+ createdAt: Date.now(),
642
+ lastUsedAt: Date.now(),
643
+ inUse: true,
644
+ queryCount: 0
645
+ };
646
+ pool.push(entry);
647
+ this.pools.set(connectionId, pool);
648
+ return this.wrapPooledConnection(entry);
649
+ }
650
+ wrapPooledConnection(entry) {
651
+ return {
652
+ query: async (sql, params) => {
653
+ entry.queryCount++;
654
+ return entry.connection.query(sql, params);
655
+ },
656
+ close: async () => {
657
+ entry.inUse = false;
658
+ },
659
+ // Return to pool, don't actually close
660
+ ping: () => entry.connection.ping()
661
+ };
662
+ }
663
+ async closePool(connectionId) {
664
+ const pool = this.pools.get(connectionId) || [];
665
+ for (const entry of pool) {
666
+ try {
667
+ await entry.connection.close();
668
+ } catch {
669
+ }
670
+ }
671
+ this.pools.delete(connectionId);
672
+ }
673
+ async loadCredentials(config) {
674
+ const creds = {};
675
+ if (this.vault) {
676
+ try {
677
+ const pw = await this.vault.getSecretByName(config.orgId, `db:${config.id}:password`, "database_credential");
678
+ if (pw) creds.password = pw.plaintext;
679
+ } catch {
680
+ }
681
+ try {
682
+ const cs = await this.vault.getSecretByName(config.orgId, `db:${config.id}:connection_string`, "database_credential");
683
+ if (cs) creds.connectionString = cs.plaintext;
684
+ } catch {
685
+ }
686
+ if (config.sshTunnel?.enabled) {
687
+ try {
688
+ const ssh = await this.vault.getSecretByName(config.orgId, `db:${config.id}:ssh_key`, "database_credential");
689
+ if (ssh) creds.sshPrivateKey = ssh.plaintext;
690
+ } catch {
691
+ }
692
+ }
693
+ }
694
+ return creds;
695
+ }
696
+ // ─── Stats ─────────────────────────────────────────────────────────────────
697
+ getPoolStats(connectionId) {
698
+ const pool = this.pools.get(connectionId) || [];
699
+ const s = this.stats.get(connectionId) || { queries: 0, errors: 0, totalTimeMs: 0, lastActivity: 0 };
700
+ return {
701
+ connectionId,
702
+ totalConnections: pool.length,
703
+ activeConnections: pool.filter((p) => p.inUse).length,
704
+ idleConnections: pool.filter((p) => !p.inUse).length,
705
+ waitingRequests: 0,
706
+ queriesExecuted: s.queries,
707
+ averageQueryTimeMs: s.queries > 0 ? Math.round(s.totalTimeMs / s.queries) : 0,
708
+ errorCount: s.errors,
709
+ lastActivityAt: s.lastActivity ? new Date(s.lastActivity).toISOString() : ""
710
+ };
711
+ }
712
+ updateStats(connectionId, timeMs, isError) {
713
+ const s = this.stats.get(connectionId) || { queries: 0, errors: 0, totalTimeMs: 0, lastActivity: 0 };
714
+ s.queries++;
715
+ s.totalTimeMs += timeMs;
716
+ if (isError) s.errors++;
717
+ s.lastActivity = Date.now();
718
+ this.stats.set(connectionId, s);
719
+ }
720
+ // ─── Audit Logging ─────────────────────────────────────────────────────────
721
+ async logAudit(query, config, access, operation, rowsAffected, success, error, startMs, queryId) {
722
+ if (!this.engineDb) return;
723
+ try {
724
+ await this.dbRun(
725
+ `INSERT INTO database_audit_log (id, org_id, agent_id, connection_id, connection_name, operation, query, param_count, rows_affected, execution_time_ms, success, error, timestamp)
726
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
727
+ [
728
+ queryId,
729
+ config.orgId,
730
+ query.agentId,
731
+ query.connectionId,
732
+ config.name,
733
+ operation,
734
+ sanitizeForLogging(query.sql || ""),
735
+ (query.params || []).length,
736
+ rowsAffected,
737
+ Date.now() - startMs,
738
+ success,
739
+ error || null,
740
+ (/* @__PURE__ */ new Date()).toISOString()
741
+ ]
742
+ );
743
+ } catch (err) {
744
+ console.warn(`[db-access] Audit log failed: ${err.message}`);
745
+ }
746
+ }
747
+ async getAuditLog(opts) {
748
+ if (!this.engineDb) return [];
749
+ const conditions = ["org_id = ?"];
750
+ const params = [opts.orgId];
751
+ if (opts.agentId) {
752
+ conditions.push("agent_id = ?");
753
+ params.push(opts.agentId);
754
+ }
755
+ if (opts.connectionId) {
756
+ conditions.push("connection_id = ?");
757
+ params.push(opts.connectionId);
758
+ }
759
+ params.push(opts.limit ?? 100, opts.offset ?? 0);
760
+ return this.dbAll(
761
+ `SELECT * FROM database_audit_log WHERE ${conditions.join(" AND ")} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
762
+ params
763
+ );
764
+ }
765
+ // ─── Driver Registration ───────────────────────────────────────────────────
766
+ registerDriver(type, driver) {
767
+ this.drivers.set(type, driver);
768
+ }
769
+ // ─── Auto-Install Helper ──────────────────────────────────────────────────
770
+ _installCache = /* @__PURE__ */ new Set();
771
+ /**
772
+ * Auto-install a npm package if not already installed.
773
+ * Caches install attempts to avoid repeated installs in the same process.
774
+ */
775
+ /**
776
+ * Load a package using createRequire (works in bundled builds where dynamic import() fails).
777
+ * Falls back through multiple strategies: createRequire at cwd, createRequire at node_modules, direct import.
778
+ */
779
+ _requirePkg(pkg) {
780
+ try {
781
+ const { createRequire } = __require("module");
782
+ const req = createRequire(__require("path").join(process.cwd(), "node_modules", ".package.json"));
783
+ return req(pkg);
784
+ } catch {
785
+ }
786
+ try {
787
+ const { createRequire } = __require("module");
788
+ const req = createRequire(__require("path").join(process.cwd(), "package.json"));
789
+ return req(pkg);
790
+ } catch {
791
+ }
792
+ try {
793
+ const absPath = __require("path").join(process.cwd(), "node_modules", pkg);
794
+ return __require(absPath);
795
+ } catch {
796
+ }
797
+ try {
798
+ return __require(pkg);
799
+ } catch {
800
+ }
801
+ return null;
802
+ }
803
+ async ensurePackage(pkg) {
804
+ const existing = this._requirePkg(pkg);
805
+ if (existing) return existing;
806
+ try {
807
+ return await import(pkg);
808
+ } catch {
809
+ }
810
+ if (this._installCache.has(pkg)) {
811
+ throw new Error(`Package "${pkg}" could not be loaded after auto-install. Please install manually: npm install ${pkg}`);
812
+ }
813
+ this._installCache.add(pkg);
814
+ console.log(`[database-access] Auto-installing "${pkg}"...`);
815
+ const { execSync } = await import("child_process");
816
+ try {
817
+ execSync(`npm install --no-save ${pkg}`, {
818
+ stdio: "pipe",
819
+ timeout: 12e4,
820
+ cwd: process.cwd()
821
+ });
822
+ console.log(`[database-access] Successfully installed "${pkg}"`);
823
+ } catch (installErr) {
824
+ const msg = installErr.stderr?.toString?.().slice(0, 200) || installErr.message;
825
+ throw new Error(`Failed to auto-install "${pkg}": ${msg}. Please install manually: npm install ${pkg}`);
826
+ }
827
+ const loaded = this._requirePkg(pkg);
828
+ if (loaded) return loaded;
829
+ try {
830
+ return await import(pkg);
831
+ } catch {
832
+ }
833
+ throw new Error(`Package "${pkg}" installed but could not be loaded. Try restarting the server, or install manually: npm install ${pkg}`);
834
+ }
835
+ /**
836
+ * Connect with a timeout wrapper — all drivers get a 15s connect timeout.
837
+ */
838
+ async connectWithTimeout(fn, timeoutMs = 15e3, label = "Connection") {
839
+ return new Promise((resolve, reject) => {
840
+ const timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms \u2014 check your host/port and ensure the database is reachable`)), timeoutMs);
841
+ fn().then(
842
+ (v) => {
843
+ clearTimeout(timer);
844
+ resolve(v);
845
+ },
846
+ (e) => {
847
+ clearTimeout(timer);
848
+ reject(e);
849
+ }
850
+ );
851
+ });
852
+ }
853
+ /**
854
+ * Detect SSL requirement from connection string or cloud provider type.
855
+ * Cloud-hosted databases almost always require SSL.
856
+ */
857
+ needsSsl(config, credentials) {
858
+ if (config.ssl === true) return true;
859
+ if (config.ssl === false) return false;
860
+ const cloudTypes = ["supabase", "neon", "planetscale", "cockroachdb", "turso", "upstash"];
861
+ if (cloudTypes.includes(config.type)) return true;
862
+ const connStr = credentials?.connectionString || "";
863
+ if (connStr.includes("sslmode=require") || connStr.includes("ssl=true")) return true;
864
+ if (/supabase|neon\.tech|cockroachlabs|planetscale|turso\.io|railway\.app|render\.com|aiven\.io|timescale\.com/i.test(connStr)) return true;
865
+ if (/supabase|neon\.tech|cockroachlabs|planetscale|turso\.io|railway|render|aiven|timescale/i.test(config.host || "")) return true;
866
+ return false;
867
+ }
868
+ /**
869
+ * Parse a connection string to extract host/port/database/username when fields are missing.
870
+ */
871
+ parseConnectionString(connStr, type) {
872
+ try {
873
+ const cleaned = connStr.replace(/^(postgres|postgresql|mysql|mongodb\+srv|mongodb|redis|rediss|libsql)/, "http");
874
+ const url = new URL(cleaned);
875
+ return {
876
+ host: url.hostname || void 0,
877
+ port: url.port ? parseInt(url.port) : void 0,
878
+ database: url.pathname?.replace(/^\//, "") || void 0,
879
+ username: url.username || void 0
880
+ };
881
+ } catch {
882
+ return {};
883
+ }
884
+ }
885
+ registerBuiltinDrivers() {
886
+ const self = this;
887
+ const pgDriver = {
888
+ async connect(config, credentials) {
889
+ const pgMod = await self.ensurePackage("postgres");
890
+ const pgFn = pgMod.default || pgMod;
891
+ let connStr = credentials.connectionString;
892
+ if (!connStr) {
893
+ const user = encodeURIComponent(config.username || "postgres");
894
+ const pass = encodeURIComponent(credentials.password || "");
895
+ const host = config.host || "localhost";
896
+ const port = config.port || 5432;
897
+ const db = config.database || "postgres";
898
+ connStr = `postgresql://${user}:${pass}@${host}:${port}/${db}`;
899
+ }
900
+ const ssl = self.needsSsl(config, credentials);
901
+ const sql = await self.connectWithTimeout(() => {
902
+ const s = pgFn(connStr, {
903
+ max: config.pool?.max ?? 10,
904
+ idle_timeout: (config.pool?.idleTimeoutMs ?? 3e4) / 1e3,
905
+ connect_timeout: 10,
906
+ ssl: ssl ? config.sslRejectUnauthorized === false ? "prefer" : "require" : false
907
+ });
908
+ return s`SELECT 1`.then(() => s);
909
+ }, 15e3, "PostgreSQL");
910
+ return {
911
+ async query(q, params) {
912
+ const result = params?.length ? await sql.unsafe(q, params) : await sql.unsafe(q);
913
+ return {
914
+ rows: [...result],
915
+ affectedRows: result.count,
916
+ fields: result.columns?.map((c) => ({ name: c.name, type: String(c.type) }))
917
+ };
918
+ },
919
+ async close() {
920
+ try {
921
+ await sql.end({ timeout: 5 });
922
+ } catch {
923
+ }
924
+ },
925
+ async ping() {
926
+ try {
927
+ await sql`SELECT 1`;
928
+ return true;
929
+ } catch (e) {
930
+ const msg = e instanceof AggregateError ? e.errors?.map?.((x) => x.message).join("; ") || "Multiple connection failures" : e.message || String(e);
931
+ throw new Error("PostgreSQL ping failed: " + msg);
932
+ }
933
+ }
934
+ };
935
+ }
936
+ };
937
+ this.drivers.set("postgresql", pgDriver);
938
+ this.drivers.set("cockroachdb", pgDriver);
939
+ this.drivers.set("supabase", pgDriver);
940
+ this.drivers.set("neon", pgDriver);
941
+ const mysqlDriver = {
942
+ async connect(config, credentials) {
943
+ const mysql2 = await self.ensurePackage("mysql2/promise");
944
+ const parsed = credentials.connectionString ? self.parseConnectionString(credentials.connectionString, config.type) : {};
945
+ const ssl = self.needsSsl(config, credentials);
946
+ const pool = await self.connectWithTimeout(async () => {
947
+ const p = mysql2.createPool({
948
+ host: config.host || parsed.host || "localhost",
949
+ port: config.port || parsed.port || 3306,
950
+ user: config.username || parsed.username,
951
+ password: credentials.password || (credentials.connectionString ? (() => {
952
+ try {
953
+ const u = new URL(credentials.connectionString.replace(/^mysql/, "http"));
954
+ return decodeURIComponent(u.password);
955
+ } catch {
956
+ return void 0;
957
+ }
958
+ })() : void 0),
959
+ database: config.database || parsed.database,
960
+ connectionLimit: config.pool?.max ?? 10,
961
+ connectTimeout: 1e4,
962
+ ssl: ssl ? { rejectUnauthorized: config.sslRejectUnauthorized !== false } : void 0,
963
+ uri: credentials.connectionString || void 0
964
+ });
965
+ await p.execute("SELECT 1");
966
+ return p;
967
+ }, 15e3, "MySQL");
968
+ return {
969
+ async query(q, params) {
970
+ const [rows, fields] = await pool.execute(q, params);
971
+ const resultRows = Array.isArray(rows) ? rows : [];
972
+ return {
973
+ rows: resultRows,
974
+ affectedRows: rows.affectedRows,
975
+ fields: fields?.map((f) => ({ name: f.name, type: String(f.type) }))
976
+ };
977
+ },
978
+ async close() {
979
+ try {
980
+ await pool.end();
981
+ } catch {
982
+ }
983
+ },
984
+ async ping() {
985
+ try {
986
+ await pool.execute("SELECT 1");
987
+ return true;
988
+ } catch (e) {
989
+ throw new Error("MySQL ping failed: " + (e.message || e));
990
+ }
991
+ }
992
+ };
993
+ }
994
+ };
995
+ this.drivers.set("mysql", mysqlDriver);
996
+ this.drivers.set("mariadb", mysqlDriver);
997
+ this.drivers.set("planetscale", mysqlDriver);
998
+ this.drivers.set("sqlite", {
999
+ async connect(config, credentials) {
1000
+ const sqliteMod = await self.ensurePackage("better-sqlite3");
1001
+ const Database = sqliteMod.default || sqliteMod;
1002
+ const dbPath = credentials.connectionString || config.database || ":memory:";
1003
+ const db = new Database(dbPath, { readonly: false });
1004
+ try {
1005
+ db.pragma("journal_mode = WAL");
1006
+ } catch {
1007
+ }
1008
+ return {
1009
+ async query(q, params) {
1010
+ const trimmed = q.trim().toUpperCase();
1011
+ if (trimmed.startsWith("SELECT") || trimmed.startsWith("WITH") || trimmed.startsWith("PRAGMA") || trimmed.startsWith("EXPLAIN")) {
1012
+ const stmt = db.prepare(q);
1013
+ const rows = params?.length ? stmt.all(...params) : stmt.all();
1014
+ return { rows, fields: rows.length > 0 ? Object.keys(rows[0]).map((k) => ({ name: k, type: "unknown" })) : [] };
1015
+ } else {
1016
+ const stmt = db.prepare(q);
1017
+ const result = params?.length ? stmt.run(...params) : stmt.run();
1018
+ return { rows: [], affectedRows: result.changes };
1019
+ }
1020
+ },
1021
+ async close() {
1022
+ try {
1023
+ db.close();
1024
+ } catch {
1025
+ }
1026
+ },
1027
+ async ping() {
1028
+ try {
1029
+ db.prepare("SELECT 1").get();
1030
+ return true;
1031
+ } catch (e) {
1032
+ throw new Error("SQLite ping failed: " + (e.message || e));
1033
+ }
1034
+ }
1035
+ };
1036
+ }
1037
+ });
1038
+ this.drivers.set("turso", {
1039
+ async connect(config, credentials) {
1040
+ const libsql = await self.ensurePackage("@libsql/client");
1041
+ const { createClient } = libsql;
1042
+ const url = credentials.connectionString || `libsql://${config.host}`;
1043
+ const client = createClient({ url, authToken: credentials.password });
1044
+ await self.connectWithTimeout(() => client.execute("SELECT 1"), 15e3, "Turso");
1045
+ return {
1046
+ async query(q, params) {
1047
+ const result = await client.execute({ sql: q, args: params || [] });
1048
+ return {
1049
+ rows: result.rows,
1050
+ affectedRows: result.rowsAffected,
1051
+ fields: result.columns?.map((c) => ({ name: String(c), type: "unknown" }))
1052
+ };
1053
+ },
1054
+ async close() {
1055
+ try {
1056
+ client.close();
1057
+ } catch {
1058
+ }
1059
+ },
1060
+ async ping() {
1061
+ try {
1062
+ await client.execute("SELECT 1");
1063
+ return true;
1064
+ } catch (e) {
1065
+ throw new Error("Turso ping failed: " + (e.message || e));
1066
+ }
1067
+ }
1068
+ };
1069
+ }
1070
+ });
1071
+ this.drivers.set("mongodb", {
1072
+ async connect(config, credentials) {
1073
+ const mongoMod = await self.ensurePackage("mongodb");
1074
+ const { MongoClient } = mongoMod;
1075
+ const uri = credentials.connectionString || (() => {
1076
+ const user = encodeURIComponent(config.username || "");
1077
+ const pass = encodeURIComponent(credentials.password || "");
1078
+ const host = config.host || "localhost";
1079
+ const port = config.port || 27017;
1080
+ const db2 = config.database || "admin";
1081
+ const auth = user ? `${user}:${pass}@` : "";
1082
+ return `mongodb://${auth}${host}:${port}/${db2}`;
1083
+ })();
1084
+ const ssl = self.needsSsl(config, credentials);
1085
+ const useTls = ssl || uri.startsWith("mongodb+srv://");
1086
+ const client = new MongoClient(uri, {
1087
+ maxPoolSize: config.pool?.max ?? 10,
1088
+ serverSelectionTimeoutMS: 1e4,
1089
+ connectTimeoutMS: 1e4,
1090
+ tls: useTls || void 0
1091
+ });
1092
+ await self.connectWithTimeout(() => client.connect(), 15e3, "MongoDB");
1093
+ const db = client.db(config.database || self.parseConnectionString(uri, "mongodb").database);
1094
+ return {
1095
+ async query(q, _params) {
1096
+ try {
1097
+ const cmd = JSON.parse(q);
1098
+ const collection = db.collection(cmd.collection);
1099
+ switch (cmd.operation) {
1100
+ case "find": {
1101
+ const cursor = collection.find(cmd.filter || {});
1102
+ if (cmd.projection) cursor.project(cmd.projection);
1103
+ if (cmd.sort) cursor.sort(cmd.sort);
1104
+ if (cmd.skip) cursor.skip(cmd.skip);
1105
+ cursor.limit(cmd.limit || 100);
1106
+ return { rows: await cursor.toArray() };
1107
+ }
1108
+ case "findOne": {
1109
+ const doc = await collection.findOne(cmd.filter || {}, { projection: cmd.projection });
1110
+ return { rows: doc ? [doc] : [] };
1111
+ }
1112
+ case "insertOne": {
1113
+ const r = await collection.insertOne(cmd.document);
1114
+ return { rows: [{ insertedId: r.insertedId }], affectedRows: 1 };
1115
+ }
1116
+ case "insertMany": {
1117
+ const r = await collection.insertMany(cmd.documents);
1118
+ return { rows: [{ insertedCount: r.insertedCount }], affectedRows: r.insertedCount };
1119
+ }
1120
+ case "updateOne":
1121
+ case "updateMany": {
1122
+ const fn = cmd.operation === "updateOne" ? collection.updateOne.bind(collection) : collection.updateMany.bind(collection);
1123
+ const r = await fn(cmd.filter || {}, cmd.update, { upsert: cmd.upsert });
1124
+ return { rows: [{ matchedCount: r.matchedCount, modifiedCount: r.modifiedCount, upsertedId: r.upsertedId }], affectedRows: r.modifiedCount };
1125
+ }
1126
+ case "deleteOne":
1127
+ case "deleteMany": {
1128
+ const fn = cmd.operation === "deleteOne" ? collection.deleteOne.bind(collection) : collection.deleteMany.bind(collection);
1129
+ const r = await fn(cmd.filter || {});
1130
+ return { rows: [], affectedRows: r.deletedCount };
1131
+ }
1132
+ case "aggregate":
1133
+ return { rows: await collection.aggregate(cmd.pipeline || []).toArray() };
1134
+ case "count":
1135
+ case "countDocuments":
1136
+ return { rows: [{ count: await collection.countDocuments(cmd.filter || {}) }] };
1137
+ case "distinct":
1138
+ return { rows: [{ values: await collection.distinct(cmd.field, cmd.filter || {}) }] };
1139
+ case "listCollections": {
1140
+ const cols = await db.listCollections().toArray();
1141
+ return { rows: cols.map((c) => ({ name: c.name, type: c.type })) };
1142
+ }
1143
+ default:
1144
+ throw new Error(`Unknown operation: ${cmd.operation}. Supported: find, findOne, insertOne, insertMany, updateOne, updateMany, deleteOne, deleteMany, aggregate, count, distinct, listCollections`);
1145
+ }
1146
+ } catch (err) {
1147
+ throw new Error(`MongoDB query error: ${err.message}`);
1148
+ }
1149
+ },
1150
+ async close() {
1151
+ try {
1152
+ await client.close();
1153
+ } catch {
1154
+ }
1155
+ },
1156
+ async ping() {
1157
+ try {
1158
+ await db.command({ ping: 1 });
1159
+ return true;
1160
+ } catch (e) {
1161
+ throw new Error("MongoDB ping failed: " + (e.message || e));
1162
+ }
1163
+ }
1164
+ };
1165
+ }
1166
+ });
1167
+ this.drivers.set("redis", {
1168
+ async connect(config, credentials) {
1169
+ const host = config.host || "localhost";
1170
+ const port = config.port || 6379;
1171
+ const ssl = self.needsSsl(config, credentials);
1172
+ const connStr = credentials.connectionString || "";
1173
+ const useTls = ssl || connStr.startsWith("rediss://");
1174
+ let password = credentials.password;
1175
+ let username = config.username;
1176
+ if (connStr) {
1177
+ try {
1178
+ const parsed = self.parseConnectionString(connStr, "redis");
1179
+ if (!password) {
1180
+ const url = new URL(connStr.replace(/^redis(s?)/, "http"));
1181
+ password = url.password || password;
1182
+ username = url.username || username;
1183
+ }
1184
+ } catch {
1185
+ }
1186
+ }
1187
+ let socket;
1188
+ const connectHost = connStr ? self.parseConnectionString(connStr, "redis").host || host : host;
1189
+ const connectPort = connStr ? self.parseConnectionString(connStr, "redis").port || port : port;
1190
+ await self.connectWithTimeout(async () => {
1191
+ if (useTls) {
1192
+ const tls = await import("tls");
1193
+ socket = tls.connect({ host: connectHost, port: connectPort, rejectUnauthorized: config.sslRejectUnauthorized !== false });
1194
+ await new Promise((resolve, reject) => {
1195
+ socket.on("secureConnect", resolve);
1196
+ socket.on("error", reject);
1197
+ });
1198
+ } else {
1199
+ const net = await import("net");
1200
+ socket = new net.Socket();
1201
+ await new Promise((resolve, reject) => {
1202
+ socket.connect(connectPort, connectHost, () => resolve());
1203
+ socket.on("error", reject);
1204
+ });
1205
+ }
1206
+ if (password) {
1207
+ const authCmd = username && username !== "default" ? `AUTH ${username} ${password}` : `AUTH ${password}`;
1208
+ await sendRedisCommand(socket, authCmd);
1209
+ }
1210
+ }, 15e3, "Redis");
1211
+ function sendRedisCommand(sock, cmd) {
1212
+ return new Promise((resolve, reject) => {
1213
+ let data = "";
1214
+ const onData = (chunk) => {
1215
+ data += chunk.toString();
1216
+ if (data.includes("\r\n")) {
1217
+ sock.off("data", onData);
1218
+ resolve(data);
1219
+ }
1220
+ };
1221
+ sock.on("data", onData);
1222
+ sock.write(cmd + "\r\n");
1223
+ setTimeout(() => {
1224
+ sock.off("data", onData);
1225
+ reject(new Error("Redis command timed out after 10s"));
1226
+ }, 1e4);
1227
+ });
1228
+ }
1229
+ return {
1230
+ async query(q) {
1231
+ const result = await sendRedisCommand(socket, q);
1232
+ return { rows: [{ result: result.trim() }] };
1233
+ },
1234
+ async close() {
1235
+ try {
1236
+ socket.destroy();
1237
+ } catch {
1238
+ }
1239
+ },
1240
+ async ping() {
1241
+ try {
1242
+ const r = await sendRedisCommand(socket, "PING");
1243
+ if (!r.includes("PONG")) throw new Error("No PONG response");
1244
+ return true;
1245
+ } catch (e) {
1246
+ throw new Error("Redis ping failed: " + (e.message || e));
1247
+ }
1248
+ }
1249
+ };
1250
+ }
1251
+ });
1252
+ this.drivers.set("upstash", {
1253
+ async connect(config, credentials) {
1254
+ let baseUrl = "";
1255
+ let token = credentials.password || "";
1256
+ if (credentials.connectionString) {
1257
+ const cs = credentials.connectionString;
1258
+ if (cs.startsWith("https://") && cs.includes("@")) {
1259
+ try {
1260
+ const url = new URL(cs);
1261
+ token = token || url.username || url.password;
1262
+ baseUrl = `https://${url.host}`;
1263
+ } catch {
1264
+ baseUrl = cs;
1265
+ }
1266
+ } else if (cs.startsWith("https://")) {
1267
+ baseUrl = cs.replace(/\/$/, "");
1268
+ } else if (cs.startsWith("rediss://")) {
1269
+ try {
1270
+ const url = new URL(cs.replace("rediss://", "https://"));
1271
+ token = token || url.password;
1272
+ baseUrl = `https://${url.hostname}`;
1273
+ } catch {
1274
+ baseUrl = `https://${config.host}`;
1275
+ }
1276
+ } else {
1277
+ baseUrl = `https://${cs}`;
1278
+ }
1279
+ } else {
1280
+ baseUrl = `https://${config.host}`;
1281
+ }
1282
+ if (!baseUrl) throw new Error("Upstash requires a REST URL or hostname. Find it in your Upstash console under REST API.");
1283
+ if (!token) throw new Error("Upstash requires an auth token. Find it in your Upstash console under REST API.");
1284
+ async function upstashRequest(command) {
1285
+ const ctrl = new AbortController();
1286
+ const timer = setTimeout(() => ctrl.abort(), 1e4);
1287
+ try {
1288
+ const resp = await fetch(baseUrl, {
1289
+ method: "POST",
1290
+ headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
1291
+ body: JSON.stringify(command),
1292
+ signal: ctrl.signal
1293
+ });
1294
+ if (!resp.ok) {
1295
+ const body = await resp.text().catch(() => "");
1296
+ throw new Error(`Upstash HTTP ${resp.status}: ${body.slice(0, 200)}`);
1297
+ }
1298
+ return resp.json();
1299
+ } finally {
1300
+ clearTimeout(timer);
1301
+ }
1302
+ }
1303
+ await self.connectWithTimeout(async () => {
1304
+ const r = await upstashRequest(["PING"]);
1305
+ if (r.result !== "PONG") throw new Error("Upstash PING failed \u2014 check your token and endpoint URL");
1306
+ }, 15e3, "Upstash");
1307
+ return {
1308
+ async query(q) {
1309
+ const parts = q.trim().split(/\s+/);
1310
+ const result = await upstashRequest(parts);
1311
+ const val = result.result ?? result;
1312
+ return { rows: [{ result: typeof val === "string" ? val : JSON.stringify(val) }] };
1313
+ },
1314
+ async close() {
1315
+ },
1316
+ async ping() {
1317
+ try {
1318
+ const r = await upstashRequest(["PING"]);
1319
+ if (r.result !== "PONG") throw new Error("No PONG response");
1320
+ return true;
1321
+ } catch (e) {
1322
+ throw new Error("Upstash ping failed: " + (e.message || e));
1323
+ }
1324
+ }
1325
+ };
1326
+ }
1327
+ });
1328
+ }
1329
+ // ─── Row Mapping ───────────────────────────────────────────────────────────
1330
+ rowToConfig(row) {
1331
+ const config = typeof row.config === "string" ? JSON.parse(row.config) : row.config || {};
1332
+ return {
1333
+ id: row.id,
1334
+ orgId: row.org_id,
1335
+ name: row.name,
1336
+ type: row.type,
1337
+ ...config,
1338
+ status: row.status,
1339
+ lastTestedAt: row.last_tested_at,
1340
+ lastError: row.last_error,
1341
+ createdAt: row.created_at,
1342
+ updatedAt: row.updated_at
1343
+ };
1344
+ }
1345
+ rowToAccess(row) {
1346
+ return {
1347
+ id: row.id,
1348
+ orgId: row.org_id,
1349
+ agentId: row.agent_id,
1350
+ connectionId: row.connection_id,
1351
+ permissions: typeof row.permissions === "string" ? JSON.parse(row.permissions) : row.permissions || ["read"],
1352
+ queryLimits: typeof row.query_limits === "string" ? JSON.parse(row.query_limits) : row.query_limits,
1353
+ schemaAccess: typeof row.schema_access === "string" ? JSON.parse(row.schema_access) : row.schema_access,
1354
+ logAllQueries: !!row.log_all_queries,
1355
+ requireApproval: !!row.require_approval,
1356
+ enabled: !!row.enabled,
1357
+ createdAt: row.created_at,
1358
+ updatedAt: row.updated_at
1359
+ };
1360
+ }
1361
+ configToStorable(config) {
1362
+ const { id, orgId, name, type, status, lastTestedAt, lastError, createdAt, updatedAt, ...rest } = config;
1363
+ return rest;
1364
+ }
1365
+ // ─── Cleanup ───────────────────────────────────────────────────────────────
1366
+ async shutdown() {
1367
+ for (const [connId] of this.pools) {
1368
+ await this.closePool(connId);
1369
+ }
1370
+ console.log("[db-access] All connection pools closed");
1371
+ }
1372
+ };
1373
+ }
1374
+ });
1375
+
1376
+ export {
1377
+ init_query_sanitizer,
1378
+ DatabaseConnectionManager,
1379
+ init_connection_manager
1380
+ };