whale-code 6.5.5 → 6.5.6

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 (847) hide show
  1. package/README.md +39 -31
  2. package/bin/{swagmanager-mcp.js → whale-code.js} +17 -2
  3. package/dist/cli/app.js +148 -72
  4. package/dist/cli/app.js.map +1 -0
  5. package/dist/cli/chat/AgentSelector.js +105 -10
  6. package/dist/cli/chat/AgentSelector.js.map +1 -0
  7. package/dist/cli/chat/ChatApp.d.ts +31 -0
  8. package/dist/cli/chat/ChatApp.js +539 -286
  9. package/dist/cli/chat/ChatApp.js.map +1 -0
  10. package/dist/cli/chat/ChatInput.js +1088 -770
  11. package/dist/cli/chat/ChatInput.js.map +1 -0
  12. package/dist/cli/chat/MarkdownText.js +39 -14
  13. package/dist/cli/chat/MarkdownText.js.map +1 -0
  14. package/dist/cli/chat/MemoryManager.js +181 -46
  15. package/dist/cli/chat/MemoryManager.js.map +1 -0
  16. package/dist/cli/chat/MessageList.d.ts +2 -3
  17. package/dist/cli/chat/MessageList.js +186 -45
  18. package/dist/cli/chat/MessageList.js.map +1 -0
  19. package/dist/cli/chat/ModelSelector.js +282 -63
  20. package/dist/cli/chat/ModelSelector.js.map +1 -0
  21. package/dist/cli/chat/NodeManager.js +165 -75
  22. package/dist/cli/chat/NodeManager.js.map +1 -0
  23. package/dist/cli/chat/NodeSelector.js +171 -30
  24. package/dist/cli/chat/NodeSelector.js.map +1 -0
  25. package/dist/cli/chat/PlanApproval.js +281 -57
  26. package/dist/cli/chat/PlanApproval.js.map +1 -0
  27. package/dist/cli/chat/RewindViewer.js +559 -144
  28. package/dist/cli/chat/RewindViewer.js.map +1 -0
  29. package/dist/cli/chat/SessionManager.js +137 -30
  30. package/dist/cli/chat/SessionManager.js.map +1 -0
  31. package/dist/cli/chat/SlashMenu.js +293 -164
  32. package/dist/cli/chat/SlashMenu.js.map +1 -0
  33. package/dist/cli/chat/StatusBar.js +172 -9
  34. package/dist/cli/chat/StatusBar.js.map +1 -0
  35. package/dist/cli/chat/StoreSelector.js +147 -18
  36. package/dist/cli/chat/StoreSelector.js.map +1 -0
  37. package/dist/cli/chat/StreamingText.d.ts +1 -5
  38. package/dist/cli/chat/StreamingText.js +22 -7
  39. package/dist/cli/chat/StreamingText.js.map +1 -0
  40. package/dist/cli/chat/SubagentPanel.d.ts +1 -2
  41. package/dist/cli/chat/SubagentPanel.js +612 -72
  42. package/dist/cli/chat/SubagentPanel.js.map +1 -0
  43. package/dist/cli/chat/TeamPanel.d.ts +1 -0
  44. package/dist/cli/chat/TeamPanel.js +230 -30
  45. package/dist/cli/chat/TeamPanel.js.map +1 -0
  46. package/dist/cli/chat/ThemeSelector.js +84 -24
  47. package/dist/cli/chat/ThemeSelector.js.map +1 -0
  48. package/dist/cli/chat/ToolIndicator.js +1476 -371
  49. package/dist/cli/chat/ToolIndicator.js.map +1 -0
  50. package/dist/cli/chat/hooks/useAgentLoop.d.ts +1 -0
  51. package/dist/cli/chat/hooks/useAgentLoop.js +481 -367
  52. package/dist/cli/chat/hooks/useAgentLoop.js.map +1 -0
  53. package/dist/cli/chat/hooks/useSlashCommands.d.ts +3 -14
  54. package/dist/cli/chat/hooks/useSlashCommands.js +744 -572
  55. package/dist/cli/chat/hooks/useSlashCommands.js.map +1 -0
  56. package/dist/cli/commands/config-cmd.js +56 -57
  57. package/dist/cli/commands/config-cmd.js.map +1 -0
  58. package/dist/cli/commands/db.js +184 -169
  59. package/dist/cli/commands/db.js.map +1 -0
  60. package/dist/cli/commands/doctor.js +212 -122
  61. package/dist/cli/commands/doctor.js.map +1 -0
  62. package/dist/cli/commands/init.js +211 -244
  63. package/dist/cli/commands/init.js.map +1 -0
  64. package/dist/cli/commands/mcp.js +127 -122
  65. package/dist/cli/commands/mcp.js.map +1 -0
  66. package/dist/cli/login/LoginApp.js +355 -141
  67. package/dist/cli/login/LoginApp.js.map +1 -0
  68. package/dist/cli/print-mode.js +196 -177
  69. package/dist/cli/print-mode.js.map +1 -0
  70. package/dist/cli/serve-mode.js +615 -530
  71. package/dist/cli/serve-mode.js.map +1 -0
  72. package/dist/cli/services/agent-config.d.ts +5 -1
  73. package/dist/cli/services/agent-config.js +66 -36
  74. package/dist/cli/services/agent-config.js.map +1 -0
  75. package/dist/cli/services/agent-definitions.d.ts +4 -1
  76. package/dist/cli/services/agent-definitions.js +97 -56
  77. package/dist/cli/services/agent-definitions.js.map +1 -0
  78. package/dist/cli/services/agent-events.js +225 -162
  79. package/dist/cli/services/agent-events.js.map +1 -0
  80. package/dist/cli/services/agent-loop.js +976 -688
  81. package/dist/cli/services/agent-loop.js.map +1 -0
  82. package/dist/cli/services/agent-worker-base.d.ts +35 -5
  83. package/dist/cli/services/agent-worker-base.js +337 -153
  84. package/dist/cli/services/agent-worker-base.js.map +1 -0
  85. package/dist/cli/services/api-retry.js +69 -64
  86. package/dist/cli/services/api-retry.js.map +1 -0
  87. package/dist/cli/services/auth-service.d.ts +3 -3
  88. package/dist/cli/services/auth-service.js +209 -132
  89. package/dist/cli/services/auth-service.js.map +1 -0
  90. package/dist/cli/services/background-processes.js +343 -267
  91. package/dist/cli/services/background-processes.js.map +1 -0
  92. package/dist/cli/services/browser-auth.d.ts +2 -2
  93. package/dist/cli/services/browser-auth.js +159 -118
  94. package/dist/cli/services/browser-auth.js.map +1 -0
  95. package/dist/cli/services/claude-md-loader.js +40 -36
  96. package/dist/cli/services/claude-md-loader.js.map +1 -0
  97. package/dist/cli/services/config-store.d.ts +9 -4
  98. package/dist/cli/services/config-store.js +164 -117
  99. package/dist/cli/services/config-store.js.map +1 -0
  100. package/dist/cli/services/debug-log.d.ts +1 -1
  101. package/dist/cli/services/debug-log.js +34 -35
  102. package/dist/cli/services/debug-log.js.map +1 -0
  103. package/dist/cli/services/env-detect.d.ts +7 -0
  104. package/dist/cli/services/env-detect.js +9 -0
  105. package/dist/cli/services/env-detect.js.map +1 -0
  106. package/dist/cli/services/error-logger.js +187 -169
  107. package/dist/cli/services/error-logger.js.map +1 -0
  108. package/dist/cli/services/file-history.d.ts +1 -1
  109. package/dist/cli/services/file-history.js +50 -54
  110. package/dist/cli/services/file-history.js.map +1 -0
  111. package/dist/cli/services/format-server-response.js +332 -372
  112. package/dist/cli/services/format-server-response.js.map +1 -0
  113. package/dist/cli/services/git-context.js +61 -45
  114. package/dist/cli/services/git-context.js.map +1 -0
  115. package/dist/cli/services/hooks.d.ts +2 -2
  116. package/dist/cli/services/hooks.js +195 -180
  117. package/dist/cli/services/hooks.js.map +1 -0
  118. package/dist/cli/services/ink-incremental.d.ts +19 -0
  119. package/dist/cli/services/ink-incremental.js +59 -0
  120. package/dist/cli/services/ink-incremental.js.map +1 -0
  121. package/dist/cli/services/ink-resize-fix.js +54 -44
  122. package/dist/cli/services/ink-resize-fix.js.map +1 -0
  123. package/dist/cli/services/ink-sync-output.d.ts +12 -0
  124. package/dist/cli/services/ink-sync-output.js +16 -0
  125. package/dist/cli/services/ink-sync-output.js.map +1 -0
  126. package/dist/cli/services/interactive-tools.js +268 -212
  127. package/dist/cli/services/interactive-tools.js.map +1 -0
  128. package/dist/cli/services/keybinding-manager.d.ts +11 -1
  129. package/dist/cli/services/keybinding-manager.js +126 -63
  130. package/dist/cli/services/keybinding-manager.js.map +1 -0
  131. package/dist/cli/services/local-tools.d.ts +1 -1
  132. package/dist/cli/services/local-tools.js +939 -656
  133. package/dist/cli/services/local-tools.js.map +1 -0
  134. package/dist/cli/services/lsp-manager.js +757 -594
  135. package/dist/cli/services/lsp-manager.js.map +1 -0
  136. package/dist/cli/services/mcp-client.d.ts +1 -1
  137. package/dist/cli/services/mcp-client.js +173 -134
  138. package/dist/cli/services/mcp-client.js.map +1 -0
  139. package/dist/cli/services/memory-manager.js +53 -40
  140. package/dist/cli/services/memory-manager.js.map +1 -0
  141. package/dist/cli/services/model-manager.js +55 -40
  142. package/dist/cli/services/model-manager.js.map +1 -0
  143. package/dist/cli/services/model-router.js +115 -85
  144. package/dist/cli/services/model-router.js.map +1 -0
  145. package/dist/cli/services/paths.d.ts +30 -0
  146. package/dist/cli/services/paths.js +81 -0
  147. package/dist/cli/services/paths.js.map +1 -0
  148. package/dist/cli/services/permission-modes.js +32 -25
  149. package/dist/cli/services/permission-modes.js.map +1 -0
  150. package/dist/cli/services/rewind.js +182 -168
  151. package/dist/cli/services/rewind.js.map +1 -0
  152. package/dist/cli/services/ripgrep.js +115 -115
  153. package/dist/cli/services/ripgrep.js.map +1 -0
  154. package/dist/cli/services/sandbox.d.ts +1 -1
  155. package/dist/cli/services/sandbox.js +58 -37
  156. package/dist/cli/services/sandbox.js.map +1 -0
  157. package/dist/cli/services/server-tools.js +738 -565
  158. package/dist/cli/services/server-tools.js.map +1 -0
  159. package/dist/cli/services/session-persistence.js +69 -74
  160. package/dist/cli/services/session-persistence.js.map +1 -0
  161. package/dist/cli/services/subagent-worker.js +42 -27
  162. package/dist/cli/services/subagent-worker.js.map +1 -0
  163. package/dist/cli/services/subagent.d.ts +2 -0
  164. package/dist/cli/services/subagent.js +605 -433
  165. package/dist/cli/services/subagent.js.map +1 -0
  166. package/dist/cli/services/system-prompt.js +86 -78
  167. package/dist/cli/services/system-prompt.js.map +1 -0
  168. package/dist/cli/services/task-decomposer.d.ts +1 -1
  169. package/dist/cli/services/task-decomposer.js +172 -139
  170. package/dist/cli/services/task-decomposer.js.map +1 -0
  171. package/dist/cli/services/team-lead.d.ts +2 -2
  172. package/dist/cli/services/team-lead.js +727 -529
  173. package/dist/cli/services/team-lead.js.map +1 -0
  174. package/dist/cli/services/team-state.js +319 -319
  175. package/dist/cli/services/team-state.js.map +1 -0
  176. package/dist/cli/services/teammate.d.ts +8 -2
  177. package/dist/cli/services/teammate.js +857 -569
  178. package/dist/cli/services/teammate.js.map +1 -0
  179. package/dist/cli/services/telemetry.d.ts +6 -1
  180. package/dist/cli/services/telemetry.js +180 -157
  181. package/dist/cli/services/telemetry.js.map +1 -0
  182. package/dist/cli/services/tools/agent-tools.d.ts +3 -3
  183. package/dist/cli/services/tools/agent-tools.js +480 -322
  184. package/dist/cli/services/tools/agent-tools.js.map +1 -0
  185. package/dist/cli/services/tools/file-ops.js +563 -450
  186. package/dist/cli/services/tools/file-ops.js.map +1 -0
  187. package/dist/cli/services/tools/search-tools.js +231 -162
  188. package/dist/cli/services/tools/search-tools.js.map +1 -0
  189. package/dist/cli/services/tools/shell-exec.js +197 -151
  190. package/dist/cli/services/tools/shell-exec.js.map +1 -0
  191. package/dist/cli/services/tools/task-manager.js +206 -173
  192. package/dist/cli/services/tools/task-manager.js.map +1 -0
  193. package/dist/cli/services/tools/web-tools.js +388 -341
  194. package/dist/cli/services/tools/web-tools.js.map +1 -0
  195. package/dist/cli/setup/SetupApp.d.ts +2 -2
  196. package/dist/cli/setup/SetupApp.js +608 -160
  197. package/dist/cli/setup/SetupApp.js.map +1 -0
  198. package/dist/cli/shared/ErrorBoundary.d.ts +22 -0
  199. package/dist/cli/shared/ErrorBoundary.js +73 -0
  200. package/dist/cli/shared/ErrorBoundary.js.map +1 -0
  201. package/dist/cli/shared/MatrixIntro.js +66 -69
  202. package/dist/cli/shared/MatrixIntro.js.map +1 -0
  203. package/dist/cli/shared/SpinnerSlot.d.ts +14 -0
  204. package/dist/cli/shared/SpinnerSlot.js +63 -0
  205. package/dist/cli/shared/SpinnerSlot.js.map +1 -0
  206. package/dist/cli/shared/Theme.d.ts +1 -1
  207. package/dist/cli/shared/Theme.js +136 -92
  208. package/dist/cli/shared/Theme.js.map +1 -0
  209. package/dist/cli/shared/WhaleBanner.js +99 -11
  210. package/dist/cli/shared/WhaleBanner.js.map +1 -0
  211. package/dist/cli/shared/markdown.d.ts +3 -1
  212. package/dist/cli/shared/markdown.js +736 -674
  213. package/dist/cli/shared/markdown.js.map +1 -0
  214. package/dist/cli/shared/marked-terminal.d.js +2 -0
  215. package/dist/cli/shared/marked-terminal.d.js.map +1 -0
  216. package/dist/cli/shared/theme-manager.js +99 -90
  217. package/dist/cli/shared/theme-manager.js.map +1 -0
  218. package/dist/cli/shared/theme-presets.js +256 -254
  219. package/dist/cli/shared/theme-presets.js.map +1 -0
  220. package/dist/cli/status/StatusApp.js +235 -86
  221. package/dist/cli/status/StatusApp.js.map +1 -0
  222. package/dist/cli/stores/StoreApp.js +275 -65
  223. package/dist/cli/stores/StoreApp.js.map +1 -0
  224. package/dist/index.d.ts +2 -2
  225. package/dist/index.js +509 -396
  226. package/dist/index.js.map +1 -0
  227. package/dist/local-agent/connection.d.ts +2 -2
  228. package/dist/local-agent/connection.js +352 -293
  229. package/dist/local-agent/connection.js.map +1 -0
  230. package/dist/local-agent/discovery.js +259 -122
  231. package/dist/local-agent/discovery.js.map +1 -0
  232. package/dist/local-agent/executor.js +216 -193
  233. package/dist/local-agent/executor.js.map +1 -0
  234. package/dist/local-agent/index.d.ts +2 -2
  235. package/dist/local-agent/index.js +156 -156
  236. package/dist/local-agent/index.js.map +1 -0
  237. package/dist/node/adapters/base.js +18 -8
  238. package/dist/node/adapters/base.js.map +1 -0
  239. package/dist/node/adapters/discord.js +286 -275
  240. package/dist/node/adapters/discord.js.map +1 -0
  241. package/dist/node/adapters/email.js +189 -202
  242. package/dist/node/adapters/email.js.map +1 -0
  243. package/dist/node/adapters/imessage.js +145 -142
  244. package/dist/node/adapters/imessage.js.map +1 -0
  245. package/dist/node/adapters/slack.js +237 -236
  246. package/dist/node/adapters/slack.js.map +1 -0
  247. package/dist/node/adapters/sms.js +149 -151
  248. package/dist/node/adapters/sms.js.map +1 -0
  249. package/dist/node/adapters/telegram.js +88 -92
  250. package/dist/node/adapters/telegram.js.map +1 -0
  251. package/dist/node/adapters/webchat.js +160 -136
  252. package/dist/node/adapters/webchat.js.map +1 -0
  253. package/dist/node/adapters/whatsapp.js +212 -215
  254. package/dist/node/adapters/whatsapp.js.map +1 -0
  255. package/dist/node/cli.js +884 -653
  256. package/dist/node/cli.js.map +1 -0
  257. package/dist/node/config.js +20 -18
  258. package/dist/node/config.js.map +1 -0
  259. package/dist/node/gateway-client.js +191 -181
  260. package/dist/node/gateway-client.js.map +1 -0
  261. package/dist/node/portal/clipboard.js +161 -130
  262. package/dist/node/portal/clipboard.js.map +1 -0
  263. package/dist/node/portal/discovery.js +51 -45
  264. package/dist/node/portal/discovery.js.map +1 -0
  265. package/dist/node/portal/forward.js +64 -58
  266. package/dist/node/portal/forward.js.map +1 -0
  267. package/dist/node/portal/index.js +246 -221
  268. package/dist/node/portal/index.js.map +1 -0
  269. package/dist/node/portal/multiplexer.js +192 -182
  270. package/dist/node/portal/multiplexer.js.map +1 -0
  271. package/dist/node/portal/permissions.js +102 -70
  272. package/dist/node/portal/permissions.js.map +1 -0
  273. package/dist/node/portal/protocol.js +153 -116
  274. package/dist/node/portal/protocol.js.map +1 -0
  275. package/dist/node/portal/screen.js +80 -69
  276. package/dist/node/portal/screen.js.map +1 -0
  277. package/dist/node/portal/session.js +124 -117
  278. package/dist/node/portal/session.js.map +1 -0
  279. package/dist/node/portal/shell.js +140 -113
  280. package/dist/node/portal/shell.js.map +1 -0
  281. package/dist/node/portal/stream.js +77 -75
  282. package/dist/node/portal/stream.js.map +1 -0
  283. package/dist/node/portal/transfer.js +190 -167
  284. package/dist/node/portal/transfer.js.map +1 -0
  285. package/dist/node/portal/ui.js +124 -99
  286. package/dist/node/portal/ui.js.map +1 -0
  287. package/dist/node/remote-desktop/compile-helper.js +50 -45
  288. package/dist/node/remote-desktop/compile-helper.js.map +1 -0
  289. package/dist/node/remote-desktop/index.js +215 -187
  290. package/dist/node/remote-desktop/index.js.map +1 -0
  291. package/dist/node/remote-desktop/protocol.js +45 -29
  292. package/dist/node/remote-desktop/protocol.js.map +1 -0
  293. package/dist/node/runtime.js +493 -410
  294. package/dist/node/runtime.js.map +1 -0
  295. package/dist/server/handlers/__test-utils__/test-db.js +39 -89
  296. package/dist/server/handlers/__test-utils__/test-db.js.map +1 -0
  297. package/dist/server/handlers/analytics.js +467 -261
  298. package/dist/server/handlers/analytics.js.map +1 -0
  299. package/dist/server/handlers/api-docs.js +1030 -895
  300. package/dist/server/handlers/api-docs.js.map +1 -0
  301. package/dist/server/handlers/api-keys.js +291 -242
  302. package/dist/server/handlers/api-keys.js.map +1 -0
  303. package/dist/server/handlers/billing.js +330 -239
  304. package/dist/server/handlers/billing.js.map +1 -0
  305. package/dist/server/handlers/browser.js +468 -395
  306. package/dist/server/handlers/browser.js.map +1 -0
  307. package/dist/server/handlers/catalog.js +1377 -978
  308. package/dist/server/handlers/catalog.js.map +1 -0
  309. package/dist/server/handlers/clickhouse.js +157 -109
  310. package/dist/server/handlers/clickhouse.js.map +1 -0
  311. package/dist/server/handlers/comms.js +1439 -984
  312. package/dist/server/handlers/comms.js.map +1 -0
  313. package/dist/server/handlers/creations.js +461 -394
  314. package/dist/server/handlers/creations.js.map +1 -0
  315. package/dist/server/handlers/crm.js +1082 -791
  316. package/dist/server/handlers/crm.js.map +1 -0
  317. package/dist/server/handlers/discovery.js +251 -232
  318. package/dist/server/handlers/discovery.js.map +1 -0
  319. package/dist/server/handlers/embeddings.js +241 -164
  320. package/dist/server/handlers/embeddings.js.map +1 -0
  321. package/dist/server/handlers/enrichment.js +887 -718
  322. package/dist/server/handlers/enrichment.js.map +1 -0
  323. package/dist/server/handlers/image-gen.js +467 -376
  324. package/dist/server/handlers/image-gen.js.map +1 -0
  325. package/dist/server/handlers/inventory.js +797 -424
  326. package/dist/server/handlers/inventory.js.map +1 -0
  327. package/dist/server/handlers/kali.js +272 -230
  328. package/dist/server/handlers/kali.js.map +1 -0
  329. package/dist/server/handlers/llm-providers.js +803 -580
  330. package/dist/server/handlers/llm-providers.js.map +1 -0
  331. package/dist/server/handlers/local-agent.js +133 -105
  332. package/dist/server/handlers/local-agent.js.map +1 -0
  333. package/dist/server/handlers/media.js +1179 -857
  334. package/dist/server/handlers/media.js.map +1 -0
  335. package/dist/server/handlers/meta-ads.js +2669 -2093
  336. package/dist/server/handlers/meta-ads.js.map +1 -0
  337. package/dist/server/handlers/nodes.js +1321 -913
  338. package/dist/server/handlers/nodes.js.map +1 -0
  339. package/dist/server/handlers/operations.js +183 -157
  340. package/dist/server/handlers/operations.js.map +1 -0
  341. package/dist/server/handlers/platform.js +346 -210
  342. package/dist/server/handlers/platform.js.map +1 -0
  343. package/dist/server/handlers/remove-bg.js +118 -86
  344. package/dist/server/handlers/remove-bg.js.map +1 -0
  345. package/dist/server/handlers/storefront.js +586 -446
  346. package/dist/server/handlers/storefront.js.map +1 -0
  347. package/dist/server/handlers/supply-chain.js +546 -326
  348. package/dist/server/handlers/supply-chain.js.map +1 -0
  349. package/dist/server/handlers/transcription.js +106 -97
  350. package/dist/server/handlers/transcription.js.map +1 -0
  351. package/dist/server/handlers/video-gen.js +593 -424
  352. package/dist/server/handlers/video-gen.js.map +1 -0
  353. package/dist/server/handlers/voice.js +1458 -1039
  354. package/dist/server/handlers/voice.js.map +1 -0
  355. package/dist/server/handlers/workflow-steps.js +2837 -2116
  356. package/dist/server/handlers/workflow-steps.js.map +1 -0
  357. package/dist/server/handlers/workflows.js +1630 -933
  358. package/dist/server/handlers/workflows.js.map +1 -0
  359. package/dist/server/index.js +3167 -2422
  360. package/dist/server/index.js.map +1 -0
  361. package/dist/server/lib/batch-client.js +471 -409
  362. package/dist/server/lib/batch-client.js.map +1 -0
  363. package/dist/server/lib/clickhouse-buffer.js +118 -104
  364. package/dist/server/lib/clickhouse-buffer.js.map +1 -0
  365. package/dist/server/lib/clickhouse-client.js +107 -107
  366. package/dist/server/lib/clickhouse-client.js.map +1 -0
  367. package/dist/server/lib/coa-renderer.js +1786 -356
  368. package/dist/server/lib/coa-renderer.js.map +1 -0
  369. package/dist/server/lib/code-worker-pool.js +227 -177
  370. package/dist/server/lib/code-worker-pool.js.map +1 -0
  371. package/dist/server/lib/code-worker.js +174 -164
  372. package/dist/server/lib/code-worker.js.map +1 -0
  373. package/dist/server/lib/compaction-service.d.ts +2 -12
  374. package/dist/server/lib/compaction-service.js +74 -184
  375. package/dist/server/lib/compaction-service.js.map +1 -0
  376. package/dist/server/lib/logger.js +36 -24
  377. package/dist/server/lib/logger.js.map +1 -0
  378. package/dist/server/lib/otel.js +101 -80
  379. package/dist/server/lib/otel.js.map +1 -0
  380. package/dist/server/lib/pdf-renderer.js +952 -788
  381. package/dist/server/lib/pdf-renderer.js.map +1 -0
  382. package/dist/server/lib/prompt-sanitizer.js +188 -108
  383. package/dist/server/lib/prompt-sanitizer.js.map +1 -0
  384. package/dist/server/lib/provider-capabilities.js +136 -138
  385. package/dist/server/lib/provider-capabilities.js.map +1 -0
  386. package/dist/server/lib/provider-failover.js +190 -168
  387. package/dist/server/lib/provider-failover.js.map +1 -0
  388. package/dist/server/lib/rate-limiter.js +186 -117
  389. package/dist/server/lib/rate-limiter.js.map +1 -0
  390. package/dist/server/lib/react-pdf-layout.js +551 -382
  391. package/dist/server/lib/react-pdf-layout.js.map +1 -0
  392. package/dist/server/lib/server-agent-loop.d.ts +4 -1
  393. package/dist/server/lib/server-agent-loop.js +906 -634
  394. package/dist/server/lib/server-agent-loop.js.map +1 -0
  395. package/dist/server/lib/server-subagent.js +260 -164
  396. package/dist/server/lib/server-subagent.js.map +1 -0
  397. package/dist/server/lib/session-checkpoint.js +105 -96
  398. package/dist/server/lib/session-checkpoint.js.map +1 -0
  399. package/dist/server/lib/ssrf-guard.js +193 -184
  400. package/dist/server/lib/ssrf-guard.js.map +1 -0
  401. package/dist/server/lib/supabase-client.js +94 -82
  402. package/dist/server/lib/supabase-client.js.map +1 -0
  403. package/dist/server/lib/template-resolver.js +154 -176
  404. package/dist/server/lib/template-resolver.js.map +1 -0
  405. package/dist/server/lib/utils.js +242 -133
  406. package/dist/server/lib/utils.js.map +1 -0
  407. package/dist/server/local-agent-gateway.d.ts +2 -2
  408. package/dist/server/local-agent-gateway.js +785 -627
  409. package/dist/server/local-agent-gateway.js.map +1 -0
  410. package/dist/server/providers/anthropic.js +250 -172
  411. package/dist/server/providers/anthropic.js.map +1 -0
  412. package/dist/server/providers/bedrock.js +217 -158
  413. package/dist/server/providers/bedrock.js.map +1 -0
  414. package/dist/server/providers/gemini.js +548 -418
  415. package/dist/server/providers/gemini.js.map +1 -0
  416. package/dist/server/providers/openai.js +571 -437
  417. package/dist/server/providers/openai.js.map +1 -0
  418. package/dist/server/providers/registry.js +23 -18
  419. package/dist/server/providers/registry.js.map +1 -0
  420. package/dist/server/providers/shared.js +123 -95
  421. package/dist/server/providers/shared.js.map +1 -0
  422. package/dist/server/providers/types.js +1 -11
  423. package/dist/server/providers/types.js.map +1 -0
  424. package/dist/server/proxy-handlers.js +209 -165
  425. package/dist/server/proxy-handlers.js.map +1 -0
  426. package/dist/server/tool-router.js +959 -599
  427. package/dist/server/tool-router.js.map +1 -0
  428. package/dist/server/validation.js +248 -188
  429. package/dist/server/validation.js.map +1 -0
  430. package/dist/server/worker.js +202 -133
  431. package/dist/server/worker.js.map +1 -0
  432. package/dist/setup.d.ts +2 -2
  433. package/dist/setup.js +151 -147
  434. package/dist/setup.js.map +1 -0
  435. package/dist/shared/agent-core.d.ts +115 -26
  436. package/dist/shared/agent-core.js +956 -522
  437. package/dist/shared/agent-core.js.map +1 -0
  438. package/dist/shared/anthropic-types.js +1 -6
  439. package/dist/shared/anthropic-types.js.map +1 -0
  440. package/dist/shared/api-client.d.ts +16 -9
  441. package/dist/shared/api-client.js +419 -327
  442. package/dist/shared/api-client.js.map +1 -0
  443. package/dist/shared/compaction.d.ts +36 -0
  444. package/dist/shared/compaction.js +138 -0
  445. package/dist/shared/compaction.js.map +1 -0
  446. package/dist/shared/constants.js +67 -64
  447. package/dist/shared/constants.js.map +1 -0
  448. package/dist/shared/sse-parser.js +221 -219
  449. package/dist/shared/sse-parser.js.map +1 -0
  450. package/dist/shared/tool-dispatch.d.ts +4 -0
  451. package/dist/shared/tool-dispatch.js +226 -165
  452. package/dist/shared/tool-dispatch.js.map +1 -0
  453. package/dist/shared/types.js +1 -6
  454. package/dist/shared/types.js.map +1 -0
  455. package/dist/types/cli-highlight.d.js +2 -0
  456. package/dist/types/cli-highlight.d.js.map +1 -0
  457. package/dist/types/diff.d.js +2 -0
  458. package/dist/types/diff.d.js.map +1 -0
  459. package/dist/types/pdf-parse.d.js +2 -0
  460. package/dist/types/pdf-parse.d.js.map +1 -0
  461. package/dist/updater.d.ts +1 -1
  462. package/dist/updater.js +118 -92
  463. package/dist/updater.js.map +1 -0
  464. package/dist/webchat/widget.js +227 -380
  465. package/dist/webchat/widget.js.map +1 -0
  466. package/package.json +22 -10
  467. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  468. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  469. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  470. package/vendor/ink/build/apply-styles.js +175 -0
  471. package/vendor/ink/build/build-layout.js +77 -0
  472. package/vendor/ink/build/calculate-wrapped-text.js +53 -0
  473. package/vendor/ink/build/colorize.d.ts +3 -0
  474. package/vendor/ink/build/colorize.js +48 -0
  475. package/vendor/ink/build/colorize.js.map +1 -0
  476. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  477. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  478. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  479. package/vendor/ink/build/components/App.d.ts +18 -0
  480. package/vendor/ink/build/components/App.js +351 -0
  481. package/vendor/ink/build/components/App.js.map +1 -0
  482. package/vendor/ink/build/components/AppContext.d.ts +15 -0
  483. package/vendor/ink/build/components/AppContext.js +11 -0
  484. package/vendor/ink/build/components/AppContext.js.map +1 -0
  485. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  486. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  487. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  488. package/vendor/ink/build/components/Box.d.ts +117 -0
  489. package/vendor/ink/build/components/Box.js +34 -0
  490. package/vendor/ink/build/components/Box.js.map +1 -0
  491. package/vendor/ink/build/components/Color.js +62 -0
  492. package/vendor/ink/build/components/Cursor.d.ts +83 -0
  493. package/vendor/ink/build/components/Cursor.js +53 -0
  494. package/vendor/ink/build/components/Cursor.js.map +1 -0
  495. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  496. package/vendor/ink/build/components/CursorContext.js +8 -0
  497. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  498. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  499. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  500. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  501. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  502. package/vendor/ink/build/components/ErrorOverview.js +84 -0
  503. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  504. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  505. package/vendor/ink/build/components/FocusContext.js +17 -0
  506. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  507. package/vendor/ink/build/components/Newline.d.ts +13 -0
  508. package/vendor/ink/build/components/Newline.js +8 -0
  509. package/vendor/ink/build/components/Newline.js.map +1 -0
  510. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  511. package/vendor/ink/build/components/Spacer.js +11 -0
  512. package/vendor/ink/build/components/Spacer.js.map +1 -0
  513. package/vendor/ink/build/components/Static.d.ts +24 -0
  514. package/vendor/ink/build/components/Static.js +28 -0
  515. package/vendor/ink/build/components/Static.js.map +1 -0
  516. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  517. package/vendor/ink/build/components/StderrContext.js +13 -0
  518. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  519. package/vendor/ink/build/components/StdinContext.d.ts +22 -0
  520. package/vendor/ink/build/components/StdinContext.js +19 -0
  521. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  522. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  523. package/vendor/ink/build/components/StdoutContext.js +13 -0
  524. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  525. package/vendor/ink/build/components/Text.d.ts +55 -0
  526. package/vendor/ink/build/components/Text.js +50 -0
  527. package/vendor/ink/build/components/Text.js.map +1 -0
  528. package/vendor/ink/build/components/Transform.d.ts +16 -0
  529. package/vendor/ink/build/components/Transform.js +15 -0
  530. package/vendor/ink/build/components/Transform.js.map +1 -0
  531. package/vendor/ink/build/cursor-helpers.d.ts +38 -0
  532. package/vendor/ink/build/cursor-helpers.js +56 -0
  533. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  534. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  535. package/vendor/ink/build/devtools-window-polyfill.js +65 -0
  536. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  537. package/vendor/ink/build/devtools.d.ts +1 -0
  538. package/vendor/ink/build/devtools.js +11 -0
  539. package/vendor/ink/build/devtools.js.map +1 -0
  540. package/vendor/ink/build/dom.d.ts +56 -0
  541. package/vendor/ink/build/dom.js +124 -0
  542. package/vendor/ink/build/dom.js.map +1 -0
  543. package/vendor/ink/build/experimental/apply-style.js +140 -0
  544. package/vendor/ink/build/experimental/dom.js +123 -0
  545. package/vendor/ink/build/experimental/output.js +91 -0
  546. package/vendor/ink/build/experimental/reconciler.js +141 -0
  547. package/vendor/ink/build/experimental/renderer.js +81 -0
  548. package/vendor/ink/build/get-max-width.d.ts +3 -0
  549. package/vendor/ink/build/get-max-width.js +10 -0
  550. package/vendor/ink/build/get-max-width.js.map +1 -0
  551. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  552. package/vendor/ink/build/hooks/use-app.js +8 -0
  553. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  554. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  555. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  556. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  557. package/vendor/ink/build/hooks/use-focus-manager.d.ts +28 -0
  558. package/vendor/ink/build/hooks/use-focus-manager.js +17 -0
  559. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  560. package/vendor/ink/build/hooks/use-focus.d.ts +29 -0
  561. package/vendor/ink/build/hooks/use-focus.js +42 -0
  562. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  563. package/vendor/ink/build/hooks/use-input.d.ts +131 -0
  564. package/vendor/ink/build/hooks/use-input.js +124 -0
  565. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  566. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +5 -0
  567. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +11 -0
  568. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  569. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  570. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  571. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  572. package/vendor/ink/build/hooks/use-stdin.d.ts +5 -0
  573. package/vendor/ink/build/hooks/use-stdin.js +8 -0
  574. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  575. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  576. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  577. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  578. package/vendor/ink/build/hooks/useInput.js +38 -0
  579. package/vendor/ink/build/index.d.ts +34 -0
  580. package/vendor/ink/build/index.js +20 -0
  581. package/vendor/ink/build/index.js.map +1 -0
  582. package/vendor/ink/build/ink.d.ts +90 -0
  583. package/vendor/ink/build/ink.js +654 -0
  584. package/vendor/ink/build/ink.js.map +1 -0
  585. package/vendor/ink/build/input-parser.d.ts +7 -0
  586. package/vendor/ink/build/input-parser.js +154 -0
  587. package/vendor/ink/build/input-parser.js.map +1 -0
  588. package/vendor/ink/build/instance.js +205 -0
  589. package/vendor/ink/build/instances.d.ts +3 -0
  590. package/vendor/ink/build/instances.js +8 -0
  591. package/vendor/ink/build/instances.js.map +1 -0
  592. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  593. package/vendor/ink/build/kitty-keyboard.js +32 -0
  594. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  595. package/vendor/ink/build/layout.d.ts +7 -0
  596. package/vendor/ink/build/layout.js +33 -0
  597. package/vendor/ink/build/layout.js.map +1 -0
  598. package/vendor/ink/build/log-update.d.ts +19 -0
  599. package/vendor/ink/build/log-update.js +243 -0
  600. package/vendor/ink/build/log-update.js.map +1 -0
  601. package/vendor/ink/build/measure-element.d.ts +16 -0
  602. package/vendor/ink/build/measure-element.js +9 -0
  603. package/vendor/ink/build/measure-element.js.map +1 -0
  604. package/vendor/ink/build/measure-text.d.ts +6 -0
  605. package/vendor/ink/build/measure-text.js +21 -0
  606. package/vendor/ink/build/measure-text.js.map +1 -0
  607. package/vendor/ink/build/options.d.ts +52 -0
  608. package/vendor/ink/build/options.js +2 -0
  609. package/vendor/ink/build/options.js.map +1 -0
  610. package/vendor/ink/build/output.d.ts +35 -0
  611. package/vendor/ink/build/output.js +183 -0
  612. package/vendor/ink/build/output.js.map +1 -0
  613. package/vendor/ink/build/parse-keypress.d.ts +22 -0
  614. package/vendor/ink/build/parse-keypress.js +493 -0
  615. package/vendor/ink/build/parse-keypress.js.map +1 -0
  616. package/vendor/ink/build/reconciler.d.ts +4 -0
  617. package/vendor/ink/build/reconciler.js +274 -0
  618. package/vendor/ink/build/reconciler.js.map +1 -0
  619. package/vendor/ink/build/render-background.d.ts +4 -0
  620. package/vendor/ink/build/render-background.js +25 -0
  621. package/vendor/ink/build/render-background.js.map +1 -0
  622. package/vendor/ink/build/render-border.d.ts +4 -0
  623. package/vendor/ink/build/render-border.js +73 -0
  624. package/vendor/ink/build/render-border.js.map +1 -0
  625. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  626. package/vendor/ink/build/render-node-to-output.js +147 -0
  627. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  628. package/vendor/ink/build/render-to-string.d.ts +38 -0
  629. package/vendor/ink/build/render-to-string.js +115 -0
  630. package/vendor/ink/build/render-to-string.js.map +1 -0
  631. package/vendor/ink/build/render.d.ts +121 -0
  632. package/vendor/ink/build/render.js +55 -0
  633. package/vendor/ink/build/render.js.map +1 -0
  634. package/vendor/ink/build/renderer.d.ts +8 -0
  635. package/vendor/ink/build/renderer.js +55 -0
  636. package/vendor/ink/build/renderer.js.map +1 -0
  637. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  638. package/vendor/ink/build/sanitize-ansi.js +27 -0
  639. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  640. package/vendor/ink/build/screen-reader-update.d.ts +13 -0
  641. package/vendor/ink/build/screen-reader-update.js +38 -0
  642. package/vendor/ink/build/screen-reader-update.js.map +1 -0
  643. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  644. package/vendor/ink/build/squash-text-nodes.js +36 -0
  645. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  646. package/vendor/ink/build/styles.d.ts +240 -0
  647. package/vendor/ink/build/styles.js +232 -0
  648. package/vendor/ink/build/styles.js.map +1 -0
  649. package/vendor/ink/build/utils.d.ts +2 -0
  650. package/vendor/ink/build/utils.js +4 -0
  651. package/vendor/ink/build/utils.js.map +1 -0
  652. package/vendor/ink/build/wrap-text.d.ts +3 -0
  653. package/vendor/ink/build/wrap-text.js +31 -0
  654. package/vendor/ink/build/wrap-text.js.map +1 -0
  655. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  656. package/vendor/ink/build/write-synchronized.js +7 -0
  657. package/vendor/ink/build/write-synchronized.js.map +1 -0
  658. package/vendor/ink/license +10 -0
  659. package/vendor/ink/node_modules/@types/node/LICENSE +21 -0
  660. package/vendor/ink/node_modules/@types/node/README.md +15 -0
  661. package/vendor/ink/node_modules/@types/node/assert/strict.d.ts +105 -0
  662. package/vendor/ink/node_modules/@types/node/assert.d.ts +955 -0
  663. package/vendor/ink/node_modules/@types/node/async_hooks.d.ts +623 -0
  664. package/vendor/ink/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  665. package/vendor/ink/node_modules/@types/node/buffer.d.ts +1810 -0
  666. package/vendor/ink/node_modules/@types/node/child_process.d.ts +1428 -0
  667. package/vendor/ink/node_modules/@types/node/cluster.d.ts +486 -0
  668. package/vendor/ink/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  669. package/vendor/ink/node_modules/@types/node/console.d.ts +151 -0
  670. package/vendor/ink/node_modules/@types/node/constants.d.ts +20 -0
  671. package/vendor/ink/node_modules/@types/node/crypto.d.ts +4065 -0
  672. package/vendor/ink/node_modules/@types/node/dgram.d.ts +564 -0
  673. package/vendor/ink/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  674. package/vendor/ink/node_modules/@types/node/dns/promises.d.ts +503 -0
  675. package/vendor/ink/node_modules/@types/node/dns.d.ts +922 -0
  676. package/vendor/ink/node_modules/@types/node/domain.d.ts +166 -0
  677. package/vendor/ink/node_modules/@types/node/events.d.ts +1054 -0
  678. package/vendor/ink/node_modules/@types/node/fs/promises.d.ts +1329 -0
  679. package/vendor/ink/node_modules/@types/node/fs.d.ts +4676 -0
  680. package/vendor/ink/node_modules/@types/node/globals.d.ts +150 -0
  681. package/vendor/ink/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  682. package/vendor/ink/node_modules/@types/node/http.d.ts +2167 -0
  683. package/vendor/ink/node_modules/@types/node/http2.d.ts +2480 -0
  684. package/vendor/ink/node_modules/@types/node/https.d.ts +405 -0
  685. package/vendor/ink/node_modules/@types/node/index.d.ts +115 -0
  686. package/vendor/ink/node_modules/@types/node/inspector/promises.d.ts +41 -0
  687. package/vendor/ink/node_modules/@types/node/inspector.d.ts +224 -0
  688. package/vendor/ink/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  689. package/vendor/ink/node_modules/@types/node/module.d.ts +819 -0
  690. package/vendor/ink/node_modules/@types/node/net.d.ts +933 -0
  691. package/vendor/ink/node_modules/@types/node/os.d.ts +507 -0
  692. package/vendor/ink/node_modules/@types/node/package.json +155 -0
  693. package/vendor/ink/node_modules/@types/node/path/posix.d.ts +8 -0
  694. package/vendor/ink/node_modules/@types/node/path/win32.d.ts +8 -0
  695. package/vendor/ink/node_modules/@types/node/path.d.ts +187 -0
  696. package/vendor/ink/node_modules/@types/node/perf_hooks.d.ts +643 -0
  697. package/vendor/ink/node_modules/@types/node/process.d.ts +2156 -0
  698. package/vendor/ink/node_modules/@types/node/punycode.d.ts +117 -0
  699. package/vendor/ink/node_modules/@types/node/querystring.d.ts +152 -0
  700. package/vendor/ink/node_modules/@types/node/quic.d.ts +910 -0
  701. package/vendor/ink/node_modules/@types/node/readline/promises.d.ts +161 -0
  702. package/vendor/ink/node_modules/@types/node/readline.d.ts +541 -0
  703. package/vendor/ink/node_modules/@types/node/repl.d.ts +415 -0
  704. package/vendor/ink/node_modules/@types/node/sea.d.ts +162 -0
  705. package/vendor/ink/node_modules/@types/node/sqlite.d.ts +955 -0
  706. package/vendor/ink/node_modules/@types/node/stream/consumers.d.ts +38 -0
  707. package/vendor/ink/node_modules/@types/node/stream/promises.d.ts +211 -0
  708. package/vendor/ink/node_modules/@types/node/stream/web.d.ts +296 -0
  709. package/vendor/ink/node_modules/@types/node/stream.d.ts +1760 -0
  710. package/vendor/ink/node_modules/@types/node/string_decoder.d.ts +67 -0
  711. package/vendor/ink/node_modules/@types/node/test/reporters.d.ts +96 -0
  712. package/vendor/ink/node_modules/@types/node/test.d.ts +2240 -0
  713. package/vendor/ink/node_modules/@types/node/timers/promises.d.ts +108 -0
  714. package/vendor/ink/node_modules/@types/node/timers.d.ts +159 -0
  715. package/vendor/ink/node_modules/@types/node/tls.d.ts +1198 -0
  716. package/vendor/ink/node_modules/@types/node/trace_events.d.ts +197 -0
  717. package/vendor/ink/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  718. package/vendor/ink/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  719. package/vendor/ink/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  720. package/vendor/ink/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  721. package/vendor/ink/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  722. package/vendor/ink/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  723. package/vendor/ink/node_modules/@types/node/tty.d.ts +250 -0
  724. package/vendor/ink/node_modules/@types/node/url.d.ts +519 -0
  725. package/vendor/ink/node_modules/@types/node/util/types.d.ts +558 -0
  726. package/vendor/ink/node_modules/@types/node/util.d.ts +1662 -0
  727. package/vendor/ink/node_modules/@types/node/v8.d.ts +983 -0
  728. package/vendor/ink/node_modules/@types/node/vm.d.ts +1208 -0
  729. package/vendor/ink/node_modules/@types/node/wasi.d.ts +202 -0
  730. package/vendor/ink/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  731. package/vendor/ink/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  732. package/vendor/ink/node_modules/@types/node/web-globals/console.d.ts +9 -0
  733. package/vendor/ink/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  734. package/vendor/ink/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  735. package/vendor/ink/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  736. package/vendor/ink/node_modules/@types/node/web-globals/events.d.ts +106 -0
  737. package/vendor/ink/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
  738. package/vendor/ink/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  739. package/vendor/ink/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  740. package/vendor/ink/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  741. package/vendor/ink/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  742. package/vendor/ink/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  743. package/vendor/ink/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  744. package/vendor/ink/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  745. package/vendor/ink/node_modules/@types/node/web-globals/url.d.ts +24 -0
  746. package/vendor/ink/node_modules/@types/node/worker_threads.d.ts +717 -0
  747. package/vendor/ink/node_modules/@types/node/zlib.d.ts +618 -0
  748. package/vendor/ink/node_modules/node-pty/LICENSE +69 -0
  749. package/vendor/ink/node_modules/node-pty/README.md +164 -0
  750. package/vendor/ink/node_modules/node-pty/binding.gyp +150 -0
  751. package/vendor/ink/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  752. package/vendor/ink/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  753. package/vendor/ink/node_modules/node-pty/lib/index.js +52 -0
  754. package/vendor/ink/node_modules/node-pty/lib/interfaces.js +7 -0
  755. package/vendor/ink/node_modules/node-pty/lib/shared/conout.js +11 -0
  756. package/vendor/ink/node_modules/node-pty/lib/terminal.js +190 -0
  757. package/vendor/ink/node_modules/node-pty/lib/types.js +7 -0
  758. package/vendor/ink/node_modules/node-pty/lib/unixTerminal.js +349 -0
  759. package/vendor/ink/node_modules/node-pty/lib/utils.js +39 -0
  760. package/vendor/ink/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  761. package/vendor/ink/node_modules/node-pty/lib/windowsPtyAgent.js +287 -0
  762. package/vendor/ink/node_modules/node-pty/lib/windowsTerminal.js +201 -0
  763. package/vendor/ink/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  764. package/vendor/ink/node_modules/node-pty/package.json +65 -0
  765. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  766. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  767. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  768. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  769. package/vendor/ink/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  770. package/vendor/ink/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  771. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  772. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  773. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  774. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  775. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  776. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  777. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  778. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  779. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  780. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  781. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  782. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  783. package/vendor/ink/node_modules/node-pty/scripts/post-install.js +76 -0
  784. package/vendor/ink/node_modules/node-pty/scripts/prebuild.js +34 -0
  785. package/vendor/ink/node_modules/node-pty/src/unix/pty.cc +875 -0
  786. package/vendor/ink/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  787. package/vendor/ink/node_modules/node-pty/src/win/conpty.cc +582 -0
  788. package/vendor/ink/node_modules/node-pty/src/win/conpty.h +41 -0
  789. package/vendor/ink/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  790. package/vendor/ink/node_modules/node-pty/src/win/path_util.cc +95 -0
  791. package/vendor/ink/node_modules/node-pty/src/win/path_util.h +26 -0
  792. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  793. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  794. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  795. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  796. package/vendor/ink/node_modules/node-pty/typings/node-pty.d.ts +215 -0
  797. package/vendor/ink/node_modules/undici-types/LICENSE +21 -0
  798. package/vendor/ink/node_modules/undici-types/README.md +6 -0
  799. package/vendor/ink/node_modules/undici-types/agent.d.ts +32 -0
  800. package/vendor/ink/node_modules/undici-types/api.d.ts +43 -0
  801. package/vendor/ink/node_modules/undici-types/balanced-pool.d.ts +30 -0
  802. package/vendor/ink/node_modules/undici-types/cache-interceptor.d.ts +173 -0
  803. package/vendor/ink/node_modules/undici-types/cache.d.ts +36 -0
  804. package/vendor/ink/node_modules/undici-types/client-stats.d.ts +15 -0
  805. package/vendor/ink/node_modules/undici-types/client.d.ts +108 -0
  806. package/vendor/ink/node_modules/undici-types/connector.d.ts +34 -0
  807. package/vendor/ink/node_modules/undici-types/content-type.d.ts +21 -0
  808. package/vendor/ink/node_modules/undici-types/cookies.d.ts +30 -0
  809. package/vendor/ink/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  810. package/vendor/ink/node_modules/undici-types/dispatcher.d.ts +276 -0
  811. package/vendor/ink/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  812. package/vendor/ink/node_modules/undici-types/errors.d.ts +161 -0
  813. package/vendor/ink/node_modules/undici-types/eventsource.d.ts +66 -0
  814. package/vendor/ink/node_modules/undici-types/fetch.d.ts +211 -0
  815. package/vendor/ink/node_modules/undici-types/formdata.d.ts +108 -0
  816. package/vendor/ink/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  817. package/vendor/ink/node_modules/undici-types/global-origin.d.ts +7 -0
  818. package/vendor/ink/node_modules/undici-types/h2c-client.d.ts +73 -0
  819. package/vendor/ink/node_modules/undici-types/handlers.d.ts +15 -0
  820. package/vendor/ink/node_modules/undici-types/header.d.ts +160 -0
  821. package/vendor/ink/node_modules/undici-types/index.d.ts +88 -0
  822. package/vendor/ink/node_modules/undici-types/interceptors.d.ts +73 -0
  823. package/vendor/ink/node_modules/undici-types/mock-agent.d.ts +68 -0
  824. package/vendor/ink/node_modules/undici-types/mock-call-history.d.ts +111 -0
  825. package/vendor/ink/node_modules/undici-types/mock-client.d.ts +27 -0
  826. package/vendor/ink/node_modules/undici-types/mock-errors.d.ts +12 -0
  827. package/vendor/ink/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  828. package/vendor/ink/node_modules/undici-types/mock-pool.d.ts +27 -0
  829. package/vendor/ink/node_modules/undici-types/package.json +55 -0
  830. package/vendor/ink/node_modules/undici-types/patch.d.ts +29 -0
  831. package/vendor/ink/node_modules/undici-types/pool-stats.d.ts +19 -0
  832. package/vendor/ink/node_modules/undici-types/pool.d.ts +41 -0
  833. package/vendor/ink/node_modules/undici-types/proxy-agent.d.ts +29 -0
  834. package/vendor/ink/node_modules/undici-types/readable.d.ts +68 -0
  835. package/vendor/ink/node_modules/undici-types/retry-agent.d.ts +8 -0
  836. package/vendor/ink/node_modules/undici-types/retry-handler.d.ts +125 -0
  837. package/vendor/ink/node_modules/undici-types/round-robin-pool.d.ts +41 -0
  838. package/vendor/ink/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  839. package/vendor/ink/node_modules/undici-types/util.d.ts +18 -0
  840. package/vendor/ink/node_modules/undici-types/utility.d.ts +7 -0
  841. package/vendor/ink/node_modules/undici-types/webidl.d.ts +341 -0
  842. package/vendor/ink/node_modules/undici-types/websocket.d.ts +186 -0
  843. package/vendor/ink/package.json +201 -0
  844. package/vendor/ink/readme.md +2636 -0
  845. package/bin/swag-agent.js +0 -9
  846. package/dist/server/lib/pg-rate-limiter.d.ts +0 -21
  847. package/dist/server/lib/pg-rate-limiter.js +0 -86
@@ -1,990 +1,1398 @@
1
1
  // handlers/nodes.ts — Node & Channel management endpoints
2
2
  // Auth: User JWT for management, Node API key for node operations
3
+
3
4
  import { createHash, randomBytes, randomUUID } from "node:crypto";
4
5
  import { checkPlanLimits, incrementUsage } from "./billing.js";
5
6
  import { queueSpan, auditRowToSpan } from "../lib/clickhouse-buffer.js";
7
+
8
+ // ============================================================================
9
+ // TYPES
10
+ // ============================================================================
11
+
12
+ /** Context returned by resolveConversation — carries customer identity + session state */
13
+
14
+ /** Sender context passed through to the agent invoker for customer-aware prompts */
15
+
16
+ // Callback type for agent invocation (injected from index.ts to avoid circular deps)
17
+
6
18
  let agentInvoker = null;
19
+
7
20
  /** Set the agent invoker — called once from index.ts to break circular dependency */
8
21
  export function setNodeAgentInvoker(invoker) {
9
- agentInvoker = invoker;
22
+ agentInvoker = invoker;
10
23
  }
24
+
11
25
  // ============================================================================
12
26
  // HELPERS
13
27
  // ============================================================================
28
+
14
29
  function hashApiKey(key) {
15
- return createHash("sha256").update(key).digest("hex");
30
+ return createHash("sha256").update(key).digest("hex");
16
31
  }
17
32
  function generateNodeApiKey() {
18
- return randomBytes(32).toString("hex");
33
+ return randomBytes(32).toString("hex");
19
34
  }
35
+
20
36
  // 5-minute in-memory cache for node auth (survives transient Supabase 525s)
21
37
  const NODE_AUTH_CACHE_TTL = 5 * 60 * 1000;
22
38
  const nodeAuthCache = new Map();
39
+
23
40
  /** Clear the node auth cache (for tests). */
24
41
  export function clearNodeAuthCache() {
25
- nodeAuthCache.clear();
42
+ nodeAuthCache.clear();
26
43
  }
44
+
27
45
  /** Authenticate a node by API key, returns node row or null.
28
46
  * Retries once on transient error, falls back to cache if both fail. */
29
47
  async function authenticateNode(supabase, apiKey) {
30
- const hash = hashApiKey(apiKey);
31
- // Attempt DB query with one retry on transient errors
32
- for (let attempt = 0; attempt < 2; attempt++) {
33
- try {
34
- const { data, error } = await supabase
35
- .from("nodes")
36
- .select("id, store_id, name")
37
- .eq("api_key_hash", hash)
38
- .single();
39
- if (data) {
40
- // Success — update cache and return
41
- nodeAuthCache.set(hash, { node: data, cachedAt: Date.now() });
42
- return data;
43
- }
44
- // Non-transient error (e.g. row not found) — no retry
45
- if (error && !isTransientError(error)) {
46
- return null;
47
- }
48
- // Transient error on first attempt — retry
49
- if (attempt === 0 && error) {
50
- console.warn(`[node-auth] Transient error (attempt 1): ${error.message}. Retrying...`);
51
- continue;
52
- }
53
- return null;
54
- }
55
- catch (err) {
56
- // Network-level error (fetch failure, timeout)
57
- if (attempt === 0) {
58
- console.warn(`[node-auth] Network error (attempt 1): ${err.message}. Retrying...`);
59
- continue;
60
- }
61
- }
62
- }
63
- // Both attempts failed — fall back to cache
64
- const cached = nodeAuthCache.get(hash);
65
- if (cached && Date.now() - cached.cachedAt < NODE_AUTH_CACHE_TTL) {
66
- console.warn(`[node-auth] Using cached auth for node ${cached.node.id} (DB unavailable)`);
67
- return cached.node;
48
+ const hash = hashApiKey(apiKey);
49
+
50
+ // Attempt DB query with one retry on transient errors
51
+ for (let attempt = 0; attempt < 2; attempt++) {
52
+ try {
53
+ const {
54
+ data,
55
+ error
56
+ } = await supabase.from("nodes").select("id, store_id, name").eq("api_key_hash", hash).single();
57
+ if (data) {
58
+ // Success — update cache and return
59
+ nodeAuthCache.set(hash, {
60
+ node: data,
61
+ cachedAt: Date.now()
62
+ });
63
+ return data;
64
+ }
65
+
66
+ // Non-transient error (e.g. row not found) no retry
67
+ if (error && !isTransientError(error)) {
68
+ return null;
69
+ }
70
+
71
+ // Transient error on first attempt — retry
72
+ if (attempt === 0 && error) {
73
+ console.warn(`[node-auth] Transient error (attempt 1): ${error.message}. Retrying...`);
74
+ continue;
75
+ }
76
+ return null;
77
+ } catch (err) {
78
+ // Network-level error (fetch failure, timeout)
79
+ if (attempt === 0) {
80
+ console.warn(`[node-auth] Network error (attempt 1): ${err.message}. Retrying...`);
81
+ continue;
82
+ }
68
83
  }
69
- return null;
84
+ }
85
+
86
+ // Both attempts failed — fall back to cache
87
+ const cached = nodeAuthCache.get(hash);
88
+ if (cached && Date.now() - cached.cachedAt < NODE_AUTH_CACHE_TTL) {
89
+ console.warn(`[node-auth] Using cached auth for node ${cached.node.id} (DB unavailable)`);
90
+ return cached.node;
91
+ }
92
+ return null;
70
93
  }
94
+
71
95
  /** Check if a user (by auth UUID) has access to a store.
72
96
  * Mirrors the logic in get_user_store_ids():
73
97
  * 1) store_members.auth_user_id = authUserId
74
98
  * 2) stores.owner_user_id = platform_users.id WHERE auth_id = authUserId */
75
99
  async function userHasStoreAccess(supabase, authUserId, storeId) {
76
- // Check store_members first (direct membership)
77
- const { data: membership } = await supabase
78
- .from("store_members")
79
- .select("id")
80
- .eq("auth_user_id", authUserId)
81
- .eq("store_id", storeId)
82
- .limit(1);
83
- if (membership?.length)
84
- return true;
85
- // Check via platform_users → stores.owner_user_id
86
- const { data: platformUser } = await supabase
87
- .from("platform_users")
88
- .select("id")
89
- .eq("auth_id", authUserId)
90
- .limit(1)
91
- .single();
92
- if (platformUser) {
93
- const { data: ownedStore } = await supabase
94
- .from("stores")
95
- .select("id")
96
- .eq("owner_user_id", platformUser.id)
97
- .eq("id", storeId)
98
- .limit(1);
99
- if (ownedStore?.length)
100
- return true;
101
- }
102
- return false;
100
+ // Check store_members first (direct membership)
101
+ const {
102
+ data: membership
103
+ } = await supabase.from("store_members").select("id").eq("auth_user_id", authUserId).eq("store_id", storeId).limit(1);
104
+ if (membership?.length) return true;
105
+
106
+ // Check via platform_users → stores.owner_user_id
107
+ const {
108
+ data: platformUser
109
+ } = await supabase.from("platform_users").select("id").eq("auth_id", authUserId).limit(1).single();
110
+ if (platformUser) {
111
+ const {
112
+ data: ownedStore
113
+ } = await supabase.from("stores").select("id").eq("owner_user_id", platformUser.id).eq("id", storeId).limit(1);
114
+ if (ownedStore?.length) return true;
115
+ }
116
+ return false;
103
117
  }
118
+
104
119
  /** Check if a Supabase error is transient (network, 5xx, Cloudflare) */
105
120
  function isTransientError(error) {
106
- const msg = error.message || "";
107
- return msg.includes("525") || msg.includes("502") || msg.includes("503")
108
- || msg.includes("504") || msg.includes("520") || msg.includes("522")
109
- || msg.includes("524") || msg.includes("timeout") || msg.includes("ECONNRESET")
110
- || msg.includes("fetch failed");
121
+ const msg = error.message || "";
122
+ return msg.includes("525") || msg.includes("502") || msg.includes("503") || msg.includes("504") || msg.includes("520") || msg.includes("522") || msg.includes("524") || msg.includes("timeout") || msg.includes("ECONNRESET") || msg.includes("fetch failed");
111
123
  }
124
+
112
125
  /** Log a node event to node_events */
113
126
  async function logNodeEvent(supabase, storeId, nodeId, eventType, details = {}) {
114
- // node_events — existing table for node lifecycle
115
- await supabase.from("node_events").insert({
116
- store_id: storeId,
127
+ // node_events — existing table for node lifecycle
128
+ await supabase.from("node_events").insert({
129
+ store_id: storeId,
130
+ node_id: nodeId,
131
+ event_type: eventType,
132
+ details
133
+ });
134
+
135
+ // Telemetry → ClickHouse
136
+ try {
137
+ const now = new Date();
138
+ queueSpan(auditRowToSpan({
139
+ action: `node.${eventType}`,
140
+ severity: "info",
141
+ store_id: storeId,
142
+ resource_type: "whale_node",
143
+ resource_id: nodeId,
144
+ source: "whale-node",
145
+ user_id: details.registered_by || details.deleted_by || null,
146
+ trace_id: randomUUID(),
147
+ span_kind: "INTERNAL",
148
+ service_name: "whale-node",
149
+ status_code: "OK",
150
+ start_time: now.toISOString(),
151
+ end_time: now.toISOString(),
152
+ details: {
153
+ ...details,
117
154
  node_id: nodeId,
118
- event_type: eventType,
119
- details,
120
- });
121
- // Telemetry → ClickHouse
122
- try {
123
- const now = new Date();
124
- queueSpan(auditRowToSpan({
125
- action: `node.${eventType}`,
126
- severity: "info",
127
- store_id: storeId,
128
- resource_type: "whale_node",
129
- resource_id: nodeId,
130
- source: "whale-node",
131
- user_id: (details.registered_by || details.deleted_by || null),
132
- trace_id: randomUUID(),
133
- span_kind: "INTERNAL",
134
- service_name: "whale-node",
135
- status_code: "OK",
136
- start_time: now.toISOString(),
137
- end_time: now.toISOString(),
138
- details: { ...details, node_id: nodeId, event_type: eventType },
139
- }));
140
- }
141
- catch {
142
- // Telemetry must never break node operations
143
- }
155
+ event_type: eventType
156
+ }
157
+ }));
158
+ } catch {
159
+ // Telemetry must never break node operations
160
+ }
144
161
  }
162
+
145
163
  /** Resolve or create a conversation for a sender on a channel.
146
164
  * Reuses conversation if last message from same sender was < 30 min ago.
147
165
  * Creates an ai_conversations row for new sessions so telemetry/history works.
148
166
  * Resolves customer identity from the dynamic bridge table. */
149
167
  async function resolveConversation(supabase, channelId, senderId, storeId, channelType, senderName) {
150
- // ── Resolve customer identity (best-effort, any channel type) ──
151
- let customerId = null;
152
- let customerName = null;
153
- if (channelType) {
154
- try {
155
- const { data } = await supabase.rpc("resolve_sender_to_customer", {
156
- p_store_id: storeId,
157
- p_channel_type: channelType,
158
- p_sender_id: senderId,
159
- p_sender_name: senderName || null,
160
- });
161
- if (data?.length) {
162
- customerId = data[0].customer_id;
163
- customerName = data[0].customer_name;
164
- }
165
- }
166
- catch { /* customer resolution non-critical */ }
167
- }
168
- // ── 30-min window — reuse existing conversation ──
169
- const thirtyMinAgo = new Date(Date.now() - 30 * 60 * 1000).toISOString();
170
- const { data: recent } = await supabase
171
- .from("channel_messages")
172
- .select("conversation_id")
173
- .eq("channel_id", channelId)
174
- .eq("sender_id", senderId)
175
- .gt("created_at", thirtyMinAgo)
176
- .not("conversation_id", "is", null)
177
- .order("created_at", { ascending: false })
178
- .limit(1);
179
- if (recent?.length && recent[0].conversation_id) {
180
- // Backfill customer_id on existing conversation if newly resolved
181
- if (customerId) {
182
- Promise.resolve(supabase.from("ai_conversations")
183
- .update({ customer_id: customerId })
184
- .eq("id", recent[0].conversation_id)
185
- .is("customer_id", null)).catch(() => { });
186
- }
187
- return { conversationId: recent[0].conversation_id, customerId, customerName, isNewSession: false };
188
- }
189
- // ── New session — create ai_conversations row ──
190
- const newId = randomUUID();
191
- const title = senderName
192
- ? `${senderName} via ${channelType || "channel"}`
193
- : `${senderId} via ${channelType || "channel"}`;
168
+ // ── Resolve customer identity (best-effort, any channel type) ──
169
+ let customerId = null;
170
+ let customerName = null;
171
+ if (channelType) {
194
172
  try {
195
- await supabase.from("ai_conversations").insert({
196
- id: newId,
197
- store_id: storeId,
198
- agent_id: null, // set later by invokeAgentForChannel
199
- channel_id: channelId,
200
- sender_id: senderId,
201
- channel_type: channelType || null,
202
- customer_id: customerId,
203
- title,
204
- metadata: { source: "channel", channel_type: channelType || "unknown" },
205
- });
173
+ const {
174
+ data
175
+ } = await supabase.rpc("resolve_sender_to_customer", {
176
+ p_store_id: storeId,
177
+ p_channel_type: channelType,
178
+ p_sender_id: senderId,
179
+ p_sender_name: senderName || null
180
+ });
181
+ if (data?.length) {
182
+ customerId = data[0].customer_id;
183
+ customerName = data[0].customer_name;
184
+ }
185
+ } catch {/* customer resolution non-critical */}
186
+ }
187
+
188
+ // ── 30-min window — reuse existing conversation ──
189
+ const thirtyMinAgo = new Date(Date.now() - 30 * 60 * 1000).toISOString();
190
+ const {
191
+ data: recent
192
+ } = await supabase.from("channel_messages").select("conversation_id").eq("channel_id", channelId).eq("sender_id", senderId).gt("created_at", thirtyMinAgo).not("conversation_id", "is", null).order("created_at", {
193
+ ascending: false
194
+ }).limit(1);
195
+ if (recent?.length && recent[0].conversation_id) {
196
+ // Backfill customer_id on existing conversation if newly resolved
197
+ if (customerId) {
198
+ Promise.resolve(supabase.from("ai_conversations").update({
199
+ customer_id: customerId
200
+ }).eq("id", recent[0].conversation_id).is("customer_id", null)).catch(() => {});
206
201
  }
207
- catch { /* ai_conversations insert non-critical — agent still works without it */ }
208
- return { conversationId: newId, customerId, customerName, isNewSession: true };
202
+ return {
203
+ conversationId: recent[0].conversation_id,
204
+ customerId,
205
+ customerName,
206
+ isNewSession: false
207
+ };
208
+ }
209
+
210
+ // ── New session — create ai_conversations row ──
211
+ const newId = randomUUID();
212
+ const title = senderName ? `${senderName} via ${channelType || "channel"}` : `${senderId} via ${channelType || "channel"}`;
213
+ try {
214
+ await supabase.from("ai_conversations").insert({
215
+ id: newId,
216
+ store_id: storeId,
217
+ agent_id: null,
218
+ // set later by invokeAgentForChannel
219
+ channel_id: channelId,
220
+ sender_id: senderId,
221
+ channel_type: channelType || null,
222
+ customer_id: customerId,
223
+ title,
224
+ metadata: {
225
+ source: "channel",
226
+ channel_type: channelType || "unknown"
227
+ }
228
+ });
229
+ } catch {/* ai_conversations insert non-critical — agent still works without it */}
230
+ return {
231
+ conversationId: newId,
232
+ customerId,
233
+ customerName,
234
+ isNewSession: true
235
+ };
209
236
  }
237
+
210
238
  // ============================================================================
211
239
  // ROUTE HANDLER
212
240
  // ============================================================================
241
+
213
242
  export async function handleNodeRoutes(pathname, method, body, supabase, auth, queryParams) {
214
- // ── POST /nodes/register ──────────────────────────────────────
215
- // User auth required. Creates a node and returns API key (shown once).
216
- if (pathname === "/nodes/register" && method === "POST") {
217
- if (!auth.userId && !auth.isServiceRole) {
218
- return { status: 401, body: { error: "User authentication required" } };
219
- }
220
- if (!body)
221
- return { status: 400, body: { error: "Request body required" } };
222
- const reg = body;
223
- if (!reg.name || !reg.store_id) {
224
- return { status: 400, body: { error: "name and store_id required" } };
225
- }
226
- // Verify user has access to this store
227
- if (auth.userId) {
228
- const hasAccess = await userHasStoreAccess(supabase, auth.userId, reg.store_id);
229
- if (!hasAccess) {
230
- return { status: 403, body: { error: "No access to this store" } };
231
- }
232
- }
233
- // Check node limit against store plan
234
- const { data: planRow } = await supabase
235
- .from("store_plans")
236
- .select("plan, limits")
237
- .eq("store_id", reg.store_id)
238
- .single();
239
- const planLimits = planRow?.limits;
240
- const nodesMax = planLimits?.nodes_max ?? 1; // free plan default
241
- const { count } = await supabase
242
- .from("nodes")
243
- .select("id", { count: "exact", head: true })
244
- .eq("store_id", reg.store_id);
245
- if ((count || 0) >= nodesMax) {
246
- return { status: 429, body: { error: `Node limit reached (${count}/${nodesMax} on ${planRow?.plan || "free"} plan)` } };
247
- }
248
- const apiKey = generateNodeApiKey();
249
- const apiKeyHash = hashApiKey(apiKey);
250
- const { data: node, error } = await supabase
251
- .from("nodes")
252
- .insert({
253
- store_id: reg.store_id,
254
- name: reg.name,
255
- api_key_hash: apiKeyHash,
256
- capabilities: reg.capabilities || [],
257
- hardware: reg.hardware || {},
258
- version: reg.version || "1.0.0",
259
- config: reg.config || {},
260
- status: "offline",
261
- })
262
- .select("id, name, store_id, status, created_at")
263
- .single();
264
- if (error) {
265
- return { status: 500, body: { error: error.message } };
266
- }
267
- await logNodeEvent(supabase, reg.store_id, node.id, "registered", {
268
- name: reg.name,
269
- registered_by: auth.userId,
270
- });
243
+ // ── POST /nodes/register ──────────────────────────────────────
244
+ // User auth required. Creates a node and returns API key (shown once).
245
+ if (pathname === "/nodes/register" && method === "POST") {
246
+ if (!auth.userId && !auth.isServiceRole) {
247
+ return {
248
+ status: 401,
249
+ body: {
250
+ error: "User authentication required"
251
+ }
252
+ };
253
+ }
254
+ if (!body) return {
255
+ status: 400,
256
+ body: {
257
+ error: "Request body required"
258
+ }
259
+ };
260
+ const reg = body;
261
+ if (!reg.name || !reg.store_id) {
262
+ return {
263
+ status: 400,
264
+ body: {
265
+ error: "name and store_id required"
266
+ }
267
+ };
268
+ }
269
+
270
+ // Verify user has access to this store
271
+ if (auth.userId) {
272
+ const hasAccess = await userHasStoreAccess(supabase, auth.userId, reg.store_id);
273
+ if (!hasAccess) {
271
274
  return {
272
- status: 201,
273
- body: {
274
- success: true,
275
- node,
276
- api_key: apiKey, // Shown ONCE — user must save this
277
- message: "Save your API key — it cannot be retrieved later.",
278
- },
275
+ status: 403,
276
+ body: {
277
+ error: "No access to this store"
278
+ }
279
279
  };
280
+ }
281
+ }
282
+
283
+ // Check node limit against store plan
284
+ const {
285
+ data: planRow
286
+ } = await supabase.from("store_plans").select("plan, limits").eq("store_id", reg.store_id).single();
287
+ const planLimits = planRow?.limits;
288
+ const nodesMax = planLimits?.nodes_max ?? 1; // free plan default
289
+
290
+ const {
291
+ count
292
+ } = await supabase.from("nodes").select("id", {
293
+ count: "exact",
294
+ head: true
295
+ }).eq("store_id", reg.store_id);
296
+ if ((count || 0) >= nodesMax) {
297
+ return {
298
+ status: 429,
299
+ body: {
300
+ error: `Node limit reached (${count}/${nodesMax} on ${planRow?.plan || "free"} plan)`
301
+ }
302
+ };
303
+ }
304
+ const apiKey = generateNodeApiKey();
305
+ const apiKeyHash = hashApiKey(apiKey);
306
+ const {
307
+ data: node,
308
+ error
309
+ } = await supabase.from("nodes").insert({
310
+ store_id: reg.store_id,
311
+ name: reg.name,
312
+ api_key_hash: apiKeyHash,
313
+ capabilities: reg.capabilities || [],
314
+ hardware: reg.hardware || {},
315
+ version: reg.version || "1.0.0",
316
+ config: reg.config || {},
317
+ status: "offline"
318
+ }).select("id, name, store_id, status, created_at").single();
319
+ if (error) {
320
+ return {
321
+ status: 500,
322
+ body: {
323
+ error: error.message
324
+ }
325
+ };
326
+ }
327
+ await logNodeEvent(supabase, reg.store_id, node.id, "registered", {
328
+ name: reg.name,
329
+ registered_by: auth.userId
330
+ });
331
+ return {
332
+ status: 201,
333
+ body: {
334
+ success: true,
335
+ node,
336
+ api_key: apiKey,
337
+ // Shown ONCE — user must save this
338
+ message: "Save your API key — it cannot be retrieved later."
339
+ }
340
+ };
341
+ }
342
+
343
+ // ── POST /nodes/heartbeat ─────────────────────────────────────
344
+ // Node API key auth. Updates status + hardware info.
345
+ if (pathname === "/nodes/heartbeat" && method === "POST") {
346
+ const node = await authenticateNode(supabase, auth.rawToken);
347
+ if (!node) {
348
+ return {
349
+ status: 401,
350
+ body: {
351
+ error: "Invalid node API key"
352
+ }
353
+ };
354
+ }
355
+ const hb = body;
356
+ const updates = {
357
+ status: "online",
358
+ last_heartbeat: new Date().toISOString(),
359
+ updated_at: new Date().toISOString()
360
+ };
361
+ if (hb.hardware) updates.hardware = hb.hardware;
362
+ if (hb.capabilities) updates.capabilities = hb.capabilities;
363
+ if (hb.version) updates.version = hb.version;
364
+ await supabase.from("nodes").update(updates).eq("id", node.id);
365
+
366
+ // Sync channel statuses if reported
367
+ if (hb.channels?.length) {
368
+ for (const ch of hb.channels) {
369
+ await supabase.from("channels").update({
370
+ status: ch.status,
371
+ updated_at: new Date().toISOString()
372
+ }).eq("node_id", node.id).eq("type", ch.type);
373
+ }
280
374
  }
281
- // ── POST /nodes/heartbeat ─────────────────────────────────────
282
- // Node API key auth. Updates status + hardware info.
283
- if (pathname === "/nodes/heartbeat" && method === "POST") {
284
- const node = await authenticateNode(supabase, auth.rawToken);
285
- if (!node) {
286
- return { status: 401, body: { error: "Invalid node API key" } };
287
- }
288
- const hb = body;
289
- const updates = {
290
- status: "online",
291
- last_heartbeat: new Date().toISOString(),
292
- updated_at: new Date().toISOString(),
375
+
376
+ // Fetch pending commands for this node
377
+ let pendingCommands = [];
378
+ try {
379
+ const {
380
+ data: cmds
381
+ } = await supabase.from("node_commands").select("id, command, payload").eq("node_id", node.id).eq("status", "pending").order("created_at", {
382
+ ascending: true
383
+ }).limit(10);
384
+ if (cmds?.length) {
385
+ pendingCommands = cmds;
386
+ // Mark them as acknowledged
387
+ const cmdIds = cmds.map(c => c.id);
388
+ await supabase.from("node_commands").update({
389
+ status: "acknowledged",
390
+ acknowledged_at: new Date().toISOString()
391
+ }).in("id", cmdIds);
392
+ }
393
+ } catch {
394
+ // Command delivery is best-effort
395
+ }
396
+ return {
397
+ status: 200,
398
+ body: {
399
+ success: true,
400
+ node_id: node.id,
401
+ commands: pendingCommands
402
+ }
403
+ };
404
+ }
405
+
406
+ // ── GET|POST /nodes ────────────────────────────────────────────
407
+ // User auth. Lists nodes for a store.
408
+ if (pathname === "/nodes" && (method === "GET" || method === "POST")) {
409
+ // store_id from body or query params
410
+ const storeId = body?.store_id || queryParams?.get("store_id");
411
+ if (!storeId) {
412
+ return {
413
+ status: 400,
414
+ body: {
415
+ error: "store_id required"
416
+ }
417
+ };
418
+ }
419
+
420
+ // Verify user has access to the requested store
421
+ if (auth.userId) {
422
+ const hasAccess = await userHasStoreAccess(supabase, auth.userId, storeId);
423
+ if (!hasAccess) {
424
+ return {
425
+ status: 403,
426
+ body: {
427
+ error: "Not authorized to access this store's nodes"
428
+ }
293
429
  };
294
- if (hb.hardware)
295
- updates.hardware = hb.hardware;
296
- if (hb.capabilities)
297
- updates.capabilities = hb.capabilities;
298
- if (hb.version)
299
- updates.version = hb.version;
300
- await supabase.from("nodes").update(updates).eq("id", node.id);
301
- // Sync channel statuses if reported
302
- if (hb.channels?.length) {
303
- for (const ch of hb.channels) {
304
- await supabase
305
- .from("channels")
306
- .update({ status: ch.status, updated_at: new Date().toISOString() })
307
- .eq("node_id", node.id)
308
- .eq("type", ch.type);
309
- }
310
- }
311
- // Fetch pending commands for this node
312
- let pendingCommands = [];
313
- try {
314
- const { data: cmds } = await supabase
315
- .from("node_commands")
316
- .select("id, command, payload")
317
- .eq("node_id", node.id)
318
- .eq("status", "pending")
319
- .order("created_at", { ascending: true })
320
- .limit(10);
321
- if (cmds?.length) {
322
- pendingCommands = cmds;
323
- // Mark them as acknowledged
324
- const cmdIds = cmds.map((c) => c.id);
325
- await supabase
326
- .from("node_commands")
327
- .update({ status: "acknowledged", acknowledged_at: new Date().toISOString() })
328
- .in("id", cmdIds);
329
- }
330
- }
331
- catch {
332
- // Command delivery is best-effort
430
+ }
431
+ } else if (!auth.isServiceRole) {
432
+ return {
433
+ status: 401,
434
+ body: {
435
+ error: "Authentication required"
436
+ }
437
+ };
438
+ }
439
+ const {
440
+ data: nodes,
441
+ error
442
+ } = await supabase.from("nodes").select("id, name, status, hardware, capabilities, version, ip_address, last_heartbeat, created_at").eq("store_id", storeId).order("created_at", {
443
+ ascending: false
444
+ });
445
+ if (error) {
446
+ return {
447
+ status: 500,
448
+ body: {
449
+ error: error.message
333
450
  }
451
+ };
452
+ }
453
+
454
+ // Also fetch channels per node
455
+ const nodeIds = (nodes || []).map(n => n.id);
456
+ const {
457
+ data: channels
458
+ } = nodeIds.length ? await supabase.from("channels").select("id, node_id, type, name, status, stats").in("node_id", nodeIds) : {
459
+ data: []
460
+ };
461
+
462
+ // Attach channels to nodes
463
+ const result = (nodes || []).map(n => ({
464
+ ...n,
465
+ channels: (channels || []).filter(c => c.node_id === n.id)
466
+ }));
467
+ return {
468
+ status: 200,
469
+ body: {
470
+ success: true,
471
+ nodes: result
472
+ }
473
+ };
474
+ }
475
+
476
+ // ── PATCH /nodes/:id ──────────────────────────────────────────
477
+ // User auth. Update node name, capabilities, config.
478
+ const patchNodeMatch = pathname.match(/^\/nodes\/([a-f0-9-]+)$/);
479
+ if (patchNodeMatch && (method === "PUT" || method === "POST") && body?._method === "PATCH") {
480
+ const nodeId = patchNodeMatch[1];
481
+ if (!auth.userId && !auth.isServiceRole) {
482
+ return {
483
+ status: 401,
484
+ body: {
485
+ error: "User authentication required"
486
+ }
487
+ };
488
+ }
489
+
490
+ // Verify ownership
491
+ const {
492
+ data: nodeRow
493
+ } = await supabase.from("nodes").select("id, store_id, name").eq("id", nodeId).single();
494
+ if (!nodeRow) {
495
+ return {
496
+ status: 404,
497
+ body: {
498
+ error: "Node not found"
499
+ }
500
+ };
501
+ }
502
+ if (auth.userId) {
503
+ const hasAccess = await userHasStoreAccess(supabase, auth.userId, nodeRow.store_id);
504
+ if (!hasAccess) {
334
505
  return {
335
- status: 200,
336
- body: { success: true, node_id: node.id, commands: pendingCommands },
506
+ status: 403,
507
+ body: {
508
+ error: "No access to this node"
509
+ }
337
510
  };
511
+ }
338
512
  }
339
- // ── GET|POST /nodes ────────────────────────────────────────────
340
- // User auth. Lists nodes for a store.
341
- if (pathname === "/nodes" && (method === "GET" || method === "POST")) {
342
- // store_id from body or query params
343
- const storeId = body?.store_id || queryParams?.get("store_id");
344
- if (!storeId) {
345
- return { status: 400, body: { error: "store_id required" } };
346
- }
347
- // Verify user has access to the requested store
348
- if (auth.userId) {
349
- const hasAccess = await userHasStoreAccess(supabase, auth.userId, storeId);
350
- if (!hasAccess) {
351
- return { status: 403, body: { error: "Not authorized to access this store's nodes" } };
352
- }
353
- }
354
- else if (!auth.isServiceRole) {
355
- return { status: 401, body: { error: "Authentication required" } };
356
- }
357
- const { data: nodes, error } = await supabase
358
- .from("nodes")
359
- .select("id, name, status, hardware, capabilities, version, ip_address, last_heartbeat, created_at")
360
- .eq("store_id", storeId)
361
- .order("created_at", { ascending: false });
362
- if (error) {
363
- return { status: 500, body: { error: error.message } };
364
- }
365
- // Also fetch channels per node
366
- const nodeIds = (nodes || []).map((n) => n.id);
367
- const { data: channels } = nodeIds.length
368
- ? await supabase
369
- .from("channels")
370
- .select("id, node_id, type, name, status, stats")
371
- .in("node_id", nodeIds)
372
- : { data: [] };
373
- // Attach channels to nodes
374
- const result = (nodes || []).map((n) => ({
375
- ...n,
376
- channels: (channels || []).filter((c) => c.node_id === n.id),
377
- }));
378
- return { status: 200, body: { success: true, nodes: result } };
379
- }
380
- // ── PATCH /nodes/:id ──────────────────────────────────────────
381
- // User auth. Update node name, capabilities, config.
382
- const patchNodeMatch = pathname.match(/^\/nodes\/([a-f0-9-]+)$/);
383
- if (patchNodeMatch && (method === "PUT" || method === "POST") && body?._method === "PATCH") {
384
- const nodeId = patchNodeMatch[1];
385
- if (!auth.userId && !auth.isServiceRole) {
386
- return { status: 401, body: { error: "User authentication required" } };
387
- }
388
- // Verify ownership
389
- const { data: nodeRow } = await supabase
390
- .from("nodes")
391
- .select("id, store_id, name")
392
- .eq("id", nodeId)
393
- .single();
394
- if (!nodeRow) {
395
- return { status: 404, body: { error: "Node not found" } };
396
- }
397
- if (auth.userId) {
398
- const hasAccess = await userHasStoreAccess(supabase, auth.userId, nodeRow.store_id);
399
- if (!hasAccess) {
400
- return { status: 403, body: { error: "No access to this node" } };
401
- }
402
- }
403
- const nodeUpdates = { updated_at: new Date().toISOString() };
404
- if (body.name !== undefined)
405
- nodeUpdates.name = body.name;
406
- if (body.capabilities !== undefined)
407
- nodeUpdates.capabilities = body.capabilities;
408
- if (body.config !== undefined)
409
- nodeUpdates.config = body.config;
410
- const { data: updated, error: updateErr } = await supabase
411
- .from("nodes")
412
- .update(nodeUpdates)
413
- .eq("id", nodeId)
414
- .select("id, name, status, capabilities, config, version, hardware, last_heartbeat, updated_at")
415
- .single();
416
- if (updateErr) {
417
- return { status: 500, body: { error: updateErr.message } };
418
- }
419
- return { status: 200, body: { success: true, node: updated } };
420
- }
421
- // ── POST /nodes/:id/commands ────────────────────────────────
422
- // User auth. Queue a command for a node.
423
- const commandMatch = pathname.match(/^\/nodes\/([a-f0-9-]+)\/commands$/);
424
- if (commandMatch && method === "POST") {
425
- const nodeId = commandMatch[1];
426
- if (!auth.userId && !auth.isServiceRole) {
427
- return { status: 401, body: { error: "User authentication required" } };
428
- }
429
- if (!body)
430
- return { status: 400, body: { error: "Request body required" } };
431
- const command = body.command;
432
- const payload = body.payload || {};
433
- if (!command) {
434
- return { status: 400, body: { error: "command is required" } };
435
- }
436
- const validCommands = ["restart", "shutdown", "rotate_key", "pause_all_channels", "resume_all_channels", "update"];
437
- if (!validCommands.includes(command)) {
438
- return { status: 400, body: { error: `Invalid command. Valid: ${validCommands.join(", ")}` } };
439
- }
440
- // Verify node exists and get store_id
441
- const { data: targetNode } = await supabase
442
- .from("nodes")
443
- .select("id, store_id")
444
- .eq("id", nodeId)
445
- .single();
446
- if (!targetNode) {
447
- return { status: 404, body: { error: "Node not found" } };
448
- }
449
- if (auth.userId) {
450
- const hasAccess = await userHasStoreAccess(supabase, auth.userId, targetNode.store_id);
451
- if (!hasAccess) {
452
- return { status: 403, body: { error: "No access to this node" } };
453
- }
454
- }
455
- const { data: cmd, error: cmdErr } = await supabase
456
- .from("node_commands")
457
- .insert({
458
- node_id: nodeId,
459
- store_id: targetNode.store_id,
460
- command,
461
- payload,
462
- status: "pending",
463
- })
464
- .select("id, command, status, created_at")
465
- .single();
466
- if (cmdErr) {
467
- return { status: 500, body: { error: cmdErr.message } };
468
- }
469
- await logNodeEvent(supabase, targetNode.store_id, nodeId, "command_queued", {
470
- command,
471
- command_id: cmd.id,
472
- queued_by: auth.userId,
473
- });
474
- return { status: 201, body: { success: true, command: cmd } };
475
- }
476
- // ── POST /nodes/:id/rotate-key ─────────────────────────────
477
- // User auth. Generate a new API key for a node.
478
- const rotateMatch = pathname.match(/^\/nodes\/([a-f0-9-]+)\/rotate-key$/);
479
- if (rotateMatch && method === "POST") {
480
- const nodeId = rotateMatch[1];
481
- if (!auth.userId && !auth.isServiceRole) {
482
- return { status: 401, body: { error: "User authentication required" } };
483
- }
484
- // Verify ownership
485
- const { data: rotateNode } = await supabase
486
- .from("nodes")
487
- .select("id, store_id, name")
488
- .eq("id", nodeId)
489
- .single();
490
- if (!rotateNode) {
491
- return { status: 404, body: { error: "Node not found" } };
492
- }
493
- if (auth.userId) {
494
- const hasAccess = await userHasStoreAccess(supabase, auth.userId, rotateNode.store_id);
495
- if (!hasAccess) {
496
- return { status: 403, body: { error: "No access to this node" } };
497
- }
498
- }
499
- const newApiKey = generateNodeApiKey();
500
- const newHash = hashApiKey(newApiKey);
501
- const { error: rotateErr } = await supabase
502
- .from("nodes")
503
- .update({ api_key_hash: newHash, updated_at: new Date().toISOString() })
504
- .eq("id", nodeId);
505
- if (rotateErr) {
506
- return { status: 500, body: { error: rotateErr.message } };
507
- }
508
- // Invalidate auth cache for old hash
509
- for (const [hash, cached] of nodeAuthCache) {
510
- if (cached.node.id === nodeId) {
511
- nodeAuthCache.delete(hash);
512
- }
513
- }
514
- await logNodeEvent(supabase, rotateNode.store_id, nodeId, "key_rotated", {
515
- rotated_by: auth.userId,
516
- });
513
+ const nodeUpdates = {
514
+ updated_at: new Date().toISOString()
515
+ };
516
+ if (body.name !== undefined) nodeUpdates.name = body.name;
517
+ if (body.capabilities !== undefined) nodeUpdates.capabilities = body.capabilities;
518
+ if (body.config !== undefined) nodeUpdates.config = body.config;
519
+ const {
520
+ data: updated,
521
+ error: updateErr
522
+ } = await supabase.from("nodes").update(nodeUpdates).eq("id", nodeId).select("id, name, status, capabilities, config, version, hardware, last_heartbeat, updated_at").single();
523
+ if (updateErr) {
524
+ return {
525
+ status: 500,
526
+ body: {
527
+ error: updateErr.message
528
+ }
529
+ };
530
+ }
531
+ return {
532
+ status: 200,
533
+ body: {
534
+ success: true,
535
+ node: updated
536
+ }
537
+ };
538
+ }
539
+
540
+ // ── POST /nodes/:id/commands ────────────────────────────────
541
+ // User auth. Queue a command for a node.
542
+ const commandMatch = pathname.match(/^\/nodes\/([a-f0-9-]+)\/commands$/);
543
+ if (commandMatch && method === "POST") {
544
+ const nodeId = commandMatch[1];
545
+ if (!auth.userId && !auth.isServiceRole) {
546
+ return {
547
+ status: 401,
548
+ body: {
549
+ error: "User authentication required"
550
+ }
551
+ };
552
+ }
553
+ if (!body) return {
554
+ status: 400,
555
+ body: {
556
+ error: "Request body required"
557
+ }
558
+ };
559
+ const command = body.command;
560
+ const payload = body.payload || {};
561
+ if (!command) {
562
+ return {
563
+ status: 400,
564
+ body: {
565
+ error: "command is required"
566
+ }
567
+ };
568
+ }
569
+ const validCommands = ["restart", "shutdown", "rotate_key", "pause_all_channels", "resume_all_channels", "update"];
570
+ if (!validCommands.includes(command)) {
571
+ return {
572
+ status: 400,
573
+ body: {
574
+ error: `Invalid command. Valid: ${validCommands.join(", ")}`
575
+ }
576
+ };
577
+ }
578
+
579
+ // Verify node exists and get store_id
580
+ const {
581
+ data: targetNode
582
+ } = await supabase.from("nodes").select("id, store_id").eq("id", nodeId).single();
583
+ if (!targetNode) {
584
+ return {
585
+ status: 404,
586
+ body: {
587
+ error: "Node not found"
588
+ }
589
+ };
590
+ }
591
+ if (auth.userId) {
592
+ const hasAccess = await userHasStoreAccess(supabase, auth.userId, targetNode.store_id);
593
+ if (!hasAccess) {
517
594
  return {
518
- status: 200,
519
- body: {
520
- success: true,
521
- api_key: newApiKey,
522
- message: "Save your new API key — it cannot be retrieved later. The old key is now invalid.",
523
- },
595
+ status: 403,
596
+ body: {
597
+ error: "No access to this node"
598
+ }
524
599
  };
600
+ }
525
601
  }
526
- // ── DELETE /nodes/:id ─────────────────────────────────────────
527
- // User auth. Deletes a node and all its channels.
528
- const deleteMatch = pathname.match(/^\/nodes\/([a-f0-9-]+)$/);
529
- if (deleteMatch && method === "DELETE") {
530
- const nodeId = deleteMatch[1];
531
- // Verify ownership
532
- const { data: node } = await supabase
533
- .from("nodes")
534
- .select("id, store_id, name")
535
- .eq("id", nodeId)
536
- .single();
537
- if (!node) {
538
- return { status: 404, body: { error: "Node not found" } };
539
- }
540
- if (auth.userId) {
541
- const hasAccess = await userHasStoreAccess(supabase, auth.userId, node.store_id);
542
- if (!hasAccess) {
543
- return { status: 403, body: { error: "No access to this node" } };
544
- }
545
- }
546
- await logNodeEvent(supabase, node.store_id, nodeId, "deleted", {
547
- name: node.name,
548
- deleted_by: auth.userId,
549
- });
550
- const { error } = await supabase.from("nodes").delete().eq("id", nodeId);
551
- if (error) {
552
- return { status: 500, body: { error: error.message } };
553
- }
554
- return { status: 200, body: { success: true, deleted: nodeId } };
555
- }
556
- // ── POST /channels ───────────────────────────────────────────
557
- // User auth OR node API key. Registers a channel on a node.
558
- if (pathname === "/channels" && method === "POST") {
559
- if (!body)
560
- return { status: 400, body: { error: "Request body required" } };
561
- const reg = body;
562
- if (!reg.store_id || !reg.node_id || !reg.type || !reg.name) {
563
- return { status: 400, body: { error: "store_id, node_id, type, and name required" } };
564
- }
565
- // Verify node exists and belongs to store
566
- const { data: node } = await supabase
567
- .from("nodes")
568
- .select("id, store_id")
569
- .eq("id", reg.node_id)
570
- .eq("store_id", reg.store_id)
571
- .single();
572
- if (!node) {
573
- return { status: 404, body: { error: "Node not found in this store" } };
574
- }
575
- // Check channel-per-node limit against store plan
576
- const { data: chanPlanRow } = await supabase
577
- .from("store_plans")
578
- .select("plan, limits")
579
- .eq("store_id", reg.store_id)
580
- .single();
581
- const chanPlanLimits = chanPlanRow?.limits;
582
- const channelsPerNode = chanPlanLimits?.channels_per_node ?? 2; // free plan default
583
- const { count: existingChannels } = await supabase
584
- .from("channels")
585
- .select("id", { count: "exact", head: true })
586
- .eq("node_id", reg.node_id);
587
- if ((existingChannels || 0) >= channelsPerNode) {
588
- return { status: 429, body: { error: `Channel limit reached (${existingChannels}/${channelsPerNode} per node on ${chanPlanRow?.plan || "free"} plan)` } };
589
- }
590
- const { data: channel, error } = await supabase
591
- .from("channels")
592
- .insert({
593
- store_id: reg.store_id,
594
- node_id: reg.node_id,
595
- type: reg.type,
596
- name: reg.name,
597
- config: reg.config || {},
598
- agent_id: reg.agent_id || null,
599
- status: "inactive",
600
- })
601
- .select("id, type, name, status, created_at")
602
- .single();
603
- if (error) {
604
- return { status: 500, body: { error: error.message } };
605
- }
606
- await logNodeEvent(supabase, reg.store_id, reg.node_id, "channel_added", {
607
- channel_id: channel.id,
608
- type: reg.type,
609
- name: reg.name,
610
- });
611
- return { status: 201, body: { success: true, channel } };
612
- }
613
- // ── GET|POST /channels/list ──────────────────────────────────
614
- // User auth. Lists channels for a store.
615
- if (pathname === "/channels/list" && (method === "GET" || method === "POST")) {
616
- const storeId = body?.store_id || queryParams?.get("store_id");
617
- if (!storeId) {
618
- return { status: 400, body: { error: "store_id required" } };
619
- }
620
- // Verify user has access to the requested store
621
- if (auth.userId) {
622
- const hasAccess = await userHasStoreAccess(supabase, auth.userId, storeId);
623
- if (!hasAccess) {
624
- return { status: 403, body: { error: "Not authorized to access this store's channels" } };
625
- }
626
- }
627
- else if (!auth.isServiceRole) {
628
- return { status: 401, body: { error: "Authentication required" } };
629
- }
630
- const { data: channels, error } = await supabase
631
- .from("channels")
632
- .select(`
602
+ const {
603
+ data: cmd,
604
+ error: cmdErr
605
+ } = await supabase.from("node_commands").insert({
606
+ node_id: nodeId,
607
+ store_id: targetNode.store_id,
608
+ command,
609
+ payload,
610
+ status: "pending"
611
+ }).select("id, command, status, created_at").single();
612
+ if (cmdErr) {
613
+ return {
614
+ status: 500,
615
+ body: {
616
+ error: cmdErr.message
617
+ }
618
+ };
619
+ }
620
+ await logNodeEvent(supabase, targetNode.store_id, nodeId, "command_queued", {
621
+ command,
622
+ command_id: cmd.id,
623
+ queued_by: auth.userId
624
+ });
625
+ return {
626
+ status: 201,
627
+ body: {
628
+ success: true,
629
+ command: cmd
630
+ }
631
+ };
632
+ }
633
+
634
+ // ── POST /nodes/:id/rotate-key ─────────────────────────────
635
+ // User auth. Generate a new API key for a node.
636
+ const rotateMatch = pathname.match(/^\/nodes\/([a-f0-9-]+)\/rotate-key$/);
637
+ if (rotateMatch && method === "POST") {
638
+ const nodeId = rotateMatch[1];
639
+ if (!auth.userId && !auth.isServiceRole) {
640
+ return {
641
+ status: 401,
642
+ body: {
643
+ error: "User authentication required"
644
+ }
645
+ };
646
+ }
647
+
648
+ // Verify ownership
649
+ const {
650
+ data: rotateNode
651
+ } = await supabase.from("nodes").select("id, store_id, name").eq("id", nodeId).single();
652
+ if (!rotateNode) {
653
+ return {
654
+ status: 404,
655
+ body: {
656
+ error: "Node not found"
657
+ }
658
+ };
659
+ }
660
+ if (auth.userId) {
661
+ const hasAccess = await userHasStoreAccess(supabase, auth.userId, rotateNode.store_id);
662
+ if (!hasAccess) {
663
+ return {
664
+ status: 403,
665
+ body: {
666
+ error: "No access to this node"
667
+ }
668
+ };
669
+ }
670
+ }
671
+ const newApiKey = generateNodeApiKey();
672
+ const newHash = hashApiKey(newApiKey);
673
+ const {
674
+ error: rotateErr
675
+ } = await supabase.from("nodes").update({
676
+ api_key_hash: newHash,
677
+ updated_at: new Date().toISOString()
678
+ }).eq("id", nodeId);
679
+ if (rotateErr) {
680
+ return {
681
+ status: 500,
682
+ body: {
683
+ error: rotateErr.message
684
+ }
685
+ };
686
+ }
687
+
688
+ // Invalidate auth cache for old hash
689
+ for (const [hash, cached] of nodeAuthCache) {
690
+ if (cached.node.id === nodeId) {
691
+ nodeAuthCache.delete(hash);
692
+ }
693
+ }
694
+ await logNodeEvent(supabase, rotateNode.store_id, nodeId, "key_rotated", {
695
+ rotated_by: auth.userId
696
+ });
697
+ return {
698
+ status: 200,
699
+ body: {
700
+ success: true,
701
+ api_key: newApiKey,
702
+ message: "Save your new API key — it cannot be retrieved later. The old key is now invalid."
703
+ }
704
+ };
705
+ }
706
+
707
+ // ── DELETE /nodes/:id ─────────────────────────────────────────
708
+ // User auth. Deletes a node and all its channels.
709
+ const deleteMatch = pathname.match(/^\/nodes\/([a-f0-9-]+)$/);
710
+ if (deleteMatch && method === "DELETE") {
711
+ const nodeId = deleteMatch[1];
712
+
713
+ // Verify ownership
714
+ const {
715
+ data: node
716
+ } = await supabase.from("nodes").select("id, store_id, name").eq("id", nodeId).single();
717
+ if (!node) {
718
+ return {
719
+ status: 404,
720
+ body: {
721
+ error: "Node not found"
722
+ }
723
+ };
724
+ }
725
+ if (auth.userId) {
726
+ const hasAccess = await userHasStoreAccess(supabase, auth.userId, node.store_id);
727
+ if (!hasAccess) {
728
+ return {
729
+ status: 403,
730
+ body: {
731
+ error: "No access to this node"
732
+ }
733
+ };
734
+ }
735
+ }
736
+ await logNodeEvent(supabase, node.store_id, nodeId, "deleted", {
737
+ name: node.name,
738
+ deleted_by: auth.userId
739
+ });
740
+ const {
741
+ error
742
+ } = await supabase.from("nodes").delete().eq("id", nodeId);
743
+ if (error) {
744
+ return {
745
+ status: 500,
746
+ body: {
747
+ error: error.message
748
+ }
749
+ };
750
+ }
751
+ return {
752
+ status: 200,
753
+ body: {
754
+ success: true,
755
+ deleted: nodeId
756
+ }
757
+ };
758
+ }
759
+
760
+ // ── POST /channels ───────────────────────────────────────────
761
+ // User auth OR node API key. Registers a channel on a node.
762
+ if (pathname === "/channels" && method === "POST") {
763
+ if (!body) return {
764
+ status: 400,
765
+ body: {
766
+ error: "Request body required"
767
+ }
768
+ };
769
+ const reg = body;
770
+ if (!reg.store_id || !reg.node_id || !reg.type || !reg.name) {
771
+ return {
772
+ status: 400,
773
+ body: {
774
+ error: "store_id, node_id, type, and name required"
775
+ }
776
+ };
777
+ }
778
+
779
+ // Verify node exists and belongs to store
780
+ const {
781
+ data: node
782
+ } = await supabase.from("nodes").select("id, store_id").eq("id", reg.node_id).eq("store_id", reg.store_id).single();
783
+ if (!node) {
784
+ return {
785
+ status: 404,
786
+ body: {
787
+ error: "Node not found in this store"
788
+ }
789
+ };
790
+ }
791
+
792
+ // Check channel-per-node limit against store plan
793
+ const {
794
+ data: chanPlanRow
795
+ } = await supabase.from("store_plans").select("plan, limits").eq("store_id", reg.store_id).single();
796
+ const chanPlanLimits = chanPlanRow?.limits;
797
+ const channelsPerNode = chanPlanLimits?.channels_per_node ?? 2; // free plan default
798
+
799
+ const {
800
+ count: existingChannels
801
+ } = await supabase.from("channels").select("id", {
802
+ count: "exact",
803
+ head: true
804
+ }).eq("node_id", reg.node_id);
805
+ if ((existingChannels || 0) >= channelsPerNode) {
806
+ return {
807
+ status: 429,
808
+ body: {
809
+ error: `Channel limit reached (${existingChannels}/${channelsPerNode} per node on ${chanPlanRow?.plan || "free"} plan)`
810
+ }
811
+ };
812
+ }
813
+ const {
814
+ data: channel,
815
+ error
816
+ } = await supabase.from("channels").insert({
817
+ store_id: reg.store_id,
818
+ node_id: reg.node_id,
819
+ type: reg.type,
820
+ name: reg.name,
821
+ config: reg.config || {},
822
+ agent_id: reg.agent_id || null,
823
+ status: "inactive"
824
+ }).select("id, type, name, status, created_at").single();
825
+ if (error) {
826
+ return {
827
+ status: 500,
828
+ body: {
829
+ error: error.message
830
+ }
831
+ };
832
+ }
833
+ await logNodeEvent(supabase, reg.store_id, reg.node_id, "channel_added", {
834
+ channel_id: channel.id,
835
+ type: reg.type,
836
+ name: reg.name
837
+ });
838
+ return {
839
+ status: 201,
840
+ body: {
841
+ success: true,
842
+ channel
843
+ }
844
+ };
845
+ }
846
+
847
+ // ── GET|POST /channels/list ──────────────────────────────────
848
+ // User auth. Lists channels for a store.
849
+ if (pathname === "/channels/list" && (method === "GET" || method === "POST")) {
850
+ const storeId = body?.store_id || queryParams?.get("store_id");
851
+ if (!storeId) {
852
+ return {
853
+ status: 400,
854
+ body: {
855
+ error: "store_id required"
856
+ }
857
+ };
858
+ }
859
+
860
+ // Verify user has access to the requested store
861
+ if (auth.userId) {
862
+ const hasAccess = await userHasStoreAccess(supabase, auth.userId, storeId);
863
+ if (!hasAccess) {
864
+ return {
865
+ status: 403,
866
+ body: {
867
+ error: "Not authorized to access this store's channels"
868
+ }
869
+ };
870
+ }
871
+ } else if (!auth.isServiceRole) {
872
+ return {
873
+ status: 401,
874
+ body: {
875
+ error: "Authentication required"
876
+ }
877
+ };
878
+ }
879
+ const {
880
+ data: channels,
881
+ error
882
+ } = await supabase.from("channels").select(`
633
883
  id, node_id, type, name, status, config, agent_id, stats,
634
884
  error_message, created_at, updated_at,
635
885
  nodes!inner(id, name, status)
636
- `)
637
- .eq("store_id", storeId)
638
- .order("created_at", { ascending: false });
639
- if (error) {
640
- return { status: 500, body: { error: error.message } };
641
- }
642
- return { status: 200, body: { success: true, channels: channels || [] } };
643
- }
644
- // ── PATCH /channels/:id ────────────────────────────────────────
645
- // User auth. Update channel config/agent assignment.
646
- const channelPatchMatch = pathname.match(/^\/channels\/([a-f0-9-]+)$/);
647
- if (channelPatchMatch && (method === "PUT" || method === "POST") && body?._method === "PATCH") {
648
- const channelId = channelPatchMatch[1];
649
- // Fetch the channel first to verify ownership
650
- const { data: existingChannel } = await supabase
651
- .from("channels")
652
- .select("id, store_id")
653
- .eq("id", channelId)
654
- .single();
655
- if (!existingChannel) {
656
- return { status: 404, body: { error: "Channel not found" } };
657
- }
658
- // Verify the user has access to this channel's store
659
- if (auth.userId) {
660
- const hasAccess = await userHasStoreAccess(supabase, auth.userId, existingChannel.store_id);
661
- if (!hasAccess) {
662
- return { status: 403, body: { error: "Not authorized to modify this channel" } };
663
- }
886
+ `).eq("store_id", storeId).order("created_at", {
887
+ ascending: false
888
+ });
889
+ if (error) {
890
+ return {
891
+ status: 500,
892
+ body: {
893
+ error: error.message
664
894
  }
665
- else if (!auth.isServiceRole) {
666
- return { status: 401, body: { error: "Authentication required" } };
667
- }
668
- const updates = {};
669
- if (body.agent_id !== undefined)
670
- updates.agent_id = body.agent_id;
671
- if (body.config !== undefined)
672
- updates.config = body.config;
673
- if (body.name !== undefined)
674
- updates.name = body.name;
675
- if (body.status !== undefined)
676
- updates.status = body.status;
677
- updates.updated_at = new Date().toISOString();
678
- const { data: channel, error } = await supabase
679
- .from("channels")
680
- .update(updates)
681
- .eq("id", channelId)
682
- .select("id, type, name, status, agent_id, config, updated_at")
683
- .single();
684
- if (error) {
685
- return { status: 500, body: { error: error.message } };
686
- }
687
- return { status: 200, body: { success: true, channel } };
688
- }
689
- // ── POST /channels/:id/messages ──────────────────────────────
690
- // Node API key auth. Ingests a message from a channel.
691
- // If direction=inbound and channel has agent_id, auto-invokes agent.
692
- const messageMatch = pathname.match(/^\/channels\/([a-f0-9-]+)\/messages$/);
693
- if (messageMatch && method === "POST") {
694
- const channelId = messageMatch[1];
695
- if (!body)
696
- return { status: 400, body: { error: "Request body required" } };
697
- // Authenticate node (or service role for admin access)
698
- const node = await authenticateNode(supabase, auth.rawToken);
699
- if (!node && !auth.isServiceRole) {
700
- return { status: 401, body: { error: "Invalid node API key" } };
701
- }
702
- // Verify channel exists (and belongs to this node if node auth)
703
- let channelQuery = supabase
704
- .from("channels")
705
- .select("id, store_id, node_id, agent_id, config, type, name")
706
- .eq("id", channelId);
707
- if (node)
708
- channelQuery = channelQuery.eq("node_id", node.id);
709
- const { data: channel } = await channelQuery.single();
710
- if (!channel) {
711
- return { status: 404, body: { error: "Channel not found" } };
712
- }
713
- const msg = body;
714
- const direction = msg.direction || "inbound";
715
- const senderId = msg.sender_id || "unknown";
716
- // Check plan message limits before accepting
717
- const limitCheck = await checkPlanLimits(supabase, channel.store_id, "message");
718
- if (!limitCheck.allowed) {
719
- return { status: 429, body: { error: limitCheck.reason || "Message limit exceeded" } };
720
- }
721
- // Resolve conversation ID (reuse if < 30 min gap from same sender)
722
- // Channel type is dynamic — read from channel.config.type or channel row's type column
723
- const channelType = channel.config?.type || channel.type || "unknown";
724
- const channelName = channel.name || "";
725
- let conversationId = msg.conversation_id;
726
- let senderContext;
727
- if (!conversationId && direction === "inbound") {
728
- const ctx = await resolveConversation(supabase, channelId, senderId, channel.store_id, channelType, msg.sender_name);
729
- conversationId = ctx.conversationId;
730
- senderContext = {
731
- senderId,
732
- senderName: msg.sender_name || undefined,
733
- customerId: ctx.customerId,
734
- customerName: ctx.customerName,
735
- channelType,
736
- channelId,
737
- channelName,
738
- };
739
- }
740
- // Insert inbound message
741
- const { data: message, error } = await supabase
742
- .from("channel_messages")
743
- .insert({
744
- store_id: channel.store_id,
895
+ };
896
+ }
897
+ return {
898
+ status: 200,
899
+ body: {
900
+ success: true,
901
+ channels: channels || []
902
+ }
903
+ };
904
+ }
905
+
906
+ // ── PATCH /channels/:id ────────────────────────────────────────
907
+ // User auth. Update channel config/agent assignment.
908
+ const channelPatchMatch = pathname.match(/^\/channels\/([a-f0-9-]+)$/);
909
+ if (channelPatchMatch && (method === "PUT" || method === "POST") && body?._method === "PATCH") {
910
+ const channelId = channelPatchMatch[1];
911
+
912
+ // Fetch the channel first to verify ownership
913
+ const {
914
+ data: existingChannel
915
+ } = await supabase.from("channels").select("id, store_id").eq("id", channelId).single();
916
+ if (!existingChannel) {
917
+ return {
918
+ status: 404,
919
+ body: {
920
+ error: "Channel not found"
921
+ }
922
+ };
923
+ }
924
+
925
+ // Verify the user has access to this channel's store
926
+ if (auth.userId) {
927
+ const hasAccess = await userHasStoreAccess(supabase, auth.userId, existingChannel.store_id);
928
+ if (!hasAccess) {
929
+ return {
930
+ status: 403,
931
+ body: {
932
+ error: "Not authorized to modify this channel"
933
+ }
934
+ };
935
+ }
936
+ } else if (!auth.isServiceRole) {
937
+ return {
938
+ status: 401,
939
+ body: {
940
+ error: "Authentication required"
941
+ }
942
+ };
943
+ }
944
+ const updates = {};
945
+ if (body.agent_id !== undefined) updates.agent_id = body.agent_id;
946
+ if (body.config !== undefined) updates.config = body.config;
947
+ if (body.name !== undefined) updates.name = body.name;
948
+ if (body.status !== undefined) updates.status = body.status;
949
+ updates.updated_at = new Date().toISOString();
950
+ const {
951
+ data: channel,
952
+ error
953
+ } = await supabase.from("channels").update(updates).eq("id", channelId).select("id, type, name, status, agent_id, config, updated_at").single();
954
+ if (error) {
955
+ return {
956
+ status: 500,
957
+ body: {
958
+ error: error.message
959
+ }
960
+ };
961
+ }
962
+ return {
963
+ status: 200,
964
+ body: {
965
+ success: true,
966
+ channel
967
+ }
968
+ };
969
+ }
970
+
971
+ // ── POST /channels/:id/messages ──────────────────────────────
972
+ // Node API key auth. Ingests a message from a channel.
973
+ // If direction=inbound and channel has agent_id, auto-invokes agent.
974
+ const messageMatch = pathname.match(/^\/channels\/([a-f0-9-]+)\/messages$/);
975
+ if (messageMatch && method === "POST") {
976
+ const channelId = messageMatch[1];
977
+ if (!body) return {
978
+ status: 400,
979
+ body: {
980
+ error: "Request body required"
981
+ }
982
+ };
983
+
984
+ // Authenticate node (or service role for admin access)
985
+ const node = await authenticateNode(supabase, auth.rawToken);
986
+ if (!node && !auth.isServiceRole) {
987
+ return {
988
+ status: 401,
989
+ body: {
990
+ error: "Invalid node API key"
991
+ }
992
+ };
993
+ }
994
+
995
+ // Verify channel exists (and belongs to this node if node auth)
996
+ let channelQuery = supabase.from("channels").select("id, store_id, node_id, agent_id, config, type, name").eq("id", channelId);
997
+ if (node) channelQuery = channelQuery.eq("node_id", node.id);
998
+ const {
999
+ data: channel
1000
+ } = await channelQuery.single();
1001
+ if (!channel) {
1002
+ return {
1003
+ status: 404,
1004
+ body: {
1005
+ error: "Channel not found"
1006
+ }
1007
+ };
1008
+ }
1009
+ const msg = body;
1010
+ const direction = msg.direction || "inbound";
1011
+ const senderId = msg.sender_id || "unknown";
1012
+
1013
+ // Check plan message limits before accepting
1014
+ const limitCheck = await checkPlanLimits(supabase, channel.store_id, "message");
1015
+ if (!limitCheck.allowed) {
1016
+ return {
1017
+ status: 429,
1018
+ body: {
1019
+ error: limitCheck.reason || "Message limit exceeded"
1020
+ }
1021
+ };
1022
+ }
1023
+
1024
+ // Resolve conversation ID (reuse if < 30 min gap from same sender)
1025
+ // Channel type is dynamic — read from channel.config.type or channel row's type column
1026
+ const channelType = channel.config?.type || channel.type || "unknown";
1027
+ const channelName = channel.name || "";
1028
+ let conversationId = msg.conversation_id;
1029
+ let senderContext;
1030
+ if (!conversationId && direction === "inbound") {
1031
+ const ctx = await resolveConversation(supabase, channelId, senderId, channel.store_id, channelType, msg.sender_name);
1032
+ conversationId = ctx.conversationId;
1033
+ senderContext = {
1034
+ senderId,
1035
+ senderName: msg.sender_name || undefined,
1036
+ customerId: ctx.customerId,
1037
+ customerName: ctx.customerName,
1038
+ channelType,
1039
+ channelId,
1040
+ channelName
1041
+ };
1042
+ }
1043
+
1044
+ // Insert inbound message
1045
+ const {
1046
+ data: message,
1047
+ error
1048
+ } = await supabase.from("channel_messages").insert({
1049
+ store_id: channel.store_id,
1050
+ channel_id: channelId,
1051
+ direction,
1052
+ sender_id: senderId,
1053
+ sender_name: msg.sender_name,
1054
+ content: msg.content,
1055
+ content_type: msg.content_type || "text",
1056
+ metadata: msg.metadata || {},
1057
+ agent_id: channel.agent_id,
1058
+ conversation_id: conversationId
1059
+ }).select("id, direction, content, conversation_id, created_at").single();
1060
+ if (error) {
1061
+ return {
1062
+ status: 500,
1063
+ body: {
1064
+ error: error.message
1065
+ }
1066
+ };
1067
+ }
1068
+
1069
+ // Update channel stats (best-effort)
1070
+ try {
1071
+ await supabase.rpc("increment_channel_stats", {
1072
+ p_channel_id: channelId
1073
+ });
1074
+ } catch {
1075
+ // Stats function may not exist yet — that's fine
1076
+ }
1077
+
1078
+ // Telemetry → ClickHouse for inbound message
1079
+ if (direction === "inbound") {
1080
+ try {
1081
+ queueSpan(auditRowToSpan({
1082
+ action: "node.message.inbound",
1083
+ severity: "info",
1084
+ store_id: channel.store_id,
1085
+ resource_type: "whale_node",
1086
+ resource_id: channelId,
1087
+ source: "whale-node",
1088
+ conversation_id: conversationId || message.conversation_id || null,
1089
+ trace_id: randomUUID(),
1090
+ span_kind: "INTERNAL",
1091
+ service_name: "whale-node",
1092
+ status_code: "OK",
1093
+ start_time: new Date().toISOString(),
1094
+ end_time: new Date().toISOString(),
1095
+ details: {
745
1096
  channel_id: channelId,
746
- direction,
1097
+ channel_type: channelType,
747
1098
  sender_id: senderId,
748
- sender_name: msg.sender_name,
749
- content: msg.content,
750
- content_type: msg.content_type || "text",
751
- metadata: msg.metadata || {},
752
- agent_id: channel.agent_id,
753
- conversation_id: conversationId,
754
- })
755
- .select("id, direction, content, conversation_id, created_at")
756
- .single();
757
- if (error) {
758
- return { status: 500, body: { error: error.message } };
759
- }
760
- // Update channel stats (best-effort)
1099
+ sender_name: msg.sender_name || null,
1100
+ node_id: node?.id || null,
1101
+ has_agent: !!channel.agent_id
1102
+ }
1103
+ }));
1104
+ } catch {
1105
+ // Telemetry must never break message flow
1106
+ }
1107
+ }
1108
+
1109
+ // Track message usage (best-effort, non-blocking)
1110
+ incrementUsage(supabase, channel.store_id, direction === "inbound" ? {
1111
+ messages_in: 1
1112
+ } : {
1113
+ messages_out: 1
1114
+ }).catch(err => console.error("[billing] usage increment failed:", err.message));
1115
+
1116
+ // ── Agent auto-invocation ───────────────────────────────────
1117
+ // If inbound message and channel has an agent assigned, invoke it
1118
+ let agentResponse = null;
1119
+ if (direction === "inbound" && channel.agent_id && agentInvoker) {
1120
+ // Check agent invocation limits before calling
1121
+ const agentLimitCheck = await checkPlanLimits(supabase, channel.store_id, "agent_invocation");
1122
+ if (!agentLimitCheck.allowed) {
1123
+ console.warn(`[channel-agent] Agent invocation blocked: ${agentLimitCheck.reason}`);
1124
+ } else {
761
1125
  try {
762
- await supabase.rpc("increment_channel_stats", {
763
- p_channel_id: channelId,
764
- });
765
- }
766
- catch {
767
- // Stats function may not exist yet — that's fine
768
- }
769
- // Telemetry ClickHouse for inbound message
770
- if (direction === "inbound") {
771
- try {
772
- queueSpan(auditRowToSpan({
773
- action: "node.message.inbound",
774
- severity: "info",
775
- store_id: channel.store_id,
776
- resource_type: "whale_node",
777
- resource_id: channelId,
778
- source: "whale-node",
779
- conversation_id: conversationId || message.conversation_id || null,
780
- trace_id: randomUUID(),
781
- span_kind: "INTERNAL",
782
- service_name: "whale-node",
783
- status_code: "OK",
784
- start_time: new Date().toISOString(),
785
- end_time: new Date().toISOString(),
786
- details: {
787
- channel_id: channelId,
788
- channel_type: channelType,
789
- sender_id: senderId,
790
- sender_name: msg.sender_name || null,
791
- node_id: node?.id || null,
792
- has_agent: !!channel.agent_id,
793
- },
794
- }));
795
- }
796
- catch {
797
- // Telemetry must never break message flow
798
- }
799
- }
800
- // Track message usage (best-effort, non-blocking)
801
- incrementUsage(supabase, channel.store_id, direction === "inbound" ? { messages_in: 1 } : { messages_out: 1 }).catch((err) => console.error("[billing] usage increment failed:", err.message));
802
- // ── Agent auto-invocation ───────────────────────────────────
803
- // If inbound message and channel has an agent assigned, invoke it
804
- let agentResponse = null;
805
- if (direction === "inbound" && channel.agent_id && agentInvoker) {
806
- // Check agent invocation limits before calling
807
- const agentLimitCheck = await checkPlanLimits(supabase, channel.store_id, "agent_invocation");
808
- if (!agentLimitCheck.allowed) {
809
- console.warn(`[channel-agent] Agent invocation blocked: ${agentLimitCheck.reason}`);
810
- }
811
- else {
812
- try {
813
- console.log(`[channel-agent] Invoking agent ${channel.agent_id} for channel ${channelId}`);
814
- const result = await agentInvoker(supabase, channel.agent_id, msg.content, channel.store_id, conversationId || message.conversation_id, senderContext);
815
- // Track agent invocation usage
816
- incrementUsage(supabase, channel.store_id, { agent_invocations: 1 })
817
- .catch((err) => console.error("[billing] agent usage increment failed:", err.message));
818
- if (result.success && result.response) {
819
- // Insert agent response as outbound message
820
- const { data: outMsg, error: outErr } = await supabase
821
- .from("channel_messages")
822
- .insert({
823
- store_id: channel.store_id,
824
- channel_id: channelId,
825
- direction: "outbound",
826
- sender_id: "agent",
827
- sender_name: "AI Agent",
828
- content: result.response,
829
- content_type: "text",
830
- metadata: { agent_id: channel.agent_id, auto_response: true },
831
- agent_id: channel.agent_id,
832
- conversation_id: conversationId || message.conversation_id,
833
- })
834
- .select("id, direction, content, conversation_id, created_at")
835
- .single();
836
- if (!outErr && outMsg) {
837
- agentResponse = outMsg;
838
- // Track outbound message usage
839
- incrementUsage(supabase, channel.store_id, { messages_out: 1 })
840
- .catch((err) => console.error("[billing] outbound usage increment failed:", err.message));
841
- console.log(`[channel-agent] Response stored: ${outMsg.id}`);
842
- }
843
- }
844
- else if (result.error) {
845
- console.error(`[channel-agent] Agent error: ${result.error}`);
846
- }
847
- }
848
- catch (err) {
849
- console.error(`[channel-agent] Invocation failed:`, err.message);
850
- }
1126
+ console.log(`[channel-agent] Invoking agent ${channel.agent_id} for channel ${channelId}`);
1127
+ const result = await agentInvoker(supabase, channel.agent_id, msg.content, channel.store_id, conversationId || message.conversation_id, senderContext);
1128
+
1129
+ // Track agent invocation usage
1130
+ incrementUsage(supabase, channel.store_id, {
1131
+ agent_invocations: 1
1132
+ }).catch(err => console.error("[billing] agent usage increment failed:", err.message));
1133
+ if (result.success && result.response) {
1134
+ // Insert agent response as outbound message
1135
+ const {
1136
+ data: outMsg,
1137
+ error: outErr
1138
+ } = await supabase.from("channel_messages").insert({
1139
+ store_id: channel.store_id,
1140
+ channel_id: channelId,
1141
+ direction: "outbound",
1142
+ sender_id: "agent",
1143
+ sender_name: "AI Agent",
1144
+ content: result.response,
1145
+ content_type: "text",
1146
+ metadata: {
1147
+ agent_id: channel.agent_id,
1148
+ auto_response: true
1149
+ },
1150
+ agent_id: channel.agent_id,
1151
+ conversation_id: conversationId || message.conversation_id
1152
+ }).select("id, direction, content, conversation_id, created_at").single();
1153
+ if (!outErr && outMsg) {
1154
+ agentResponse = outMsg;
1155
+ // Track outbound message usage
1156
+ incrementUsage(supabase, channel.store_id, {
1157
+ messages_out: 1
1158
+ }).catch(err => console.error("[billing] outbound usage increment failed:", err.message));
1159
+ console.log(`[channel-agent] Response stored: ${outMsg.id}`);
851
1160
  }
1161
+ } else if (result.error) {
1162
+ console.error(`[channel-agent] Agent error: ${result.error}`);
1163
+ }
1164
+ } catch (err) {
1165
+ console.error(`[channel-agent] Invocation failed:`, err.message);
852
1166
  }
1167
+ }
1168
+ }
1169
+ return {
1170
+ status: 201,
1171
+ body: {
1172
+ success: true,
1173
+ message,
1174
+ agent_response: agentResponse,
1175
+ conversation_id: conversationId || message.conversation_id
1176
+ }
1177
+ };
1178
+ }
1179
+
1180
+ // ── GET /channels/:id/messages ─────────────────────────────────
1181
+ // Node API key auth. Retrieves messages for a channel.
1182
+ // Query params (via body): direction, undelivered, limit, after
1183
+ const getMessagesMatch = pathname.match(/^\/channels\/([a-f0-9-]+)\/messages$/);
1184
+ if (getMessagesMatch && method === "GET") {
1185
+ const channelId = getMessagesMatch[1];
1186
+
1187
+ // Authenticate node (or service role for admin access)
1188
+ const node = await authenticateNode(supabase, auth.rawToken);
1189
+ if (!node && !auth.isServiceRole) {
1190
+ return {
1191
+ status: 401,
1192
+ body: {
1193
+ error: "Invalid node API key"
1194
+ }
1195
+ };
1196
+ }
1197
+
1198
+ // Verify channel exists (and belongs to this node if node auth)
1199
+ let chQuery = supabase.from("channels").select("id, node_id").eq("id", channelId);
1200
+ if (node) chQuery = chQuery.eq("node_id", node.id);
1201
+ const {
1202
+ data: channel
1203
+ } = await chQuery.single();
1204
+ if (!channel) {
1205
+ return {
1206
+ status: 404,
1207
+ body: {
1208
+ error: "Channel not found"
1209
+ }
1210
+ };
1211
+ }
1212
+ let query = supabase.from("channel_messages").select("id, direction, sender_id, sender_name, content, content_type, metadata, conversation_id, delivered_at, created_at").eq("channel_id", channelId);
1213
+
1214
+ // Filter by direction (body or query param)
1215
+ const direction = body?.direction || queryParams?.get("direction");
1216
+ if (direction) {
1217
+ query = query.eq("direction", direction);
1218
+ }
1219
+
1220
+ // Filter undelivered outbound messages (for node polling)
1221
+ const undelivered = body?.undelivered || queryParams?.get("undelivered");
1222
+ if (undelivered === true || undelivered === "true") {
1223
+ query = query.eq("direction", "outbound").is("delivered_at", null);
1224
+ }
1225
+
1226
+ // After a specific timestamp
1227
+ const after = body?.after || queryParams?.get("after");
1228
+ if (after) {
1229
+ query = query.gt("created_at", after);
1230
+ }
1231
+ const rawLimit = body?.limit || queryParams?.get("limit") || 50;
1232
+ const limit = Math.min(Number(rawLimit), 200);
1233
+ query = query.order("created_at", {
1234
+ ascending: true
1235
+ }).limit(limit);
1236
+ const {
1237
+ data: messages,
1238
+ error: msgErr
1239
+ } = await query;
1240
+ if (msgErr) {
1241
+ return {
1242
+ status: 500,
1243
+ body: {
1244
+ error: msgErr.message
1245
+ }
1246
+ };
1247
+ }
1248
+ return {
1249
+ status: 200,
1250
+ body: {
1251
+ success: true,
1252
+ messages: messages || []
1253
+ }
1254
+ };
1255
+ }
1256
+
1257
+ // ── PATCH /channels/:id/messages/:msgId ──────────────────────
1258
+ // Node API key auth. Mark message as delivered.
1259
+ const deliverMatch = pathname.match(/^\/channels\/([a-f0-9-]+)\/messages\/([a-f0-9-]+)$/);
1260
+ if (deliverMatch && (method === "PUT" || method === "POST") && body?._method === "PATCH") {
1261
+ const channelId = deliverMatch[1];
1262
+ const msgId = deliverMatch[2];
1263
+ const node = await authenticateNode(supabase, auth.rawToken);
1264
+ if (!node) {
1265
+ return {
1266
+ status: 401,
1267
+ body: {
1268
+ error: "Invalid node API key"
1269
+ }
1270
+ };
1271
+ }
1272
+ const {
1273
+ error
1274
+ } = await supabase.from("channel_messages").update({
1275
+ delivered_at: body?.delivered_at || new Date().toISOString(),
1276
+ metadata: {
1277
+ ...(body?.metadata || {}),
1278
+ delivered_by_node: node.id
1279
+ }
1280
+ }).eq("id", msgId).eq("channel_id", channelId);
1281
+ if (error) {
1282
+ return {
1283
+ status: 500,
1284
+ body: {
1285
+ error: error.message
1286
+ }
1287
+ };
1288
+ }
1289
+ return {
1290
+ status: 200,
1291
+ body: {
1292
+ success: true,
1293
+ delivered: msgId
1294
+ }
1295
+ };
1296
+ }
1297
+
1298
+ // ── POST /channels/:id/messages/:msgId/delivered ─────────────
1299
+ // Simpler delivery marking endpoint (no _method hack needed)
1300
+ const deliverSimpleMatch = pathname.match(/^\/channels\/([a-f0-9-]+)\/messages\/([a-f0-9-]+)\/delivered$/);
1301
+ if (deliverSimpleMatch && method === "POST") {
1302
+ const channelId = deliverSimpleMatch[1];
1303
+ const msgId = deliverSimpleMatch[2];
1304
+ const node = await authenticateNode(supabase, auth.rawToken);
1305
+ if (!node) {
1306
+ return {
1307
+ status: 401,
1308
+ body: {
1309
+ error: "Invalid node API key"
1310
+ }
1311
+ };
1312
+ }
1313
+ const {
1314
+ error
1315
+ } = await supabase.from("channel_messages").update({
1316
+ delivered_at: new Date().toISOString()
1317
+ }).eq("id", msgId).eq("channel_id", channelId);
1318
+ if (error) {
1319
+ return {
1320
+ status: 500,
1321
+ body: {
1322
+ error: error.message
1323
+ }
1324
+ };
1325
+ }
1326
+ return {
1327
+ status: 200,
1328
+ body: {
1329
+ success: true,
1330
+ delivered: msgId
1331
+ }
1332
+ };
1333
+ }
1334
+
1335
+ // ── GET /nodes/:id/events ────────────────────────────────────
1336
+ // User auth. Lists recent events for a node.
1337
+ const eventsMatch = pathname.match(/^\/nodes\/([a-f0-9-]+)\/events$/);
1338
+ if (eventsMatch && method === "GET") {
1339
+ const nodeId = eventsMatch[1];
1340
+ const limit = body?.limit || 50;
1341
+
1342
+ // Verify user has access to this node's store
1343
+ const {
1344
+ data: node
1345
+ } = await supabase.from("nodes").select("store_id").eq("id", nodeId).single();
1346
+ if (!node) {
1347
+ return {
1348
+ status: 404,
1349
+ body: {
1350
+ error: "Node not found"
1351
+ }
1352
+ };
1353
+ }
1354
+ if (auth.userId) {
1355
+ const hasAccess = await userHasStoreAccess(supabase, auth.userId, node.store_id);
1356
+ if (!hasAccess) {
853
1357
  return {
854
- status: 201,
855
- body: {
856
- success: true,
857
- message,
858
- agent_response: agentResponse,
859
- conversation_id: conversationId || message.conversation_id,
860
- },
1358
+ status: 403,
1359
+ body: {
1360
+ error: "Not authorized to view this node's events"
1361
+ }
861
1362
  };
1363
+ }
1364
+ } else if (!auth.isServiceRole) {
1365
+ return {
1366
+ status: 401,
1367
+ body: {
1368
+ error: "Authentication required"
1369
+ }
1370
+ };
862
1371
  }
863
- // ── GET /channels/:id/messages ─────────────────────────────────
864
- // Node API key auth. Retrieves messages for a channel.
865
- // Query params (via body): direction, undelivered, limit, after
866
- const getMessagesMatch = pathname.match(/^\/channels\/([a-f0-9-]+)\/messages$/);
867
- if (getMessagesMatch && method === "GET") {
868
- const channelId = getMessagesMatch[1];
869
- // Authenticate node (or service role for admin access)
870
- const node = await authenticateNode(supabase, auth.rawToken);
871
- if (!node && !auth.isServiceRole) {
872
- return { status: 401, body: { error: "Invalid node API key" } };
873
- }
874
- // Verify channel exists (and belongs to this node if node auth)
875
- let chQuery = supabase.from("channels").select("id, node_id").eq("id", channelId);
876
- if (node)
877
- chQuery = chQuery.eq("node_id", node.id);
878
- const { data: channel } = await chQuery.single();
879
- if (!channel) {
880
- return { status: 404, body: { error: "Channel not found" } };
881
- }
882
- let query = supabase
883
- .from("channel_messages")
884
- .select("id, direction, sender_id, sender_name, content, content_type, metadata, conversation_id, delivered_at, created_at")
885
- .eq("channel_id", channelId);
886
- // Filter by direction (body or query param)
887
- const direction = body?.direction || queryParams?.get("direction");
888
- if (direction) {
889
- query = query.eq("direction", direction);
890
- }
891
- // Filter undelivered outbound messages (for node polling)
892
- const undelivered = body?.undelivered || queryParams?.get("undelivered");
893
- if (undelivered === true || undelivered === "true") {
894
- query = query.eq("direction", "outbound").is("delivered_at", null);
895
- }
896
- // After a specific timestamp
897
- const after = body?.after || queryParams?.get("after");
898
- if (after) {
899
- query = query.gt("created_at", after);
900
- }
901
- const rawLimit = body?.limit || queryParams?.get("limit") || 50;
902
- const limit = Math.min(Number(rawLimit), 200);
903
- query = query.order("created_at", { ascending: true }).limit(limit);
904
- const { data: messages, error: msgErr } = await query;
905
- if (msgErr) {
906
- return { status: 500, body: { error: msgErr.message } };
907
- }
908
- return { status: 200, body: { success: true, messages: messages || [] } };
909
- }
910
- // ── PATCH /channels/:id/messages/:msgId ──────────────────────
911
- // Node API key auth. Mark message as delivered.
912
- const deliverMatch = pathname.match(/^\/channels\/([a-f0-9-]+)\/messages\/([a-f0-9-]+)$/);
913
- if (deliverMatch && (method === "PUT" || method === "POST") && body?._method === "PATCH") {
914
- const channelId = deliverMatch[1];
915
- const msgId = deliverMatch[2];
916
- const node = await authenticateNode(supabase, auth.rawToken);
917
- if (!node) {
918
- return { status: 401, body: { error: "Invalid node API key" } };
919
- }
920
- const { error } = await supabase
921
- .from("channel_messages")
922
- .update({
923
- delivered_at: body?.delivered_at || new Date().toISOString(),
924
- metadata: { ...(body?.metadata || {}), delivered_by_node: node.id },
925
- })
926
- .eq("id", msgId)
927
- .eq("channel_id", channelId);
928
- if (error) {
929
- return { status: 500, body: { error: error.message } };
930
- }
931
- return { status: 200, body: { success: true, delivered: msgId } };
932
- }
933
- // ── POST /channels/:id/messages/:msgId/delivered ─────────────
934
- // Simpler delivery marking endpoint (no _method hack needed)
935
- const deliverSimpleMatch = pathname.match(/^\/channels\/([a-f0-9-]+)\/messages\/([a-f0-9-]+)\/delivered$/);
936
- if (deliverSimpleMatch && method === "POST") {
937
- const channelId = deliverSimpleMatch[1];
938
- const msgId = deliverSimpleMatch[2];
939
- const node = await authenticateNode(supabase, auth.rawToken);
940
- if (!node) {
941
- return { status: 401, body: { error: "Invalid node API key" } };
942
- }
943
- const { error } = await supabase
944
- .from("channel_messages")
945
- .update({ delivered_at: new Date().toISOString() })
946
- .eq("id", msgId)
947
- .eq("channel_id", channelId);
948
- if (error) {
949
- return { status: 500, body: { error: error.message } };
950
- }
951
- return { status: 200, body: { success: true, delivered: msgId } };
952
- }
953
- // ── GET /nodes/:id/events ────────────────────────────────────
954
- // User auth. Lists recent events for a node.
955
- const eventsMatch = pathname.match(/^\/nodes\/([a-f0-9-]+)\/events$/);
956
- if (eventsMatch && method === "GET") {
957
- const nodeId = eventsMatch[1];
958
- const limit = body?.limit || 50;
959
- // Verify user has access to this node's store
960
- const { data: node } = await supabase
961
- .from("nodes")
962
- .select("store_id")
963
- .eq("id", nodeId)
964
- .single();
965
- if (!node) {
966
- return { status: 404, body: { error: "Node not found" } };
967
- }
968
- if (auth.userId) {
969
- const hasAccess = await userHasStoreAccess(supabase, auth.userId, node.store_id);
970
- if (!hasAccess) {
971
- return { status: 403, body: { error: "Not authorized to view this node's events" } };
972
- }
973
- }
974
- else if (!auth.isServiceRole) {
975
- return { status: 401, body: { error: "Authentication required" } };
976
- }
977
- const { data: events, error } = await supabase
978
- .from("node_events")
979
- .select("id, event_type, details, created_at")
980
- .eq("node_id", nodeId)
981
- .order("created_at", { ascending: false })
982
- .limit(limit);
983
- if (error) {
984
- return { status: 500, body: { error: error.message } };
985
- }
986
- return { status: 200, body: { success: true, events: events || [] } };
1372
+ const {
1373
+ data: events,
1374
+ error
1375
+ } = await supabase.from("node_events").select("id, event_type, details, created_at").eq("node_id", nodeId).order("created_at", {
1376
+ ascending: false
1377
+ }).limit(limit);
1378
+ if (error) {
1379
+ return {
1380
+ status: 500,
1381
+ body: {
1382
+ error: error.message
1383
+ }
1384
+ };
987
1385
  }
988
- // No route matched
989
- return null;
1386
+ return {
1387
+ status: 200,
1388
+ body: {
1389
+ success: true,
1390
+ events: events || []
1391
+ }
1392
+ };
1393
+ }
1394
+
1395
+ // No route matched
1396
+ return null;
990
1397
  }
1398
+ //# sourceMappingURL=nodes.js.map