whale-code 6.5.5 → 6.5.7

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 (2089) hide show
  1. package/README.md +39 -31
  2. package/bin/{swagmanager-mcp.js → whale-code.js} +40 -2
  3. package/dist/cli/__tests__/print-mode-streaming.test.js +270 -0
  4. package/dist/cli/__tests__/print-mode.basic-output.test.js +230 -0
  5. package/dist/cli/__tests__/print-mode.session-errors.test.js +252 -0
  6. package/dist/cli/__tests__/print-mode.test.js +273 -0
  7. package/dist/cli/__tests__/serve-mode-messages.test.js +338 -0
  8. package/dist/cli/__tests__/serve-mode.messages.part2.test.js +266 -0
  9. package/dist/cli/__tests__/serve-mode.messages.test.js +277 -0
  10. package/dist/cli/__tests__/serve-mode.startup-http.test.js +279 -0
  11. package/dist/cli/__tests__/serve-mode.test.js +345 -0
  12. package/dist/cli/app.d.ts +1 -0
  13. package/dist/cli/app.js +154 -72
  14. package/dist/cli/app.js.map +1 -0
  15. package/dist/cli/chat/AgentSelector.js +105 -10
  16. package/dist/cli/chat/AgentSelector.js.map +1 -0
  17. package/dist/cli/chat/ChatApp.d.ts +8 -5
  18. package/dist/cli/chat/ChatApp.js +253 -315
  19. package/dist/cli/chat/ChatApp.js.map +1 -0
  20. package/dist/cli/chat/ChatInput.d.ts +3 -2
  21. package/dist/cli/chat/ChatInput.js +1111 -770
  22. package/dist/cli/chat/ChatInput.js.map +1 -0
  23. package/dist/cli/chat/ImsgManager.d.ts +20 -0
  24. package/dist/cli/chat/ImsgManager.js +341 -0
  25. package/dist/cli/chat/ImsgManager.js.map +1 -0
  26. package/dist/cli/chat/MarkdownText.d.ts +2 -1
  27. package/dist/cli/chat/MarkdownText.js +44 -15
  28. package/dist/cli/chat/MarkdownText.js.map +1 -0
  29. package/dist/cli/chat/MemoryManager.js +182 -46
  30. package/dist/cli/chat/MemoryManager.js.map +1 -0
  31. package/dist/cli/chat/MessageList.d.ts +3 -4
  32. package/dist/cli/chat/MessageList.js +195 -49
  33. package/dist/cli/chat/MessageList.js.map +1 -0
  34. package/dist/cli/chat/ModelSelector.js +282 -63
  35. package/dist/cli/chat/ModelSelector.js.map +1 -0
  36. package/dist/cli/chat/NodeManager.js +61 -84
  37. package/dist/cli/chat/NodeManager.js.map +1 -0
  38. package/dist/cli/chat/NodeSelector.js +171 -30
  39. package/dist/cli/chat/NodeSelector.js.map +1 -0
  40. package/dist/cli/chat/OverlayContext.d.ts +27 -0
  41. package/dist/cli/chat/OverlayContext.js +2 -0
  42. package/dist/cli/chat/OverlayContext.js.map +1 -0
  43. package/dist/cli/chat/PlanApproval.js +282 -57
  44. package/dist/cli/chat/PlanApproval.js.map +1 -0
  45. package/dist/cli/chat/RewindConfirmation.d.ts +12 -0
  46. package/dist/cli/chat/RewindConfirmation.js +243 -0
  47. package/dist/cli/chat/RewindConfirmation.js.map +1 -0
  48. package/dist/cli/chat/RewindViewer.js +560 -144
  49. package/dist/cli/chat/RewindViewer.js.map +1 -0
  50. package/dist/cli/chat/SessionManager.js +138 -30
  51. package/dist/cli/chat/SessionManager.js.map +1 -0
  52. package/dist/cli/chat/SlashMenu.d.ts +7 -24
  53. package/dist/cli/chat/SlashMenu.js +150 -190
  54. package/dist/cli/chat/SlashMenu.js.map +1 -0
  55. package/dist/cli/chat/StatusBar.d.ts +5 -2
  56. package/dist/cli/chat/StatusBar.js +188 -10
  57. package/dist/cli/chat/StatusBar.js.map +1 -0
  58. package/dist/cli/chat/StoreSelector.js +147 -18
  59. package/dist/cli/chat/StoreSelector.js.map +1 -0
  60. package/dist/cli/chat/StreamingText.d.ts +0 -3
  61. package/dist/cli/chat/StreamingText.js +21 -6
  62. package/dist/cli/chat/StreamingText.js.map +1 -0
  63. package/dist/cli/chat/SubagentPanel.d.ts +6 -7
  64. package/dist/cli/chat/SubagentPanel.js +253 -91
  65. package/dist/cli/chat/SubagentPanel.js.map +1 -0
  66. package/dist/cli/chat/TeamPanel.d.ts +1 -0
  67. package/dist/cli/chat/TeamPanel.js +201 -29
  68. package/dist/cli/chat/TeamPanel.js.map +1 -0
  69. package/dist/cli/chat/ThemeSelector.js +84 -24
  70. package/dist/cli/chat/ThemeSelector.js.map +1 -0
  71. package/dist/cli/chat/ToolIndicator.d.ts +7 -23
  72. package/dist/cli/chat/ToolIndicator.js +635 -430
  73. package/dist/cli/chat/ToolIndicator.js.map +1 -0
  74. package/dist/cli/chat/chat-input-menu-handler.d.ts +32 -0
  75. package/dist/cli/chat/components/LiveArea.d.ts +12 -0
  76. package/dist/cli/chat/components/LiveArea.js +252 -0
  77. package/dist/cli/chat/components/LiveArea.js.map +1 -0
  78. package/dist/cli/chat/components/OverlayRouter.d.ts +13 -0
  79. package/dist/cli/chat/components/OverlayRouter.js +257 -0
  80. package/dist/cli/chat/components/OverlayRouter.js.map +1 -0
  81. package/dist/cli/chat/components/StaticMessages.d.ts +9 -0
  82. package/dist/cli/chat/components/StaticMessages.js +132 -0
  83. package/dist/cli/chat/components/StaticMessages.js.map +1 -0
  84. package/dist/cli/chat/hooks/agent-loop-events.d.ts +77 -0
  85. package/dist/cli/chat/hooks/agent-loop-events.js +171 -0
  86. package/dist/cli/chat/hooks/agent-loop-events.js.map +1 -0
  87. package/dist/cli/chat/hooks/slash-command-config-system.d.ts +11 -0
  88. package/dist/cli/chat/hooks/slash-command-config-system.js +177 -0
  89. package/dist/cli/chat/hooks/slash-command-config-system.js.map +1 -0
  90. package/dist/cli/chat/hooks/slash-command-handlers-system.d.ts +12 -0
  91. package/dist/cli/chat/hooks/slash-command-handlers-system.js +127 -0
  92. package/dist/cli/chat/hooks/slash-command-handlers-system.js.map +1 -0
  93. package/dist/cli/chat/hooks/slash-command-handlers.d.ts +21 -0
  94. package/dist/cli/chat/hooks/slash-command-handlers.js +222 -0
  95. package/dist/cli/chat/hooks/slash-command-handlers.js.map +1 -0
  96. package/dist/cli/chat/hooks/slash-imsg-handlers.js +148 -0
  97. package/dist/cli/chat/hooks/slash-imsg-handlers.js.map +1 -0
  98. package/dist/cli/chat/hooks/slash-node-handlers.d.ts +26 -0
  99. package/dist/cli/chat/hooks/slash-node-handlers.js +226 -0
  100. package/dist/cli/chat/hooks/slash-node-handlers.js.map +1 -0
  101. package/dist/cli/chat/hooks/useAgentLoop.d.ts +2 -20
  102. package/dist/cli/chat/hooks/useAgentLoop.js +344 -373
  103. package/dist/cli/chat/hooks/useAgentLoop.js.map +1 -0
  104. package/dist/cli/chat/hooks/useElapsedClock.d.ts +19 -0
  105. package/dist/cli/chat/hooks/useElapsedClock.js +80 -0
  106. package/dist/cli/chat/hooks/useElapsedClock.js.map +1 -0
  107. package/dist/cli/chat/hooks/useOverlayHandlers.d.ts +23 -0
  108. package/dist/cli/chat/hooks/useOverlayHandlers.js +125 -0
  109. package/dist/cli/chat/hooks/useOverlayHandlers.js.map +1 -0
  110. package/dist/cli/chat/hooks/useSlashCommands.d.ts +14 -33
  111. package/dist/cli/chat/hooks/useSlashCommands.js +167 -616
  112. package/dist/cli/chat/hooks/useSlashCommands.js.map +1 -0
  113. package/dist/cli/chat/hooks/useStreamingReducer.d.ts +66 -0
  114. package/dist/cli/chat/imsg-manager-render.d.ts +29 -0
  115. package/dist/cli/chat/imsg-manager-render.js +294 -0
  116. package/dist/cli/chat/imsg-manager-render.js.map +1 -0
  117. package/dist/cli/chat/slash-commands.d.ts +37 -0
  118. package/dist/cli/chat/slash-commands.js +194 -0
  119. package/dist/cli/chat/slash-commands.js.map +1 -0
  120. package/dist/cli/chat/store.d.ts +115 -0
  121. package/dist/cli/chat/store.js +177 -0
  122. package/dist/cli/chat/store.js.map +1 -0
  123. package/dist/cli/chat/tool-indicator-helpers.d.ts +14 -0
  124. package/dist/cli/chat/tool-indicator-helpers.js +167 -0
  125. package/dist/cli/chat/tool-indicator-helpers.js.map +1 -0
  126. package/dist/cli/chat/tool-indicator-summary.d.ts +6 -0
  127. package/dist/cli/chat/tool-indicator-summary.js +103 -0
  128. package/dist/cli/chat/tool-indicator-summary.js.map +1 -0
  129. package/dist/cli/chat/tool-indicator-types.d.ts +66 -0
  130. package/dist/cli/chat/tool-indicator-types.js +177 -0
  131. package/dist/cli/chat/tool-indicator-types.js.map +1 -0
  132. package/dist/cli/commands/__tests__/config-cmd.test.js +270 -0
  133. package/dist/cli/commands/__tests__/doctor.test.js +257 -0
  134. package/dist/cli/commands/__tests__/imsg-node-bridge.test.js +99 -0
  135. package/dist/cli/commands/__tests__/imsg-utils.test.js +73 -0
  136. package/dist/cli/commands/__tests__/init.test.js +214 -0
  137. package/dist/cli/commands/__tests__/mcp.test.js +287 -0
  138. package/dist/cli/commands/config-cmd.js +56 -57
  139. package/dist/cli/commands/config-cmd.js.map +1 -0
  140. package/dist/cli/commands/db.js +184 -169
  141. package/dist/cli/commands/db.js.map +1 -0
  142. package/dist/cli/commands/doctor.js +212 -122
  143. package/dist/cli/commands/doctor.js.map +1 -0
  144. package/dist/cli/commands/imsg-config.d.ts +31 -0
  145. package/dist/cli/commands/imsg-config.js +104 -0
  146. package/dist/cli/commands/imsg-config.js.map +1 -0
  147. package/dist/cli/commands/imsg-node-bridge.d.ts +25 -0
  148. package/dist/cli/commands/imsg-node-bridge.js +229 -0
  149. package/dist/cli/commands/imsg-node-bridge.js.map +1 -0
  150. package/dist/cli/commands/imsg-utils.d.ts +14 -0
  151. package/dist/cli/commands/imsg-utils.js +42 -0
  152. package/dist/cli/commands/imsg-utils.js.map +1 -0
  153. package/dist/cli/commands/imsg-watcher-helpers.d.ts +40 -0
  154. package/dist/cli/commands/imsg-watcher-helpers.js +184 -0
  155. package/dist/cli/commands/imsg-watcher-helpers.js.map +1 -0
  156. package/dist/cli/commands/imsg-watcher.d.ts +11 -0
  157. package/dist/cli/commands/imsg-watcher.js +230 -0
  158. package/dist/cli/commands/imsg-watcher.js.map +1 -0
  159. package/dist/cli/commands/imsg.d.ts +14 -0
  160. package/dist/cli/commands/imsg.js +181 -0
  161. package/dist/cli/commands/imsg.js.map +1 -0
  162. package/dist/cli/commands/init.js +211 -244
  163. package/dist/cli/commands/init.js.map +1 -0
  164. package/dist/cli/commands/mcp.js +127 -122
  165. package/dist/cli/commands/mcp.js.map +1 -0
  166. package/dist/cli/commands/sessions.d.ts +9 -0
  167. package/dist/cli/commands/sessions.js +93 -0
  168. package/dist/cli/commands/sessions.js.map +1 -0
  169. package/dist/cli/login/LoginApp.js +358 -141
  170. package/dist/cli/login/LoginApp.js.map +1 -0
  171. package/dist/cli/print-mode.js +196 -177
  172. package/dist/cli/print-mode.js.map +1 -0
  173. package/dist/cli/serve-mode-persistence.d.ts +18 -0
  174. package/dist/cli/serve-mode-persistence.js +157 -0
  175. package/dist/cli/serve-mode-persistence.js.map +1 -0
  176. package/dist/cli/serve-mode-query.d.ts +9 -0
  177. package/dist/cli/serve-mode-query.js +247 -0
  178. package/dist/cli/serve-mode-query.js.map +1 -0
  179. package/dist/cli/serve-mode-types.d.ts +29 -0
  180. package/dist/cli/serve-mode-types.js +56 -0
  181. package/dist/cli/serve-mode-types.js.map +1 -0
  182. package/dist/cli/serve-mode.js +615 -530
  183. package/dist/cli/serve-mode.js.map +1 -0
  184. package/dist/cli/services/__tests__/agent-definitions.test.js +153 -0
  185. package/dist/cli/services/__tests__/agent-events-global.test.js +39 -0
  186. package/dist/cli/services/__tests__/agent-events.part2.test.js +113 -0
  187. package/dist/cli/services/__tests__/agent-events.test.js +157 -0
  188. package/dist/cli/services/__tests__/agent-loop-auth.test.js +392 -0
  189. package/dist/cli/services/__tests__/agent-loop-budget.test.js +389 -0
  190. package/dist/cli/services/__tests__/agent-loop-tools-lifecycle.test.js +430 -0
  191. package/dist/cli/services/__tests__/agent-loop-tools-maxturns.test.js +486 -0
  192. package/dist/cli/services/__tests__/agent-loop-utils-execution.test.js +528 -0
  193. package/dist/cli/services/__tests__/agent-loop-utils-helpers.test.js +466 -0
  194. package/dist/cli/services/__tests__/agent-worker-base-execute.test.js +257 -0
  195. package/dist/cli/services/__tests__/agent-worker-base-helpers.test.js +198 -0
  196. package/dist/cli/services/__tests__/agent-worker-base.test.js +278 -0
  197. package/dist/cli/services/__tests__/auth-service-exports.test.js +41 -0
  198. package/dist/cli/services/__tests__/auth-service.part2.test.js +169 -0
  199. package/dist/cli/services/__tests__/auth-service.test.js +242 -0
  200. package/dist/cli/services/__tests__/background-processes.test.js +282 -0
  201. package/dist/cli/services/__tests__/claude-md-loader.test.js +134 -0
  202. package/dist/cli/services/__tests__/config-store.test.js +247 -0
  203. package/dist/cli/services/__tests__/debug-log.test.js +199 -0
  204. package/dist/cli/services/__tests__/edge-cases-caching.test.js +174 -0
  205. package/dist/cli/services/__tests__/edge-cases-compaction-core.test.js +226 -0
  206. package/dist/cli/services/__tests__/edge-cases-compaction-openai.test.js +152 -0
  207. package/dist/cli/services/__tests__/edge-cases-compaction-shapes.test.js +53 -0
  208. package/dist/cli/services/__tests__/edge-cases-compaction-thinking.test.js +226 -0
  209. package/dist/cli/services/__tests__/edge-cases-compaction.test.js +131 -0
  210. package/dist/cli/services/__tests__/edge-cases-paths.test.js +86 -0
  211. package/dist/cli/services/__tests__/error-logger-messages.test.js +81 -0
  212. package/dist/cli/services/__tests__/error-logger-transport.test.js +119 -0
  213. package/dist/cli/services/__tests__/error-logger.test.js +264 -0
  214. package/dist/cli/services/__tests__/file-history.test.js +136 -0
  215. package/dist/cli/services/__tests__/git-context-cache-reset.test.js +223 -0
  216. package/dist/cli/services/__tests__/git-context.test.js +241 -0
  217. package/dist/cli/services/__tests__/interactive-tools-execute.test.js +166 -0
  218. package/dist/cli/services/__tests__/interactive-tools-plan.test.js +197 -0
  219. package/dist/cli/services/__tests__/interactive-tools.part2.test.js +168 -0
  220. package/dist/cli/services/__tests__/interactive-tools.test.js +179 -0
  221. package/dist/cli/services/__tests__/keybinding-manager.test.js +205 -0
  222. package/dist/cli/services/__tests__/local-tools-dispatch.test.js +404 -0
  223. package/dist/cli/services/__tests__/local-tools.test.js +238 -0
  224. package/dist/cli/services/__tests__/lsp-manager.test.js +364 -0
  225. package/dist/cli/services/__tests__/mcp-client-connect-disconnect.test.js +310 -0
  226. package/dist/cli/services/__tests__/mcp-client.test.js +93 -0
  227. package/dist/cli/services/__tests__/memory-manager.test.js +154 -0
  228. package/dist/cli/services/__tests__/model-manager-utils.test.js +154 -0
  229. package/dist/cli/services/__tests__/model-manager.test.js +175 -0
  230. package/dist/cli/services/__tests__/permission-modes.test.js +222 -0
  231. package/dist/cli/services/__tests__/ripgrep.test.js +328 -0
  232. package/dist/cli/services/__tests__/server-tools-execute.test.js +317 -0
  233. package/dist/cli/services/__tests__/server-tools.test.js +272 -0
  234. package/dist/cli/services/__tests__/session-persistence.test.js +245 -0
  235. package/dist/cli/services/__tests__/subagent-basic.test.js +489 -0
  236. package/dist/cli/services/__tests__/subagent-edge.test.js +545 -0
  237. package/dist/cli/services/__tests__/subagent-prompts.test.js +558 -0
  238. package/dist/cli/services/__tests__/subagent-worker-errors.test.js +255 -0
  239. package/dist/cli/services/__tests__/subagent-worker.test.js +242 -0
  240. package/dist/cli/services/__tests__/system-prompt.test.js +210 -0
  241. package/dist/cli/services/__tests__/team-lead-comms-messaging.test.js +250 -0
  242. package/dist/cli/services/__tests__/team-lead-comms-result.test.js +232 -0
  243. package/dist/cli/services/__tests__/team-lead-comms-stop.test.js +344 -0
  244. package/dist/cli/services/__tests__/team-lead-comms.test.js +285 -0
  245. package/dist/cli/services/__tests__/team-lead-create.test.js +327 -0
  246. package/dist/cli/services/__tests__/team-lead-run.test.js +318 -0
  247. package/dist/cli/services/__tests__/team-lead-stop.test.js +199 -0
  248. package/dist/cli/services/__tests__/team-state-comms.test.js +240 -0
  249. package/dist/cli/services/__tests__/team-state-core.test.js +230 -0
  250. package/dist/cli/services/__tests__/team-state-tasks-complete-fail-available.test.js +224 -0
  251. package/dist/cli/services/__tests__/team-state-tasks.test.js +184 -0
  252. package/dist/cli/services/__tests__/telemetry-ai-metadata.test.js +116 -0
  253. package/dist/cli/services/__tests__/telemetry.part2.test.js +195 -0
  254. package/dist/cli/services/__tests__/telemetry.test.js +176 -0
  255. package/dist/cli/services/agent-config.d.ts +5 -1
  256. package/dist/cli/services/agent-config.js +66 -36
  257. package/dist/cli/services/agent-config.js.map +1 -0
  258. package/dist/cli/services/agent-definitions.d.ts +4 -1
  259. package/dist/cli/services/agent-definitions.js +97 -56
  260. package/dist/cli/services/agent-definitions.js.map +1 -0
  261. package/dist/cli/services/agent-event-types.d.ts +148 -0
  262. package/dist/cli/services/agent-event-types.js +2 -0
  263. package/dist/cli/services/agent-event-types.js.map +1 -0
  264. package/dist/cli/services/agent-events.js +225 -162
  265. package/dist/cli/services/agent-events.js.map +1 -0
  266. package/dist/cli/services/agent-loop-iteration.d.ts +13 -0
  267. package/dist/cli/services/agent-loop-setup.d.ts +32 -0
  268. package/dist/cli/services/agent-loop-shell-summarize.d.ts +11 -0
  269. package/dist/cli/services/agent-loop-shell-summarize.js +94 -0
  270. package/dist/cli/services/agent-loop-shell-summarize.js.map +1 -0
  271. package/dist/cli/services/agent-loop-tools.d.ts +37 -0
  272. package/dist/cli/services/agent-loop-tools.js +243 -0
  273. package/dist/cli/services/agent-loop-tools.js.map +1 -0
  274. package/dist/cli/services/agent-loop-types.d.ts +52 -0
  275. package/dist/cli/services/agent-loop-types.js +25 -0
  276. package/dist/cli/services/agent-loop-types.js.map +1 -0
  277. package/dist/cli/services/agent-loop.d.ts +2 -2
  278. package/dist/cli/services/agent-loop.js +1135 -702
  279. package/dist/cli/services/agent-loop.js.map +1 -0
  280. package/dist/cli/services/agent-worker-base-api.d.ts +19 -0
  281. package/dist/cli/services/agent-worker-base-helpers.d.ts +27 -0
  282. package/dist/cli/services/agent-worker-base-tools.d.ts +16 -0
  283. package/dist/cli/services/agent-worker-base-types.d.ts +81 -0
  284. package/dist/cli/services/agent-worker-base.d.ts +35 -5
  285. package/dist/cli/services/agent-worker-base.js +337 -153
  286. package/dist/cli/services/agent-worker-base.js.map +1 -0
  287. package/dist/cli/services/api-retry.js +69 -64
  288. package/dist/cli/services/api-retry.js.map +1 -0
  289. package/dist/cli/services/auth-service.d.ts +3 -3
  290. package/dist/cli/services/auth-service.js +213 -136
  291. package/dist/cli/services/auth-service.js.map +1 -0
  292. package/dist/cli/services/background-agents.d.ts +26 -0
  293. package/dist/cli/services/background-processes-ops.d.ts +24 -0
  294. package/dist/cli/services/background-processes.d.ts +2 -1
  295. package/dist/cli/services/background-processes.js +347 -267
  296. package/dist/cli/services/background-processes.js.map +1 -0
  297. package/dist/cli/services/background-tool-defs.d.ts +50 -0
  298. package/dist/cli/services/browser-auth.d.ts +2 -2
  299. package/dist/cli/services/browser-auth.js +159 -118
  300. package/dist/cli/services/browser-auth.js.map +1 -0
  301. package/dist/cli/services/claude-md-loader.js +40 -36
  302. package/dist/cli/services/claude-md-loader.js.map +1 -0
  303. package/dist/cli/services/cli-agent-loop.d.ts +41 -0
  304. package/dist/cli/services/cli-agent-loop.js +104 -0
  305. package/dist/cli/services/cli-agent-loop.js.map +1 -0
  306. package/dist/cli/services/config-modules-model.test.js +133 -0
  307. package/dist/cli/services/config-modules-permission.test.js +85 -0
  308. package/dist/cli/services/config-modules-permissions.test.js +85 -0
  309. package/dist/cli/services/config-modules-session.test.js +297 -0
  310. package/dist/cli/services/config-store.d.ts +14 -4
  311. package/dist/cli/services/config-store.js +249 -117
  312. package/dist/cli/services/config-store.js.map +1 -0
  313. package/dist/cli/services/debug-log.d.ts +1 -1
  314. package/dist/cli/services/debug-log.js +34 -35
  315. package/dist/cli/services/debug-log.js.map +1 -0
  316. package/dist/cli/services/env-detect.d.ts +7 -0
  317. package/dist/cli/services/env-detect.js +9 -0
  318. package/dist/cli/services/env-detect.js.map +1 -0
  319. package/dist/cli/services/error-logger-internals.d.ts +25 -0
  320. package/dist/cli/services/error-logger-internals.js +111 -0
  321. package/dist/cli/services/error-logger-internals.js.map +1 -0
  322. package/dist/cli/services/error-logger.js +187 -169
  323. package/dist/cli/services/error-logger.js.map +1 -0
  324. package/dist/cli/services/file-history.d.ts +1 -1
  325. package/dist/cli/services/file-history.js +50 -54
  326. package/dist/cli/services/file-history.js.map +1 -0
  327. package/dist/cli/services/format-server-response-columns.test.js +265 -0
  328. package/dist/cli/services/format-server-response-fallback.test.js +65 -0
  329. package/dist/cli/services/format-server-response-formatters.d.ts +18 -0
  330. package/dist/cli/services/format-server-response-formatters.js +195 -0
  331. package/dist/cli/services/format-server-response-formatters.js.map +1 -0
  332. package/dist/cli/services/format-server-response-primitives-basic.test.js +261 -0
  333. package/dist/cli/services/format-server-response-primitives-nested.test.js +188 -0
  334. package/dist/cli/services/format-server-response-primitives.test.js +300 -0
  335. package/dist/cli/services/format-server-response-realworld.test.js +248 -0
  336. package/dist/cli/services/format-server-response-values.test.js +247 -0
  337. package/dist/cli/services/format-server-response.js +334 -372
  338. package/dist/cli/services/format-server-response.js.map +1 -0
  339. package/dist/cli/services/git-context.js +61 -45
  340. package/dist/cli/services/git-context.js.map +1 -0
  341. package/dist/cli/services/hooks-execute.d.ts +47 -0
  342. package/dist/cli/services/hooks-execute.js +181 -0
  343. package/dist/cli/services/hooks-execute.js.map +1 -0
  344. package/dist/cli/services/hooks-runners.test.js +184 -0
  345. package/dist/cli/services/hooks.d.ts +2 -2
  346. package/dist/cli/services/hooks.glob-load.test.js +233 -0
  347. package/dist/cli/services/hooks.js +195 -180
  348. package/dist/cli/services/hooks.js.map +1 -0
  349. package/dist/cli/services/hooks.run-hooks.test.js +184 -0
  350. package/dist/cli/services/hooks.test.js +233 -0
  351. package/dist/cli/services/ink-incremental.d.ts +19 -0
  352. package/dist/cli/services/ink-incremental.js +59 -0
  353. package/dist/cli/services/ink-incremental.js.map +1 -0
  354. package/dist/cli/services/ink-resize-fix.js +54 -44
  355. package/dist/cli/services/ink-resize-fix.js.map +1 -0
  356. package/dist/cli/services/ink-sync-output.d.ts +12 -0
  357. package/dist/cli/services/ink-sync-output.js +16 -0
  358. package/dist/cli/services/ink-sync-output.js.map +1 -0
  359. package/dist/cli/services/interactive-tool-defs.d.ts +80 -0
  360. package/dist/cli/services/interactive-tools.d.ts +5 -2
  361. package/dist/cli/services/interactive-tools.js +283 -213
  362. package/dist/cli/services/interactive-tools.js.map +1 -0
  363. package/dist/cli/services/keybinding-manager.d.ts +13 -1
  364. package/dist/cli/services/keybinding-manager.js +131 -63
  365. package/dist/cli/services/keybinding-manager.js.map +1 -0
  366. package/dist/cli/services/local-tools-agent-defs-tasks.d.ts +6 -0
  367. package/dist/cli/services/local-tools-agent-defs-tasks.js +245 -0
  368. package/dist/cli/services/local-tools-agent-defs-tasks.js.map +1 -0
  369. package/dist/cli/services/local-tools-agent-defs-utils.d.ts +6 -0
  370. package/dist/cli/services/local-tools-agent-defs-utils.js +198 -0
  371. package/dist/cli/services/local-tools-agent-defs-utils.js.map +1 -0
  372. package/dist/cli/services/local-tools-agent-defs.d.ts +10 -0
  373. package/dist/cli/services/local-tools-agent-defs.js +13 -0
  374. package/dist/cli/services/local-tools-agent-defs.js.map +1 -0
  375. package/dist/cli/services/local-tools-definitions.d.ts +6 -0
  376. package/dist/cli/services/local-tools-files.test.js +256 -0
  377. package/dist/cli/services/local-tools-read-many.d.ts +6 -0
  378. package/dist/cli/services/local-tools.d.ts +1 -1
  379. package/dist/cli/services/local-tools.js +938 -657
  380. package/dist/cli/services/local-tools.js.map +1 -0
  381. package/dist/cli/services/lsp-config.d.ts +13 -0
  382. package/dist/cli/services/lsp-config.js +72 -0
  383. package/dist/cli/services/lsp-config.js.map +1 -0
  384. package/dist/cli/services/lsp-formatters.d.ts +11 -0
  385. package/dist/cli/services/lsp-formatters.js +209 -0
  386. package/dist/cli/services/lsp-formatters.js.map +1 -0
  387. package/dist/cli/services/lsp-manager.js +757 -594
  388. package/dist/cli/services/lsp-manager.js.map +1 -0
  389. package/dist/cli/services/lsp-protocol.d.ts +9 -0
  390. package/dist/cli/services/lsp-protocol.js +40 -0
  391. package/dist/cli/services/lsp-protocol.js.map +1 -0
  392. package/dist/cli/services/lsp-server-docs.d.ts +8 -0
  393. package/dist/cli/services/lsp-server-docs.js +132 -0
  394. package/dist/cli/services/lsp-server-docs.js.map +1 -0
  395. package/dist/cli/services/lsp-server-documents.d.ts +8 -0
  396. package/dist/cli/services/lsp-server-documents.js +132 -0
  397. package/dist/cli/services/lsp-server-documents.js.map +1 -0
  398. package/dist/cli/services/lsp-server.d.ts +40 -0
  399. package/dist/cli/services/lsp-server.js +270 -0
  400. package/dist/cli/services/lsp-server.js.map +1 -0
  401. package/dist/cli/services/mcp-client.d.ts +1 -1
  402. package/dist/cli/services/mcp-client.js +173 -134
  403. package/dist/cli/services/mcp-client.js.map +1 -0
  404. package/dist/cli/services/memory-manager.js +53 -40
  405. package/dist/cli/services/memory-manager.js.map +1 -0
  406. package/dist/cli/services/model-manager.js +55 -40
  407. package/dist/cli/services/model-manager.js.map +1 -0
  408. package/dist/cli/services/model-router.js +115 -85
  409. package/dist/cli/services/model-router.js.map +1 -0
  410. package/dist/cli/services/model-router.test.js +245 -0
  411. package/dist/cli/services/paths.d.ts +30 -0
  412. package/dist/cli/services/paths.js +81 -0
  413. package/dist/cli/services/paths.js.map +1 -0
  414. package/dist/cli/services/permission-modes.js +45 -25
  415. package/dist/cli/services/permission-modes.js.map +1 -0
  416. package/dist/cli/services/project-index.d.ts +41 -0
  417. package/dist/cli/services/project-index.js +286 -0
  418. package/dist/cli/services/project-index.js.map +1 -0
  419. package/dist/cli/services/rewind-rewindTo.test.js +202 -0
  420. package/dist/cli/services/rewind.js +182 -168
  421. package/dist/cli/services/rewind.js.map +1 -0
  422. package/dist/cli/services/rewind.test.js +175 -0
  423. package/dist/cli/services/ripgrep.js +115 -115
  424. package/dist/cli/services/ripgrep.js.map +1 -0
  425. package/dist/cli/services/sandbox.d.ts +1 -1
  426. package/dist/cli/services/sandbox.js +58 -37
  427. package/dist/cli/services/sandbox.js.map +1 -0
  428. package/dist/cli/services/sandbox.test.js +198 -0
  429. package/dist/cli/services/server-tools-client.d.ts +46 -0
  430. package/dist/cli/services/server-tools-client.js +211 -0
  431. package/dist/cli/services/server-tools-client.js.map +1 -0
  432. package/dist/cli/services/server-tools-media.d.ts +21 -0
  433. package/dist/cli/services/server-tools-media.js +163 -0
  434. package/dist/cli/services/server-tools-media.js.map +1 -0
  435. package/dist/cli/services/server-tools-preprocess.d.ts +23 -0
  436. package/dist/cli/services/server-tools-preprocess.js +386 -0
  437. package/dist/cli/services/server-tools-preprocess.js.map +1 -0
  438. package/dist/cli/services/server-tools.d.ts +2 -0
  439. package/dist/cli/services/server-tools.js +763 -565
  440. package/dist/cli/services/server-tools.js.map +1 -0
  441. package/dist/cli/services/session-client.d.ts +78 -0
  442. package/dist/cli/services/session-client.js +197 -0
  443. package/dist/cli/services/session-client.js.map +1 -0
  444. package/dist/cli/services/session-persistence.d.ts +3 -0
  445. package/dist/cli/services/session-persistence.js +81 -74
  446. package/dist/cli/services/session-persistence.js.map +1 -0
  447. package/dist/cli/services/subagent-execution.d.ts +12 -0
  448. package/dist/cli/services/subagent-loop-v2.d.ts +36 -0
  449. package/dist/cli/services/subagent-loop-v2.js +238 -0
  450. package/dist/cli/services/subagent-loop-v2.js.map +1 -0
  451. package/dist/cli/services/subagent-tools.d.ts +10 -0
  452. package/dist/cli/services/subagent-tools.js +96 -0
  453. package/dist/cli/services/subagent-tools.js.map +1 -0
  454. package/dist/cli/services/subagent-types.d.ts +57 -0
  455. package/dist/cli/services/subagent-types.js +218 -0
  456. package/dist/cli/services/subagent-types.js.map +1 -0
  457. package/dist/cli/services/subagent-worker.js +42 -27
  458. package/dist/cli/services/subagent-worker.js.map +1 -0
  459. package/dist/cli/services/subagent.d.ts +2 -0
  460. package/dist/cli/services/subagent.js +606 -433
  461. package/dist/cli/services/subagent.js.map +1 -0
  462. package/dist/cli/services/system-prompt.js +111 -80
  463. package/dist/cli/services/system-prompt.js.map +1 -0
  464. package/dist/cli/services/task-decomposer.d.ts +1 -1
  465. package/dist/cli/services/task-decomposer.js +172 -139
  466. package/dist/cli/services/task-decomposer.js.map +1 -0
  467. package/dist/cli/services/team-lead-auto.d.ts +11 -0
  468. package/dist/cli/services/team-lead-execution.d.ts +28 -0
  469. package/dist/cli/services/team-lead-types.d.ts +37 -0
  470. package/dist/cli/services/team-lead-types.js +2 -0
  471. package/dist/cli/services/team-lead-types.js.map +1 -0
  472. package/dist/cli/services/team-lead.d.ts +62 -2
  473. package/dist/cli/services/team-lead.js +863 -528
  474. package/dist/cli/services/team-lead.js.map +1 -0
  475. package/dist/cli/services/team-state-members.d.ts +11 -0
  476. package/dist/cli/services/team-state-members.js +185 -0
  477. package/dist/cli/services/team-state-members.js.map +1 -0
  478. package/dist/cli/services/team-state-messaging.d.ts +16 -0
  479. package/dist/cli/services/team-state-messaging.js +90 -0
  480. package/dist/cli/services/team-state-messaging.js.map +1 -0
  481. package/dist/cli/services/team-state-tasks.d.ts +12 -0
  482. package/dist/cli/services/team-state-tasks.js +23 -0
  483. package/dist/cli/services/team-state-tasks.js.map +1 -0
  484. package/dist/cli/services/team-state-types.d.ts +54 -0
  485. package/dist/cli/services/team-state-types.js +129 -0
  486. package/dist/cli/services/team-state-types.js.map +1 -0
  487. package/dist/cli/services/team-state.d.ts +5 -0
  488. package/dist/cli/services/team-state.js +348 -319
  489. package/dist/cli/services/team-state.js.map +1 -0
  490. package/dist/cli/services/teammate-loop.js +557 -0
  491. package/dist/cli/services/teammate-loop.js.map +1 -0
  492. package/dist/cli/services/teammate-prompt.d.ts +2 -0
  493. package/dist/cli/services/teammate-prompt.js +68 -0
  494. package/dist/cli/services/teammate-prompt.js.map +1 -0
  495. package/dist/cli/services/teammate-tools.d.ts +6 -0
  496. package/dist/cli/services/teammate-tools.js +158 -0
  497. package/dist/cli/services/teammate-tools.js.map +1 -0
  498. package/dist/cli/services/teammate-types.d.ts +36 -0
  499. package/dist/cli/services/teammate-types.js +43 -0
  500. package/dist/cli/services/teammate-types.js.map +1 -0
  501. package/dist/cli/services/teammate-worker-helpers.d.ts +71 -0
  502. package/dist/cli/services/teammate-worker-helpers.js +183 -0
  503. package/dist/cli/services/teammate-worker-helpers.js.map +1 -0
  504. package/dist/cli/services/teammate-worker-task-cleanup.d.ts +27 -0
  505. package/dist/cli/services/teammate-worker-task-cleanup.js +91 -0
  506. package/dist/cli/services/teammate-worker-task-cleanup.js.map +1 -0
  507. package/dist/cli/services/teammate-worker-task-loop.d.ts +35 -0
  508. package/dist/cli/services/teammate-worker-task-loop.js +273 -0
  509. package/dist/cli/services/teammate-worker-task-loop.js.map +1 -0
  510. package/dist/cli/services/teammate-worker.d.ts +13 -0
  511. package/dist/cli/services/teammate-worker.js +294 -0
  512. package/dist/cli/services/teammate-worker.js.map +1 -0
  513. package/dist/cli/services/teammate.d.ts +8 -2
  514. package/dist/cli/services/teammate.js +857 -569
  515. package/dist/cli/services/teammate.js.map +1 -0
  516. package/dist/cli/services/telemetry-spans.d.ts +17 -0
  517. package/dist/cli/services/telemetry-spans.js +172 -0
  518. package/dist/cli/services/telemetry-spans.js.map +1 -0
  519. package/dist/cli/services/telemetry.d.ts +6 -1
  520. package/dist/cli/services/telemetry.js +180 -157
  521. package/dist/cli/services/telemetry.js.map +1 -0
  522. package/dist/cli/services/tools/__tests__/agent-tools-tasks-teams.test.js +250 -0
  523. package/dist/cli/services/tools/__tests__/agent-tools-teams.test.js +200 -0
  524. package/dist/cli/services/tools/__tests__/agent-tools.test.js +340 -0
  525. package/dist/cli/services/tools/__tests__/file-ops-cache.test.js +152 -0
  526. package/dist/cli/services/tools/__tests__/file-ops-notebook.test.js +249 -0
  527. package/dist/cli/services/tools/__tests__/file-ops-read.test.js +261 -0
  528. package/dist/cli/services/tools/__tests__/file-ops-write.test.js +292 -0
  529. package/dist/cli/services/tools/__tests__/search-tools-rg.test.js +92 -0
  530. package/dist/cli/services/tools/__tests__/search-tools.part2.test.js +174 -0
  531. package/dist/cli/services/tools/__tests__/search-tools.test.js +227 -0
  532. package/dist/cli/services/tools/__tests__/shell-exec-allowed-core.test.js +163 -0
  533. package/dist/cli/services/tools/__tests__/shell-exec-allowed-extended.test.js +220 -0
  534. package/dist/cli/services/tools/__tests__/shell-exec-allowed.part2.test.js +215 -0
  535. package/dist/cli/services/tools/__tests__/shell-exec-allowed.test.js +154 -0
  536. package/dist/cli/services/tools/__tests__/shell-exec-blocked.test.js +132 -0
  537. package/dist/cli/services/tools/__tests__/shell-exec-execution.test.js +245 -0
  538. package/dist/cli/services/tools/__tests__/task-manager-create.test.js +110 -0
  539. package/dist/cli/services/tools/__tests__/task-manager-crud.test.js +339 -0
  540. package/dist/cli/services/tools/__tests__/task-manager-list-get.test.js +343 -0
  541. package/dist/cli/services/tools/__tests__/task-manager-query.test.js +346 -0
  542. package/dist/cli/services/tools/__tests__/task-manager-routing.test.js +58 -0
  543. package/dist/cli/services/tools/__tests__/task-manager-update.test.js +224 -0
  544. package/dist/cli/services/tools/__tests__/task-manager.test.js +159 -0
  545. package/dist/cli/services/tools/__tests__/web-tools-html-search.test.js +227 -0
  546. package/dist/cli/services/tools/__tests__/web-tools.test.js +285 -0
  547. package/dist/cli/services/tools/agent-tools-tasks.d.ts +10 -0
  548. package/dist/cli/services/tools/agent-tools-tasks.js +216 -0
  549. package/dist/cli/services/tools/agent-tools-tasks.js.map +1 -0
  550. package/dist/cli/services/tools/agent-tools-team.d.ts +8 -0
  551. package/dist/cli/services/tools/agent-tools-team.js +101 -0
  552. package/dist/cli/services/tools/agent-tools-team.js.map +1 -0
  553. package/dist/cli/services/tools/agent-tools.d.ts +3 -3
  554. package/dist/cli/services/tools/agent-tools.js +555 -324
  555. package/dist/cli/services/tools/agent-tools.js.map +1 -0
  556. package/dist/cli/services/tools/file-ops-notebook.d.ts +9 -0
  557. package/dist/cli/services/tools/file-ops-notebook.js +186 -0
  558. package/dist/cli/services/tools/file-ops-notebook.js.map +1 -0
  559. package/dist/cli/services/tools/file-ops-read.d.ts +11 -0
  560. package/dist/cli/services/tools/file-ops-read.js +237 -0
  561. package/dist/cli/services/tools/file-ops-read.js.map +1 -0
  562. package/dist/cli/services/tools/file-ops-write.d.ts +8 -0
  563. package/dist/cli/services/tools/file-ops-write.js +260 -0
  564. package/dist/cli/services/tools/file-ops-write.js.map +1 -0
  565. package/dist/cli/services/tools/file-ops.d.ts +1 -0
  566. package/dist/cli/services/tools/file-ops.js +727 -447
  567. package/dist/cli/services/tools/file-ops.js.map +1 -0
  568. package/dist/cli/services/tools/html-to-text.d.ts +6 -0
  569. package/dist/cli/services/tools/html-to-text.js +118 -0
  570. package/dist/cli/services/tools/html-to-text.js.map +1 -0
  571. package/dist/cli/services/tools/search-tools.js +231 -162
  572. package/dist/cli/services/tools/search-tools.js.map +1 -0
  573. package/dist/cli/services/tools/shell-exec.js +197 -151
  574. package/dist/cli/services/tools/shell-exec.js.map +1 -0
  575. package/dist/cli/services/tools/shell-exec.test.js +148 -0
  576. package/dist/cli/services/tools/ssrf-guard.d.ts +6 -0
  577. package/dist/cli/services/tools/ssrf-guard.js +80 -0
  578. package/dist/cli/services/tools/ssrf-guard.js.map +1 -0
  579. package/dist/cli/services/tools/task-manager.js +206 -173
  580. package/dist/cli/services/tools/task-manager.js.map +1 -0
  581. package/dist/cli/services/tools/web-tools.js +388 -341
  582. package/dist/cli/services/tools/web-tools.js.map +1 -0
  583. package/dist/cli/setup/SetupApp.d.ts +2 -2
  584. package/dist/cli/setup/SetupApp.js +608 -160
  585. package/dist/cli/setup/SetupApp.js.map +1 -0
  586. package/dist/cli/setup/SetupComponents.d.ts +10 -0
  587. package/dist/cli/setup/SetupComponents.js +144 -0
  588. package/dist/cli/setup/SetupComponents.js.map +1 -0
  589. package/dist/cli/setup/setup-cli-targets.d.ts +9 -0
  590. package/dist/cli/setup/setup-cli-targets.js +49 -0
  591. package/dist/cli/setup/setup-cli-targets.js.map +1 -0
  592. package/dist/cli/shared/ErrorBoundary.d.ts +22 -0
  593. package/dist/cli/shared/ErrorBoundary.js +73 -0
  594. package/dist/cli/shared/ErrorBoundary.js.map +1 -0
  595. package/dist/cli/shared/InlineErrorBoundary.d.ts +22 -0
  596. package/dist/cli/shared/InlineErrorBoundary.js +35 -0
  597. package/dist/cli/shared/InlineErrorBoundary.js.map +1 -0
  598. package/dist/cli/shared/MatrixIntro.js +67 -69
  599. package/dist/cli/shared/MatrixIntro.js.map +1 -0
  600. package/dist/cli/shared/SharedTick.d.ts +10 -0
  601. package/dist/cli/shared/SpinnerSlot.d.ts +22 -0
  602. package/dist/cli/shared/SpinnerSlot.js +156 -0
  603. package/dist/cli/shared/SpinnerSlot.js.map +1 -0
  604. package/dist/cli/shared/Theme.d.ts +1 -1
  605. package/dist/cli/shared/Theme.js +136 -92
  606. package/dist/cli/shared/Theme.js.map +1 -0
  607. package/dist/cli/shared/WhaleBanner.js +100 -11
  608. package/dist/cli/shared/WhaleBanner.js.map +1 -0
  609. package/dist/cli/shared/__tests__/markdown.test.js +188 -0
  610. package/dist/cli/shared/format-utils.d.ts +16 -0
  611. package/dist/cli/shared/format-utils.js +121 -0
  612. package/dist/cli/shared/format-utils.js.map +1 -0
  613. package/dist/cli/shared/markdown-chart.d.ts +8 -0
  614. package/dist/cli/shared/markdown-chart.js +92 -0
  615. package/dist/cli/shared/markdown-chart.js.map +1 -0
  616. package/dist/cli/shared/markdown-diff.d.ts +15 -0
  617. package/dist/cli/shared/markdown-diff.js +205 -0
  618. package/dist/cli/shared/markdown-diff.js.map +1 -0
  619. package/dist/cli/shared/markdown-helpers.d.ts +33 -0
  620. package/dist/cli/shared/markdown-helpers.js +129 -0
  621. package/dist/cli/shared/markdown-helpers.js.map +1 -0
  622. package/dist/cli/shared/markdown-table.d.ts +10 -0
  623. package/dist/cli/shared/markdown-table.js +227 -0
  624. package/dist/cli/shared/markdown-table.js.map +1 -0
  625. package/dist/cli/shared/markdown.d.ts +2 -4
  626. package/dist/cli/shared/markdown.js +658 -703
  627. package/dist/cli/shared/markdown.js.map +1 -0
  628. package/dist/cli/shared/marked-terminal.d.js +2 -0
  629. package/dist/cli/shared/marked-terminal.d.js.map +1 -0
  630. package/dist/cli/shared/theme-manager.js +99 -90
  631. package/dist/cli/shared/theme-manager.js.map +1 -0
  632. package/dist/cli/shared/theme-presets.js +256 -254
  633. package/dist/cli/shared/theme-presets.js.map +1 -0
  634. package/dist/cli/shared/useLatestRef.d.ts +11 -0
  635. package/dist/cli/shared/useLatestRef.js +34 -0
  636. package/dist/cli/shared/useLatestRef.js.map +1 -0
  637. package/dist/cli/shared/useSyncState.d.ts +11 -0
  638. package/dist/cli/shared/useSyncState.js +39 -0
  639. package/dist/cli/shared/useSyncState.js.map +1 -0
  640. package/dist/cli/status/StatusApp.js +235 -86
  641. package/dist/cli/status/StatusApp.js.map +1 -0
  642. package/dist/cli/stores/StoreApp.js +278 -66
  643. package/dist/cli/stores/StoreApp.js.map +1 -0
  644. package/dist/index-agent.d.ts +11 -0
  645. package/dist/index-agent.js +83 -0
  646. package/dist/index-agent.js.map +1 -0
  647. package/dist/index-auth.d.ts +22 -0
  648. package/dist/index-auth.js +114 -0
  649. package/dist/index-auth.js.map +1 -0
  650. package/dist/index-handlers.d.ts +25 -0
  651. package/dist/index-handlers.js +164 -0
  652. package/dist/index-handlers.js.map +1 -0
  653. package/dist/index-tools.d.ts +22 -0
  654. package/dist/index-tools.js +115 -0
  655. package/dist/index-tools.js.map +1 -0
  656. package/dist/index.d.ts +2 -2
  657. package/dist/index.js +509 -396
  658. package/dist/index.js.map +1 -0
  659. package/dist/local-agent/__tests__/connection-disconnect.test.js +201 -0
  660. package/dist/local-agent/__tests__/connection-lifecycle.test.js +289 -0
  661. package/dist/local-agent/__tests__/connection-msghandling.test.js +311 -0
  662. package/dist/local-agent/__tests__/connection-reconnect.test.js +230 -0
  663. package/dist/local-agent/__tests__/connection-toolexec.test.js +253 -0
  664. package/dist/local-agent/__tests__/discovery.test.js +328 -0
  665. package/dist/local-agent/__tests__/executor-background.test.js +219 -0
  666. package/dist/local-agent/__tests__/executor-exec.test.js +221 -0
  667. package/dist/local-agent/__tests__/executor-jobs-sessions.test.js +220 -0
  668. package/dist/local-agent/__tests__/executor-system-info.test.js +133 -0
  669. package/dist/local-agent/__tests__/executor-systeminfo.test.js +109 -0
  670. package/dist/local-agent/__tests__/executor.test.js +235 -0
  671. package/dist/local-agent/__tests__/index.test.js +139 -0
  672. package/dist/local-agent/connection-handlers.d.ts +8 -0
  673. package/dist/local-agent/connection-handlers.js +112 -0
  674. package/dist/local-agent/connection-handlers.js.map +1 -0
  675. package/dist/local-agent/connection-tools.d.ts +8 -0
  676. package/dist/local-agent/connection-tools.js +111 -0
  677. package/dist/local-agent/connection-tools.js.map +1 -0
  678. package/dist/local-agent/connection.d.ts +2 -2
  679. package/dist/local-agent/connection.js +352 -293
  680. package/dist/local-agent/connection.js.map +1 -0
  681. package/dist/local-agent/discovery.js +259 -122
  682. package/dist/local-agent/discovery.js.map +1 -0
  683. package/dist/local-agent/executor-jobs.d.ts +12 -0
  684. package/dist/local-agent/executor-jobs.js +143 -0
  685. package/dist/local-agent/executor-jobs.js.map +1 -0
  686. package/dist/local-agent/executor.d.ts +3 -0
  687. package/dist/local-agent/executor.js +218 -195
  688. package/dist/local-agent/executor.js.map +1 -0
  689. package/dist/local-agent/index.d.ts +2 -2
  690. package/dist/local-agent/index.js +156 -156
  691. package/dist/local-agent/index.js.map +1 -0
  692. package/dist/node/__tests__/cli-channels.test.js +293 -0
  693. package/dist/node/__tests__/cli-config-edge.test.js +154 -0
  694. package/dist/node/__tests__/cli-config.test.js +215 -0
  695. package/dist/node/__tests__/config.test.js +292 -0
  696. package/dist/node/__tests__/runtime-heartbeat.test.js +153 -0
  697. package/dist/node/__tests__/runtime-lifecycle-init.test.js +263 -0
  698. package/dist/node/__tests__/runtime-lifecycle-stats.test.js +180 -0
  699. package/dist/node/__tests__/runtime-lifecycle.test.js +305 -0
  700. package/dist/node/__tests__/runtime-relay.test.js +341 -0
  701. package/dist/node/adapters/__tests__/base.test.js +286 -0
  702. package/dist/node/adapters/__tests__/discord.test.js +284 -0
  703. package/dist/node/adapters/__tests__/email-send.test.js +295 -0
  704. package/dist/node/adapters/__tests__/email.inbound-send.test.js +217 -0
  705. package/dist/node/adapters/__tests__/email.lifecycle.test.js +211 -0
  706. package/dist/node/adapters/__tests__/email.test.js +290 -0
  707. package/dist/node/adapters/__tests__/email.webhook-send.test.js +251 -0
  708. package/dist/node/adapters/__tests__/imessage-filter.test.js +183 -0
  709. package/dist/node/adapters/__tests__/imessage-lifecycle.test.js +215 -0
  710. package/dist/node/adapters/__tests__/imessage-send-restart.test.js +227 -0
  711. package/dist/node/adapters/__tests__/slack.part2.test.js +135 -0
  712. package/dist/node/adapters/__tests__/slack.test.js +241 -0
  713. package/dist/node/adapters/__tests__/sms-extras.test.js +108 -0
  714. package/dist/node/adapters/__tests__/sms-lifecycle.test.js +203 -0
  715. package/dist/node/adapters/__tests__/sms-messaging.test.js +266 -0
  716. package/dist/node/adapters/__tests__/sms.part2.test.js +174 -0
  717. package/dist/node/adapters/__tests__/sms.test.js +253 -0
  718. package/dist/node/adapters/__tests__/telegram-polling.test.js +256 -0
  719. package/dist/node/adapters/__tests__/telegram-send.test.js +166 -0
  720. package/dist/node/adapters/__tests__/webchat-inbound.test.js +188 -0
  721. package/dist/node/adapters/__tests__/webchat-outbound.test.js +178 -0
  722. package/dist/node/adapters/__tests__/whatsapp-inbound.test.js +200 -0
  723. package/dist/node/adapters/__tests__/whatsapp-send.test.js +212 -0
  724. package/dist/node/adapters/__tests__/whatsapp.test.js +280 -0
  725. package/dist/node/adapters/base.js +18 -8
  726. package/dist/node/adapters/base.js.map +1 -0
  727. package/dist/node/adapters/discord-gateway.d.ts +42 -0
  728. package/dist/node/adapters/discord-gateway.js +169 -0
  729. package/dist/node/adapters/discord-gateway.js.map +1 -0
  730. package/dist/node/adapters/discord.js +286 -275
  731. package/dist/node/adapters/discord.js.map +1 -0
  732. package/dist/node/adapters/email.js +189 -202
  733. package/dist/node/adapters/email.js.map +1 -0
  734. package/dist/node/adapters/imessage.d.ts +11 -0
  735. package/dist/node/adapters/imessage.js +165 -145
  736. package/dist/node/adapters/imessage.js.map +1 -0
  737. package/dist/node/adapters/slack.js +237 -236
  738. package/dist/node/adapters/slack.js.map +1 -0
  739. package/dist/node/adapters/sms.js +149 -151
  740. package/dist/node/adapters/sms.js.map +1 -0
  741. package/dist/node/adapters/telegram.js +88 -92
  742. package/dist/node/adapters/telegram.js.map +1 -0
  743. package/dist/node/adapters/webchat.js +160 -136
  744. package/dist/node/adapters/webchat.js.map +1 -0
  745. package/dist/node/adapters/whatsapp.js +212 -215
  746. package/dist/node/adapters/whatsapp.js.map +1 -0
  747. package/dist/node/cli-channels.d.ts +6 -0
  748. package/dist/node/cli-channels.js +229 -0
  749. package/dist/node/cli-channels.js.map +1 -0
  750. package/dist/node/cli-node.d.ts +9 -0
  751. package/dist/node/cli-node.js +186 -0
  752. package/dist/node/cli-node.js.map +1 -0
  753. package/dist/node/cli-portal.d.ts +11 -0
  754. package/dist/node/cli-portal.js +503 -0
  755. package/dist/node/cli-portal.js.map +1 -0
  756. package/dist/node/cli.js +884 -653
  757. package/dist/node/cli.js.map +1 -0
  758. package/dist/node/config.js +20 -18
  759. package/dist/node/config.js.map +1 -0
  760. package/dist/node/gateway-client.js +191 -181
  761. package/dist/node/gateway-client.js.map +1 -0
  762. package/dist/node/portal/clipboard.js +161 -130
  763. package/dist/node/portal/clipboard.js.map +1 -0
  764. package/dist/node/portal/discovery.js +51 -45
  765. package/dist/node/portal/discovery.js.map +1 -0
  766. package/dist/node/portal/forward.js +64 -58
  767. package/dist/node/portal/forward.js.map +1 -0
  768. package/dist/node/portal/index.js +246 -221
  769. package/dist/node/portal/index.js.map +1 -0
  770. package/dist/node/portal/multiplexer.js +192 -182
  771. package/dist/node/portal/multiplexer.js.map +1 -0
  772. package/dist/node/portal/permissions.js +102 -70
  773. package/dist/node/portal/permissions.js.map +1 -0
  774. package/dist/node/portal/portal-target.d.ts +16 -0
  775. package/dist/node/portal/portal-target.js +102 -0
  776. package/dist/node/portal/portal-target.js.map +1 -0
  777. package/dist/node/portal/protocol-frames.d.ts +33 -0
  778. package/dist/node/portal/protocol-frames.js +170 -0
  779. package/dist/node/portal/protocol-frames.js.map +1 -0
  780. package/dist/node/portal/protocol.js +153 -116
  781. package/dist/node/portal/protocol.js.map +1 -0
  782. package/dist/node/portal/screen.js +80 -69
  783. package/dist/node/portal/screen.js.map +1 -0
  784. package/dist/node/portal/session.js +124 -117
  785. package/dist/node/portal/session.js.map +1 -0
  786. package/dist/node/portal/shell.js +140 -113
  787. package/dist/node/portal/shell.js.map +1 -0
  788. package/dist/node/portal/stream.js +77 -75
  789. package/dist/node/portal/stream.js.map +1 -0
  790. package/dist/node/portal/transfer.js +190 -167
  791. package/dist/node/portal/transfer.js.map +1 -0
  792. package/dist/node/portal/ui.js +124 -99
  793. package/dist/node/portal/ui.js.map +1 -0
  794. package/dist/node/remote-desktop/compile-helper.js +50 -45
  795. package/dist/node/remote-desktop/compile-helper.js.map +1 -0
  796. package/dist/node/remote-desktop/index.js +215 -187
  797. package/dist/node/remote-desktop/index.js.map +1 -0
  798. package/dist/node/remote-desktop/protocol.js +45 -29
  799. package/dist/node/remote-desktop/protocol.js.map +1 -0
  800. package/dist/node/runtime-network.d.ts +25 -0
  801. package/dist/node/runtime-network.js +226 -0
  802. package/dist/node/runtime-network.js.map +1 -0
  803. package/dist/node/runtime-utils.d.ts +27 -0
  804. package/dist/node/runtime-utils.js +99 -0
  805. package/dist/node/runtime-utils.js.map +1 -0
  806. package/dist/node/runtime.d.ts +1 -0
  807. package/dist/node/runtime.js +562 -409
  808. package/dist/node/runtime.js.map +1 -0
  809. package/dist/node/task-runner.d.ts +33 -0
  810. package/dist/node/task-runner.js +276 -0
  811. package/dist/node/task-runner.js.map +1 -0
  812. package/dist/server/__tests__/gateway-fast-fail.test.js +160 -0
  813. package/dist/server/__tests__/local-agent-gateway.test.js +186 -0
  814. package/dist/server/__tests__/proxy-handlers-delegation.test.js +240 -0
  815. package/dist/server/__tests__/proxy-handlers-validation.test.js +211 -0
  816. package/dist/server/__tests__/proxy-handlers.part2.test.js +240 -0
  817. package/dist/server/__tests__/proxy-handlers.test.js +213 -0
  818. package/dist/server/__tests__/strip-base64-e2e.test.js +303 -0
  819. package/dist/server/__tests__/strip-base64.test.js +256 -0
  820. package/dist/server/__tests__/tool-router-agent-tools.test.js +324 -0
  821. package/dist/server/__tests__/tool-router-execute-core.test.js +357 -0
  822. package/dist/server/__tests__/tool-router-execute-permissions.test.js +332 -0
  823. package/dist/server/__tests__/tool-router-execute.test.js +348 -0
  824. package/dist/server/__tests__/tool-router-load.test.js +432 -0
  825. package/dist/server/__tests__/tool-router-permissions.test.js +359 -0
  826. package/dist/server/__tests__/tool-router-registry-cache.test.js +383 -0
  827. package/dist/server/__tests__/tool-router-registry-handlers.test.js +272 -0
  828. package/dist/server/__tests__/tool-router-registry.test.js +331 -0
  829. package/dist/server/__tests__/validation-inventory.test.js +250 -0
  830. package/dist/server/__tests__/validation-misc.test.js +243 -0
  831. package/dist/server/__tests__/validation-supply-chain.test.js +188 -0
  832. package/dist/server/__tests__/worker.test.js +265 -0
  833. package/dist/server/handlers/__test-utils__/test-db.d.ts +2 -0
  834. package/dist/server/handlers/__test-utils__/test-db.js +52 -89
  835. package/dist/server/handlers/__test-utils__/test-db.js.map +1 -0
  836. package/dist/server/handlers/__tests__/conversation-lock.test.js +117 -0
  837. package/dist/server/handlers/__tests__/e2e/auth-cross-platform-login.e2e.test.js +268 -0
  838. package/dist/server/handlers/__tests__/e2e/auth-cross-platform-tokens.e2e.test.js +264 -0
  839. package/dist/server/handlers/__tests__/e2e/email-pipeline-send.e2e.test.js +214 -0
  840. package/dist/server/handlers/__tests__/e2e/email-pipeline-threads.e2e.test.js +168 -0
  841. package/dist/server/handlers/__tests__/e2e/error-logging-pipeline-dedup.e2e.test.js +229 -0
  842. package/dist/server/handlers/__tests__/e2e/error-logging-pipeline.e2e.test.js +239 -0
  843. package/dist/server/handlers/__tests__/e2e/error-logging-rate-limit.e2e.test.js +150 -0
  844. package/dist/server/handlers/__tests__/e2e/inventory-sync-guards.e2e.test.js +177 -0
  845. package/dist/server/handlers/__tests__/e2e/inventory-sync.e2e.test.js +228 -0
  846. package/dist/server/handlers/__tests__/e2e/inventory-sync.part2.e2e.test.js +188 -0
  847. package/dist/server/handlers/__tests__/e2e/order-lifecycle-fulfillment.e2e.test.js +295 -0
  848. package/dist/server/handlers/__tests__/e2e/order-lifecycle.e2e.test.js +277 -0
  849. package/dist/server/handlers/__tests__/e2e/order-lifecycle.fulfillment.e2e.test.js +307 -0
  850. package/dist/server/handlers/__tests__/e2e/order-lifecycle.setup.e2e.test.js +177 -0
  851. package/dist/server/handlers/__tests__/e2e/storefront-checkout-cart.e2e.test.js +255 -0
  852. package/dist/server/handlers/__tests__/e2e/storefront-checkout-webhook.e2e.test.js +231 -0
  853. package/dist/server/handlers/__tests__/e2e/workflow-execution-failures.e2e.test.js +235 -0
  854. package/dist/server/handlers/__tests__/e2e/workflow-execution.e2e.test.js +294 -0
  855. package/dist/server/handlers/__tests__/e2e/workflow-security.e2e.test.js +311 -0
  856. package/dist/server/handlers/__tests__/e2e/workflow-security.part2.e2e.test.js +267 -0
  857. package/dist/server/handlers/__tests__/workflow-cache.test.js +237 -0
  858. package/dist/server/handlers/analytics-errors-edge.test.js +173 -0
  859. package/dist/server/handlers/analytics.js +553 -260
  860. package/dist/server/handlers/analytics.js.map +1 -0
  861. package/dist/server/handlers/analytics.test.js +280 -0
  862. package/dist/server/handlers/api-docs-examples-ext.d.ts +9 -0
  863. package/dist/server/handlers/api-docs-examples-ext.js +278 -0
  864. package/dist/server/handlers/api-docs-examples-ext.js.map +1 -0
  865. package/dist/server/handlers/api-docs-examples.d.ts +8 -0
  866. package/dist/server/handlers/api-docs-examples.js +221 -0
  867. package/dist/server/handlers/api-docs-examples.js.map +1 -0
  868. package/dist/server/handlers/api-docs-sections-ext.d.ts +2 -0
  869. package/dist/server/handlers/api-docs-sections-ext.js +497 -0
  870. package/dist/server/handlers/api-docs-sections-ext.js.map +1 -0
  871. package/dist/server/handlers/api-docs-sections.d.ts +21 -0
  872. package/dist/server/handlers/api-docs-sections.js +293 -0
  873. package/dist/server/handlers/api-docs-sections.js.map +1 -0
  874. package/dist/server/handlers/api-docs.d.ts +2 -0
  875. package/dist/server/handlers/api-docs.js +1205 -893
  876. package/dist/server/handlers/api-docs.js.map +1 -0
  877. package/dist/server/handlers/api-keys.js +291 -242
  878. package/dist/server/handlers/api-keys.js.map +1 -0
  879. package/dist/server/handlers/api-keys.part2.test.js +157 -0
  880. package/dist/server/handlers/api-keys.test.js +161 -0
  881. package/dist/server/handlers/billing-core.d.ts +28 -0
  882. package/dist/server/handlers/billing-core.js +126 -0
  883. package/dist/server/handlers/billing-core.js.map +1 -0
  884. package/dist/server/handlers/billing-routes.d.ts +9 -0
  885. package/dist/server/handlers/billing-routes.js +243 -0
  886. package/dist/server/handlers/billing-routes.js.map +1 -0
  887. package/dist/server/handlers/billing-routes.test.js +123 -0
  888. package/dist/server/handlers/billing.js +330 -239
  889. package/dist/server/handlers/billing.js.map +1 -0
  890. package/dist/server/handlers/billing.test.js +215 -0
  891. package/dist/server/handlers/browser-actions-errors.test.js +94 -0
  892. package/dist/server/handlers/browser-actions.d.ts +35 -0
  893. package/dist/server/handlers/browser-actions.js +171 -0
  894. package/dist/server/handlers/browser-actions.js.map +1 -0
  895. package/dist/server/handlers/browser-actions.part2.test.js +190 -0
  896. package/dist/server/handlers/browser-actions.test.js +190 -0
  897. package/dist/server/handlers/browser-captcha.d.ts +6 -0
  898. package/dist/server/handlers/browser-captcha.js +172 -0
  899. package/dist/server/handlers/browser-captcha.js.map +1 -0
  900. package/dist/server/handlers/browser-lifecycle.d.ts +9 -0
  901. package/dist/server/handlers/browser-lifecycle.js +142 -0
  902. package/dist/server/handlers/browser-lifecycle.js.map +1 -0
  903. package/dist/server/handlers/browser-validation.test.js +257 -0
  904. package/dist/server/handlers/browser.js +468 -395
  905. package/dist/server/handlers/browser.js.map +1 -0
  906. package/dist/server/handlers/catalog-advanced.d.ts +9 -0
  907. package/dist/server/handlers/catalog-advanced.js +352 -0
  908. package/dist/server/handlers/catalog-advanced.js.map +1 -0
  909. package/dist/server/handlers/catalog-categories.d.ts +5 -0
  910. package/dist/server/handlers/catalog-categories.js +181 -0
  911. package/dist/server/handlers/catalog-categories.js.map +1 -0
  912. package/dist/server/handlers/catalog-products.d.ts +5 -0
  913. package/dist/server/handlers/catalog-products.js +404 -0
  914. package/dist/server/handlers/catalog-products.js.map +1 -0
  915. package/dist/server/handlers/catalog-schemas.d.ts +5 -0
  916. package/dist/server/handlers/catalog-schemas.js +426 -0
  917. package/dist/server/handlers/catalog-schemas.js.map +1 -0
  918. package/dist/server/handlers/catalog.js +1377 -978
  919. package/dist/server/handlers/catalog.js.map +1 -0
  920. package/dist/server/handlers/catalog.test.js +297 -0
  921. package/dist/server/handlers/clickhouse.js +157 -109
  922. package/dist/server/handlers/clickhouse.js.map +1 -0
  923. package/dist/server/handlers/comms-documents-advanced.d.ts +8 -0
  924. package/dist/server/handlers/comms-documents-advanced.js +159 -0
  925. package/dist/server/handlers/comms-documents-advanced.js.map +1 -0
  926. package/dist/server/handlers/comms-documents.d.ts +10 -0
  927. package/dist/server/handlers/comms-documents.js +445 -0
  928. package/dist/server/handlers/comms-documents.js.map +1 -0
  929. package/dist/server/handlers/comms-email.d.ts +10 -0
  930. package/dist/server/handlers/comms-email.js +492 -0
  931. package/dist/server/handlers/comms-email.js.map +1 -0
  932. package/dist/server/handlers/comms-pdf-generation.d.ts +8 -0
  933. package/dist/server/handlers/comms-pdf-generation.js +327 -0
  934. package/dist/server/handlers/comms-pdf-generation.js.map +1 -0
  935. package/dist/server/handlers/comms-pdf-helpers.d.ts +9 -0
  936. package/dist/server/handlers/comms-pdf-helpers.js +126 -0
  937. package/dist/server/handlers/comms-pdf-helpers.js.map +1 -0
  938. package/dist/server/handlers/comms-shared.d.ts +3 -0
  939. package/dist/server/handlers/comms-shared.js +28 -0
  940. package/dist/server/handlers/comms-shared.js.map +1 -0
  941. package/dist/server/handlers/comms.js +1439 -984
  942. package/dist/server/handlers/comms.js.map +1 -0
  943. package/dist/server/handlers/comms.test.js +289 -0
  944. package/dist/server/handlers/creations-actions.d.ts +21 -0
  945. package/dist/server/handlers/creations-actions.js +250 -0
  946. package/dist/server/handlers/creations-actions.js.map +1 -0
  947. package/dist/server/handlers/creations-advanced-collections.test.js +214 -0
  948. package/dist/server/handlers/creations-advanced-generate.test.js +142 -0
  949. package/dist/server/handlers/creations-advanced.test.js +171 -0
  950. package/dist/server/handlers/creations-collections-preview.test.js +214 -0
  951. package/dist/server/handlers/creations-crud.d.ts +35 -0
  952. package/dist/server/handlers/creations-crud.js +248 -0
  953. package/dist/server/handlers/creations-crud.js.map +1 -0
  954. package/dist/server/handlers/creations-crud.test.js +260 -0
  955. package/dist/server/handlers/creations-generate.d.ts +21 -0
  956. package/dist/server/handlers/creations-generate.js +256 -0
  957. package/dist/server/handlers/creations-generate.js.map +1 -0
  958. package/dist/server/handlers/creations-mutations.test.js +197 -0
  959. package/dist/server/handlers/creations.js +461 -394
  960. package/dist/server/handlers/creations.js.map +1 -0
  961. package/dist/server/handlers/crm-customers-read.d.ts +58 -0
  962. package/dist/server/handlers/crm-customers-read.js +208 -0
  963. package/dist/server/handlers/crm-customers-read.js.map +1 -0
  964. package/dist/server/handlers/crm-customers-write.d.ts +54 -0
  965. package/dist/server/handlers/crm-customers-write.js +414 -0
  966. package/dist/server/handlers/crm-customers-write.js.map +1 -0
  967. package/dist/server/handlers/crm-utils.d.ts +6 -0
  968. package/dist/server/handlers/crm-utils.js +19 -0
  969. package/dist/server/handlers/crm-utils.js.map +1 -0
  970. package/dist/server/handlers/crm.js +1082 -791
  971. package/dist/server/handlers/crm.js.map +1 -0
  972. package/dist/server/handlers/crm.test.js +179 -0
  973. package/dist/server/handlers/discovery-advertise.d.ts +18 -0
  974. package/dist/server/handlers/discovery-advertise.js +101 -0
  975. package/dist/server/handlers/discovery-advertise.js.map +1 -0
  976. package/dist/server/handlers/discovery-advertise.test.js +185 -0
  977. package/dist/server/handlers/discovery-scan.d.ts +19 -0
  978. package/dist/server/handlers/discovery-scan.js +107 -0
  979. package/dist/server/handlers/discovery-scan.js.map +1 -0
  980. package/dist/server/handlers/discovery-scan.test.js +233 -0
  981. package/dist/server/handlers/discovery.js +251 -232
  982. package/dist/server/handlers/discovery.js.map +1 -0
  983. package/dist/server/handlers/embeddings-embed-search.test.js +196 -0
  984. package/dist/server/handlers/embeddings-index-delete-stats.test.js +140 -0
  985. package/dist/server/handlers/embeddings-search.test.js +221 -0
  986. package/dist/server/handlers/embeddings.js +241 -164
  987. package/dist/server/handlers/embeddings.js.map +1 -0
  988. package/dist/server/handlers/embeddings.test.js +137 -0
  989. package/dist/server/handlers/enrichment-breach.d.ts +8 -0
  990. package/dist/server/handlers/enrichment-breach.js +266 -0
  991. package/dist/server/handlers/enrichment-breach.js.map +1 -0
  992. package/dist/server/handlers/enrichment-data.d.ts +13 -0
  993. package/dist/server/handlers/enrichment-data.js +145 -0
  994. package/dist/server/handlers/enrichment-data.js.map +1 -0
  995. package/dist/server/handlers/enrichment-enrich.d.ts +6 -0
  996. package/dist/server/handlers/enrichment-enrich.js +235 -0
  997. package/dist/server/handlers/enrichment-enrich.js.map +1 -0
  998. package/dist/server/handlers/enrichment-helpers.d.ts +10 -0
  999. package/dist/server/handlers/enrichment-helpers.js +17 -0
  1000. package/dist/server/handlers/enrichment-helpers.js.map +1 -0
  1001. package/dist/server/handlers/enrichment-mutations.test.js +240 -0
  1002. package/dist/server/handlers/enrichment-queries.test.js +181 -0
  1003. package/dist/server/handlers/enrichment-validation.test.js +177 -0
  1004. package/dist/server/handlers/enrichment-writes.d.ts +16 -0
  1005. package/dist/server/handlers/enrichment-writes.js +226 -0
  1006. package/dist/server/handlers/enrichment-writes.js.map +1 -0
  1007. package/dist/server/handlers/enrichment.js +887 -718
  1008. package/dist/server/handlers/enrichment.js.map +1 -0
  1009. package/dist/server/handlers/image-gen-actions.d.ts +18 -0
  1010. package/dist/server/handlers/image-gen-actions.js +183 -0
  1011. package/dist/server/handlers/image-gen-actions.js.map +1 -0
  1012. package/dist/server/handlers/image-gen-providers.d.ts +29 -0
  1013. package/dist/server/handlers/image-gen-providers.js +161 -0
  1014. package/dist/server/handlers/image-gen-providers.js.map +1 -0
  1015. package/dist/server/handlers/image-gen.js +467 -376
  1016. package/dist/server/handlers/image-gen.js.map +1 -0
  1017. package/dist/server/handlers/image-gen.test.js +205 -0
  1018. package/dist/server/handlers/inventory-audit.d.ts +10 -0
  1019. package/dist/server/handlers/inventory-audit.js +90 -0
  1020. package/dist/server/handlers/inventory-audit.js.map +1 -0
  1021. package/dist/server/handlers/inventory-helpers.d.ts +8 -0
  1022. package/dist/server/handlers/inventory-helpers.js +19 -0
  1023. package/dist/server/handlers/inventory-helpers.js.map +1 -0
  1024. package/dist/server/handlers/inventory-query.d.ts +67 -0
  1025. package/dist/server/handlers/inventory-query.js +299 -0
  1026. package/dist/server/handlers/inventory-query.js.map +1 -0
  1027. package/dist/server/handlers/inventory.js +797 -424
  1028. package/dist/server/handlers/inventory.js.map +1 -0
  1029. package/dist/server/handlers/inventory.test.js +380 -0
  1030. package/dist/server/handlers/kali-background.test.js +222 -0
  1031. package/dist/server/handlers/kali-errors.test.js +92 -0
  1032. package/dist/server/handlers/kali-validation.test.js +234 -0
  1033. package/dist/server/handlers/kali.js +272 -230
  1034. package/dist/server/handlers/kali.js.map +1 -0
  1035. package/dist/server/handlers/llm-providers-actions.test.js +220 -0
  1036. package/dist/server/handlers/llm-providers-anthropic.test.js +239 -0
  1037. package/dist/server/handlers/llm-providers-clients.d.ts +6 -0
  1038. package/dist/server/handlers/llm-providers-clients.js +296 -0
  1039. package/dist/server/handlers/llm-providers-clients.js.map +1 -0
  1040. package/dist/server/handlers/llm-providers-credentials.d.ts +3 -0
  1041. package/dist/server/handlers/llm-providers-credentials.js +109 -0
  1042. package/dist/server/handlers/llm-providers-credentials.js.map +1 -0
  1043. package/dist/server/handlers/llm-providers-failover.test.js +232 -0
  1044. package/dist/server/handlers/llm-providers-models.d.ts +67 -0
  1045. package/dist/server/handlers/llm-providers-models.js +213 -0
  1046. package/dist/server/handlers/llm-providers-models.js.map +1 -0
  1047. package/dist/server/handlers/llm-providers-providers.test.js +300 -0
  1048. package/dist/server/handlers/llm-providers-validation.test.js +239 -0
  1049. package/dist/server/handlers/llm-providers.js +803 -580
  1050. package/dist/server/handlers/llm-providers.js.map +1 -0
  1051. package/dist/server/handlers/local-agent-tools.test.js +224 -0
  1052. package/dist/server/handlers/local-agent.js +133 -105
  1053. package/dist/server/handlers/local-agent.js.map +1 -0
  1054. package/dist/server/handlers/local-agent.test.js +198 -0
  1055. package/dist/server/handlers/local-agent.tools-status.test.js +204 -0
  1056. package/dist/server/handlers/local-agent.validation-exec.test.js +182 -0
  1057. package/dist/server/handlers/media-actions.d.ts +14 -0
  1058. package/dist/server/handlers/media-actions.js +398 -0
  1059. package/dist/server/handlers/media-actions.js.map +1 -0
  1060. package/dist/server/handlers/media-crud.d.ts +9 -0
  1061. package/dist/server/handlers/media-crud.js +365 -0
  1062. package/dist/server/handlers/media-crud.js.map +1 -0
  1063. package/dist/server/handlers/media-upload.d.ts +4 -0
  1064. package/dist/server/handlers/media-upload.js +179 -0
  1065. package/dist/server/handlers/media-upload.js.map +1 -0
  1066. package/dist/server/handlers/media-utils.d.ts +14 -0
  1067. package/dist/server/handlers/media-utils.js +33 -0
  1068. package/dist/server/handlers/media-utils.js.map +1 -0
  1069. package/dist/server/handlers/media.js +1179 -857
  1070. package/dist/server/handlers/media.js.map +1 -0
  1071. package/dist/server/handlers/meta-ads-advanced.d.ts +46 -0
  1072. package/dist/server/handlers/meta-ads-advanced.js +222 -0
  1073. package/dist/server/handlers/meta-ads-advanced.js.map +1 -0
  1074. package/dist/server/handlers/meta-ads-audience-rules.test.js +243 -0
  1075. package/dist/server/handlers/meta-ads-audience-targeting.test.js +205 -0
  1076. package/dist/server/handlers/meta-ads-audiences-targeting.test.js +383 -0
  1077. package/dist/server/handlers/meta-ads-audiences.d.ts +48 -0
  1078. package/dist/server/handlers/meta-ads-audiences.js +214 -0
  1079. package/dist/server/handlers/meta-ads-audiences.js.map +1 -0
  1080. package/dist/server/handlers/meta-ads-crud-ads.test.js +136 -0
  1081. package/dist/server/handlers/meta-ads-crud-campaigns.test.js +189 -0
  1082. package/dist/server/handlers/meta-ads-crud-create.test.js +303 -0
  1083. package/dist/server/handlers/meta-ads-crud-list-update.test.js +259 -0
  1084. package/dist/server/handlers/meta-ads-crud-read.d.ts +78 -0
  1085. package/dist/server/handlers/meta-ads-crud-read.js +204 -0
  1086. package/dist/server/handlers/meta-ads-crud-read.js.map +1 -0
  1087. package/dist/server/handlers/meta-ads-crud-write.d.ts +105 -0
  1088. package/dist/server/handlers/meta-ads-crud-write.js +390 -0
  1089. package/dist/server/handlers/meta-ads-crud-write.js.map +1 -0
  1090. package/dist/server/handlers/meta-ads-crud.d.ts +9 -0
  1091. package/dist/server/handlers/meta-ads-crud.js +12 -0
  1092. package/dist/server/handlers/meta-ads-crud.js.map +1 -0
  1093. package/dist/server/handlers/meta-ads-delete-publish-sync.test.js +282 -0
  1094. package/dist/server/handlers/meta-ads-graph-api.d.ts +25 -0
  1095. package/dist/server/handlers/meta-ads-graph-api.js +155 -0
  1096. package/dist/server/handlers/meta-ads-graph-api.js.map +1 -0
  1097. package/dist/server/handlers/meta-ads-insights.test.js +80 -0
  1098. package/dist/server/handlers/meta-ads-list-get.test.js +237 -0
  1099. package/dist/server/handlers/meta-ads-media.d.ts +16 -0
  1100. package/dist/server/handlers/meta-ads-media.js +205 -0
  1101. package/dist/server/handlers/meta-ads-media.js.map +1 -0
  1102. package/dist/server/handlers/meta-ads-pixels-rules.d.ts +127 -0
  1103. package/dist/server/handlers/meta-ads-pixels-rules.js +463 -0
  1104. package/dist/server/handlers/meta-ads-pixels-rules.js.map +1 -0
  1105. package/dist/server/handlers/meta-ads-publish-ad.d.ts +11 -0
  1106. package/dist/server/handlers/meta-ads-publish-ad.js +229 -0
  1107. package/dist/server/handlers/meta-ads-publish-ad.js.map +1 -0
  1108. package/dist/server/handlers/meta-ads-publish-delete.test.js +254 -0
  1109. package/dist/server/handlers/meta-ads-publish-helpers.js +117 -0
  1110. package/dist/server/handlers/meta-ads-publish-helpers.js.map +1 -0
  1111. package/dist/server/handlers/meta-ads-publish-sync.test.js +205 -0
  1112. package/dist/server/handlers/meta-ads-publish.d.ts +14 -0
  1113. package/dist/server/handlers/meta-ads-publish.js +194 -0
  1114. package/dist/server/handlers/meta-ads-publish.js.map +1 -0
  1115. package/dist/server/handlers/meta-ads-publish.test.js +254 -0
  1116. package/dist/server/handlers/meta-ads-sync-insights.test.js +184 -0
  1117. package/dist/server/handlers/meta-ads-targeting.d.ts +27 -0
  1118. package/dist/server/handlers/meta-ads-targeting.js +323 -0
  1119. package/dist/server/handlers/meta-ads-targeting.js.map +1 -0
  1120. package/dist/server/handlers/meta-ads-types.d.ts +49 -0
  1121. package/dist/server/handlers/meta-ads-types.js +59 -0
  1122. package/dist/server/handlers/meta-ads-types.js.map +1 -0
  1123. package/dist/server/handlers/meta-ads-update.test.js +117 -0
  1124. package/dist/server/handlers/meta-ads.d.ts +25 -0
  1125. package/dist/server/handlers/meta-ads.js +3049 -2084
  1126. package/dist/server/handlers/meta-ads.js.map +1 -0
  1127. package/dist/server/handlers/nodes-channels.test.js +413 -0
  1128. package/dist/server/handlers/nodes-events.test.js +131 -0
  1129. package/dist/server/handlers/nodes-handlers-channels-messages.d.ts +20 -0
  1130. package/dist/server/handlers/nodes-handlers-channels-messages.js +314 -0
  1131. package/dist/server/handlers/nodes-handlers-channels-messages.js.map +1 -0
  1132. package/dist/server/handlers/nodes-handlers-channels.d.ts +19 -0
  1133. package/dist/server/handlers/nodes-handlers-channels.js +225 -0
  1134. package/dist/server/handlers/nodes-handlers-channels.js.map +1 -0
  1135. package/dist/server/handlers/nodes-handlers-mgmt.d.ts +33 -0
  1136. package/dist/server/handlers/nodes-handlers-mgmt.js +418 -0
  1137. package/dist/server/handlers/nodes-handlers-mgmt.js.map +1 -0
  1138. package/dist/server/handlers/nodes-list-delete.test.js +171 -0
  1139. package/dist/server/handlers/nodes-messages-delivery.test.js +208 -0
  1140. package/dist/server/handlers/nodes-messages.test.js +211 -0
  1141. package/dist/server/handlers/nodes-register.test.js +277 -0
  1142. package/dist/server/handlers/nodes-routes-channels.d.ts +9 -0
  1143. package/dist/server/handlers/nodes-routes-channels.js +217 -0
  1144. package/dist/server/handlers/nodes-routes-channels.js.map +1 -0
  1145. package/dist/server/handlers/nodes-routes-crud.d.ts +9 -0
  1146. package/dist/server/handlers/nodes-routes-crud.js +290 -0
  1147. package/dist/server/handlers/nodes-routes-crud.js.map +1 -0
  1148. package/dist/server/handlers/nodes-routes-delivery.d.ts +9 -0
  1149. package/dist/server/handlers/nodes-routes-delivery.js +226 -0
  1150. package/dist/server/handlers/nodes-routes-delivery.js.map +1 -0
  1151. package/dist/server/handlers/nodes-routes-messages.d.ts +9 -0
  1152. package/dist/server/handlers/nodes-routes-messages.js +218 -0
  1153. package/dist/server/handlers/nodes-routes-messages.js.map +1 -0
  1154. package/dist/server/handlers/nodes-routes-register.d.ts +9 -0
  1155. package/dist/server/handlers/nodes-routes-register.js +239 -0
  1156. package/dist/server/handlers/nodes-routes-register.js.map +1 -0
  1157. package/dist/server/handlers/nodes-types.d.ts +82 -0
  1158. package/dist/server/handlers/nodes-types.js +220 -0
  1159. package/dist/server/handlers/nodes-types.js.map +1 -0
  1160. package/dist/server/handlers/nodes-utils.d.ts +70 -0
  1161. package/dist/server/handlers/nodes-utils.js +214 -0
  1162. package/dist/server/handlers/nodes-utils.js.map +1 -0
  1163. package/dist/server/handlers/nodes.js +1321 -913
  1164. package/dist/server/handlers/nodes.js.map +1 -0
  1165. package/dist/server/handlers/nodes.test.js +353 -0
  1166. package/dist/server/handlers/operations.js +183 -157
  1167. package/dist/server/handlers/operations.js.map +1 -0
  1168. package/dist/server/handlers/operations.test.js +136 -0
  1169. package/dist/server/handlers/platform-telemetry.d.ts +10 -0
  1170. package/dist/server/handlers/platform-telemetry.js +372 -0
  1171. package/dist/server/handlers/platform-telemetry.js.map +1 -0
  1172. package/dist/server/handlers/platform-telemetry.test.js +200 -0
  1173. package/dist/server/handlers/platform-websearch.test.js +160 -0
  1174. package/dist/server/handlers/platform.js +346 -210
  1175. package/dist/server/handlers/platform.js.map +1 -0
  1176. package/dist/server/handlers/remove-bg.js +118 -86
  1177. package/dist/server/handlers/remove-bg.js.map +1 -0
  1178. package/dist/server/handlers/sessions-handlers.d.ts +23 -0
  1179. package/dist/server/handlers/sessions-handlers.js +374 -0
  1180. package/dist/server/handlers/sessions-handlers.js.map +1 -0
  1181. package/dist/server/handlers/storefront-cart.d.ts +31 -0
  1182. package/dist/server/handlers/storefront-cart.js +381 -0
  1183. package/dist/server/handlers/storefront-cart.js.map +1 -0
  1184. package/dist/server/handlers/storefront.js +586 -446
  1185. package/dist/server/handlers/storefront.js.map +1 -0
  1186. package/dist/server/handlers/storefront.test.js +329 -0
  1187. package/dist/server/handlers/supply-chain-transfers.d.ts +10 -0
  1188. package/dist/server/handlers/supply-chain-transfers.js +214 -0
  1189. package/dist/server/handlers/supply-chain-transfers.js.map +1 -0
  1190. package/dist/server/handlers/supply-chain-utils.d.ts +2 -0
  1191. package/dist/server/handlers/supply-chain-utils.js +21 -0
  1192. package/dist/server/handlers/supply-chain-utils.js.map +1 -0
  1193. package/dist/server/handlers/supply-chain.js +546 -326
  1194. package/dist/server/handlers/supply-chain.js.map +1 -0
  1195. package/dist/server/handlers/supply-chain.test.js +347 -0
  1196. package/dist/server/handlers/transcription.js +106 -97
  1197. package/dist/server/handlers/transcription.js.map +1 -0
  1198. package/dist/server/handlers/transcription.test.js +118 -0
  1199. package/dist/server/handlers/video-gen-providers.d.ts +30 -0
  1200. package/dist/server/handlers/video-gen-providers.js +205 -0
  1201. package/dist/server/handlers/video-gen-providers.js.map +1 -0
  1202. package/dist/server/handlers/video-gen-sora.d.ts +5 -0
  1203. package/dist/server/handlers/video-gen-sora.js +87 -0
  1204. package/dist/server/handlers/video-gen-sora.js.map +1 -0
  1205. package/dist/server/handlers/video-gen-veo.js +114 -0
  1206. package/dist/server/handlers/video-gen-veo.js.map +1 -0
  1207. package/dist/server/handlers/video-gen.js +593 -424
  1208. package/dist/server/handlers/video-gen.js.map +1 -0
  1209. package/dist/server/handlers/video-gen.test.js +146 -0
  1210. package/dist/server/handlers/voice-handlers-audio.d.ts +9 -0
  1211. package/dist/server/handlers/voice-handlers-audio.js +229 -0
  1212. package/dist/server/handlers/voice-handlers-audio.js.map +1 -0
  1213. package/dist/server/handlers/voice-handlers-music.d.ts +5 -0
  1214. package/dist/server/handlers/voice-handlers-music.js +124 -0
  1215. package/dist/server/handlers/voice-handlers-music.js.map +1 -0
  1216. package/dist/server/handlers/voice-handlers-pvc.d.ts +8 -0
  1217. package/dist/server/handlers/voice-handlers-pvc.js +345 -0
  1218. package/dist/server/handlers/voice-handlers-pvc.js.map +1 -0
  1219. package/dist/server/handlers/voice-handlers-tts.d.ts +5 -0
  1220. package/dist/server/handlers/voice-handlers-tts.js +142 -0
  1221. package/dist/server/handlers/voice-handlers-tts.js.map +1 -0
  1222. package/dist/server/handlers/voice-handlers-voices.d.ts +8 -0
  1223. package/dist/server/handlers/voice-handlers-voices.js +321 -0
  1224. package/dist/server/handlers/voice-handlers-voices.js.map +1 -0
  1225. package/dist/server/handlers/voice-utils.d.ts +32 -0
  1226. package/dist/server/handlers/voice-utils.js +281 -0
  1227. package/dist/server/handlers/voice-utils.js.map +1 -0
  1228. package/dist/server/handlers/voice.js +1458 -1039
  1229. package/dist/server/handlers/voice.js.map +1 -0
  1230. package/dist/server/handlers/voice.test.js +153 -0
  1231. package/dist/server/handlers/workflow-crud-advanced.d.ts +8 -0
  1232. package/dist/server/handlers/workflow-crud-advanced.js +275 -0
  1233. package/dist/server/handlers/workflow-crud-advanced.js.map +1 -0
  1234. package/dist/server/handlers/workflow-crud-core.d.ts +8 -0
  1235. package/dist/server/handlers/workflow-crud-core.js +286 -0
  1236. package/dist/server/handlers/workflow-crud-core.js.map +1 -0
  1237. package/dist/server/handlers/workflow-crud-events.d.ts +8 -0
  1238. package/dist/server/handlers/workflow-crud-events.js +483 -0
  1239. package/dist/server/handlers/workflow-crud-events.js.map +1 -0
  1240. package/dist/server/handlers/workflow-crud-runs-lifecycle.d.ts +8 -0
  1241. package/dist/server/handlers/workflow-crud-runs-lifecycle.js +231 -0
  1242. package/dist/server/handlers/workflow-crud-runs-lifecycle.js.map +1 -0
  1243. package/dist/server/handlers/workflow-crud-runs-versioning.d.ts +8 -0
  1244. package/dist/server/handlers/workflow-crud-runs-versioning.js +319 -0
  1245. package/dist/server/handlers/workflow-crud-runs-versioning.js.map +1 -0
  1246. package/dist/server/handlers/workflow-crud-runs.d.ts +8 -0
  1247. package/dist/server/handlers/workflow-crud-runs.js +18 -0
  1248. package/dist/server/handlers/workflow-crud-runs.js.map +1 -0
  1249. package/dist/server/handlers/workflow-steps-advancement.d.ts +13 -0
  1250. package/dist/server/handlers/workflow-steps-advancement.js +197 -0
  1251. package/dist/server/handlers/workflow-steps-advancement.js.map +1 -0
  1252. package/dist/server/handlers/workflow-steps-circuit-breaker.d.ts +7 -0
  1253. package/dist/server/handlers/workflow-steps-circuit-breaker.js +131 -0
  1254. package/dist/server/handlers/workflow-steps-circuit-breaker.js.map +1 -0
  1255. package/dist/server/handlers/workflow-steps-completion.d.ts +5 -0
  1256. package/dist/server/handlers/workflow-steps-completion.js +254 -0
  1257. package/dist/server/handlers/workflow-steps-completion.js.map +1 -0
  1258. package/dist/server/handlers/workflow-steps-cron.d.ts +4 -0
  1259. package/dist/server/handlers/workflow-steps-cron.js +259 -0
  1260. package/dist/server/handlers/workflow-steps-cron.js.map +1 -0
  1261. package/dist/server/handlers/workflow-steps-engine-execute.d.ts +3 -0
  1262. package/dist/server/handlers/workflow-steps-engine-execute.js +388 -0
  1263. package/dist/server/handlers/workflow-steps-engine-execute.js.map +1 -0
  1264. package/dist/server/handlers/workflow-steps-engine-inline.d.ts +2 -0
  1265. package/dist/server/handlers/workflow-steps-engine-inline.js +91 -0
  1266. package/dist/server/handlers/workflow-steps-engine-inline.js.map +1 -0
  1267. package/dist/server/handlers/workflow-steps-engine-persist.d.ts +5 -0
  1268. package/dist/server/handlers/workflow-steps-engine-persist.js +192 -0
  1269. package/dist/server/handlers/workflow-steps-engine-persist.js.map +1 -0
  1270. package/dist/server/handlers/workflow-steps-engine-process.d.ts +7 -0
  1271. package/dist/server/handlers/workflow-steps-engine-process.js +290 -0
  1272. package/dist/server/handlers/workflow-steps-engine-process.js.map +1 -0
  1273. package/dist/server/handlers/workflow-steps-engine.d.ts +3 -0
  1274. package/dist/server/handlers/workflow-steps-engine.js +10 -0
  1275. package/dist/server/handlers/workflow-steps-engine.js.map +1 -0
  1276. package/dist/server/handlers/workflow-steps-executors-llm.d.ts +7 -0
  1277. package/dist/server/handlers/workflow-steps-executors-llm.js +265 -0
  1278. package/dist/server/handlers/workflow-steps-executors-llm.js.map +1 -0
  1279. package/dist/server/handlers/workflow-steps-executors.d.ts +9 -0
  1280. package/dist/server/handlers/workflow-steps-executors.js +188 -0
  1281. package/dist/server/handlers/workflow-steps-executors.js.map +1 -0
  1282. package/dist/server/handlers/workflow-steps-maintenance.d.ts +4 -0
  1283. package/dist/server/handlers/workflow-steps-maintenance.js +256 -0
  1284. package/dist/server/handlers/workflow-steps-maintenance.js.map +1 -0
  1285. package/dist/server/handlers/workflow-steps-types.d.ts +84 -0
  1286. package/dist/server/handlers/workflow-steps-types.js +179 -0
  1287. package/dist/server/handlers/workflow-steps-types.js.map +1 -0
  1288. package/dist/server/handlers/workflow-steps-webhook.d.ts +5 -0
  1289. package/dist/server/handlers/workflow-steps-webhook.js +179 -0
  1290. package/dist/server/handlers/workflow-steps-webhook.js.map +1 -0
  1291. package/dist/server/handlers/workflow-steps.js +2837 -2116
  1292. package/dist/server/handlers/workflow-steps.js.map +1 -0
  1293. package/dist/server/handlers/workflow-steps.test.js +330 -0
  1294. package/dist/server/handlers/workflows-extras.test.js +65 -0
  1295. package/dist/server/handlers/workflows.js +1630 -933
  1296. package/dist/server/handlers/workflows.js.map +1 -0
  1297. package/dist/server/handlers/workflows.part2.test.js +170 -0
  1298. package/dist/server/handlers/workflows.test.js +281 -0
  1299. package/dist/server/index.js +1702 -2596
  1300. package/dist/server/index.js.map +1 -0
  1301. package/dist/server/lib/__tests__/batch-client-conversion-jsonl.test.js +171 -0
  1302. package/dist/server/lib/__tests__/batch-client-polling.test.js +292 -0
  1303. package/dist/server/lib/__tests__/batch-client-queue.test.js +270 -0
  1304. package/dist/server/lib/__tests__/clickhouse-buffer.test.js +236 -0
  1305. package/dist/server/lib/__tests__/code-worker-edge-cases.test.js +118 -0
  1306. package/dist/server/lib/__tests__/code-worker-pool-execute.test.js +193 -0
  1307. package/dist/server/lib/__tests__/code-worker-pool-execution.test.js +165 -0
  1308. package/dist/server/lib/__tests__/code-worker-pool-init.test.js +131 -0
  1309. package/dist/server/lib/__tests__/code-worker-pool.test.js +194 -0
  1310. package/dist/server/lib/__tests__/code-worker-sandbox-ops.test.js +123 -0
  1311. package/dist/server/lib/__tests__/code-worker-sandbox.test.js +217 -0
  1312. package/dist/server/lib/__tests__/code-worker.test.js +179 -0
  1313. package/dist/server/lib/__tests__/compaction-service-generate.test.js +229 -0
  1314. package/dist/server/lib/__tests__/compaction-service.test.js +319 -0
  1315. package/dist/server/lib/__tests__/otel.test.js +146 -0
  1316. package/dist/server/lib/__tests__/prompt-sanitizer-validation.test.js +165 -0
  1317. package/dist/server/lib/__tests__/prompt-sanitizer.sanitize.test.js +343 -0
  1318. package/dist/server/lib/__tests__/prompt-sanitizer.test.js +328 -0
  1319. package/dist/server/lib/__tests__/prompt-sanitizer.validate-tool.test.js +145 -0
  1320. package/dist/server/lib/__tests__/provider-capabilities.test.js +263 -0
  1321. package/dist/server/lib/__tests__/provider-failover-routing.test.js +145 -0
  1322. package/dist/server/lib/__tests__/provider-failover-state.test.js +131 -0
  1323. package/dist/server/lib/__tests__/rate-limiter-budgets.test.js +216 -0
  1324. package/dist/server/lib/__tests__/rate-limiter.budgets-tools.test.js +113 -0
  1325. package/dist/server/lib/__tests__/rate-limiter.check-request.test.js +141 -0
  1326. package/dist/server/lib/__tests__/rate-limiter.stats-lifecycle.test.js +135 -0
  1327. package/dist/server/lib/__tests__/rate-limiter.test.js +207 -0
  1328. package/dist/server/lib/__tests__/server-agent-loop-abort-conditions.test.js +544 -0
  1329. package/dist/server/lib/__tests__/server-agent-loop-abort.part2.test.js +504 -0
  1330. package/dist/server/lib/__tests__/server-agent-loop-abort.test.js +396 -0
  1331. package/dist/server/lib/__tests__/server-agent-loop-compaction.test.js +397 -0
  1332. package/dist/server/lib/__tests__/server-agent-loop-failover.test.js +356 -0
  1333. package/dist/server/lib/__tests__/server-agent-loop-features-caching.test.js +519 -0
  1334. package/dist/server/lib/__tests__/server-agent-loop-features-edges.test.js +512 -0
  1335. package/dist/server/lib/__tests__/server-subagent-bailout.test.js +194 -0
  1336. package/dist/server/lib/__tests__/server-subagent-basics.test.js +348 -0
  1337. package/dist/server/lib/__tests__/server-subagent-errors-abort.test.js +319 -0
  1338. package/dist/server/lib/__tests__/server-subagent-errors-progress.test.js +253 -0
  1339. package/dist/server/lib/__tests__/server-subagent-errors.part2.test.js +253 -0
  1340. package/dist/server/lib/__tests__/server-subagent-errors.test.js +319 -0
  1341. package/dist/server/lib/__tests__/session-checkpoint-load.test.js +275 -0
  1342. package/dist/server/lib/__tests__/session-checkpoint-save.test.js +159 -0
  1343. package/dist/server/lib/__tests__/ssrf-guard.test.js +93 -0
  1344. package/dist/server/lib/__tests__/supabase-client.test.js +111 -0
  1345. package/dist/server/lib/__tests__/template-resolver.test.js +317 -0
  1346. package/dist/server/lib/__tests__/utils-timeout.test.js +49 -0
  1347. package/dist/server/lib/__tests__/utils.test.js +322 -0
  1348. package/dist/server/lib/agent-loop-executor.d.ts +7 -0
  1349. package/dist/server/lib/agent-loop-executor.js +224 -0
  1350. package/dist/server/lib/agent-loop-executor.js.map +1 -0
  1351. package/dist/server/lib/agent-loop-turn.d.ts +53 -0
  1352. package/dist/server/lib/agent-loop-turn.js +166 -0
  1353. package/dist/server/lib/agent-loop-turn.js.map +1 -0
  1354. package/dist/server/lib/agent-loop-types.d.ts +118 -0
  1355. package/dist/server/lib/agent-loop-types.js +53 -0
  1356. package/dist/server/lib/agent-loop-types.js.map +1 -0
  1357. package/dist/server/lib/batch-client-anthropic.d.ts +10 -0
  1358. package/dist/server/lib/batch-client-anthropic.js +144 -0
  1359. package/dist/server/lib/batch-client-anthropic.js.map +1 -0
  1360. package/dist/server/lib/batch-client-openai.d.ts +10 -0
  1361. package/dist/server/lib/batch-client-openai.js +172 -0
  1362. package/dist/server/lib/batch-client-openai.js.map +1 -0
  1363. package/dist/server/lib/batch-client-types.d.ts +54 -0
  1364. package/dist/server/lib/batch-client-types.js +27 -0
  1365. package/dist/server/lib/batch-client-types.js.map +1 -0
  1366. package/dist/server/lib/batch-client.js +471 -409
  1367. package/dist/server/lib/batch-client.js.map +1 -0
  1368. package/dist/server/lib/clickhouse-buffer.js +118 -104
  1369. package/dist/server/lib/clickhouse-buffer.js.map +1 -0
  1370. package/dist/server/lib/clickhouse-client.js +107 -107
  1371. package/dist/server/lib/clickhouse-client.js.map +1 -0
  1372. package/dist/server/lib/coa-renderer-components.d.ts +15 -0
  1373. package/dist/server/lib/coa-renderer-components.js +361 -0
  1374. package/dist/server/lib/coa-renderer-components.js.map +1 -0
  1375. package/dist/server/lib/coa-renderer-pages.d.ts +15 -0
  1376. package/dist/server/lib/coa-renderer-pages.js +467 -0
  1377. package/dist/server/lib/coa-renderer-pages.js.map +1 -0
  1378. package/dist/server/lib/coa-renderer-styles.d.ts +525 -0
  1379. package/dist/server/lib/coa-renderer-styles.js +540 -0
  1380. package/dist/server/lib/coa-renderer-styles.js.map +1 -0
  1381. package/dist/server/lib/coa-renderer-tokens.d.ts +107 -0
  1382. package/dist/server/lib/coa-renderer-tokens.js +46 -0
  1383. package/dist/server/lib/coa-renderer-tokens.js.map +1 -0
  1384. package/dist/server/lib/coa-renderer.js +1786 -356
  1385. package/dist/server/lib/coa-renderer.js.map +1 -0
  1386. package/dist/server/lib/code-worker-pool.js +227 -177
  1387. package/dist/server/lib/code-worker-pool.js.map +1 -0
  1388. package/dist/server/lib/code-worker.js +174 -164
  1389. package/dist/server/lib/code-worker.js.map +1 -0
  1390. package/dist/server/lib/compaction-service.d.ts +2 -12
  1391. package/dist/server/lib/compaction-service.js +74 -184
  1392. package/dist/server/lib/compaction-service.js.map +1 -0
  1393. package/dist/server/lib/logger.js +36 -24
  1394. package/dist/server/lib/logger.js.map +1 -0
  1395. package/dist/server/lib/otel.js +101 -80
  1396. package/dist/server/lib/otel.js.map +1 -0
  1397. package/dist/server/lib/pdf-renderer-calc.d.ts +7 -0
  1398. package/dist/server/lib/pdf-renderer-calc.js +272 -0
  1399. package/dist/server/lib/pdf-renderer-calc.js.map +1 -0
  1400. package/dist/server/lib/pdf-renderer-generation.d.ts +73 -0
  1401. package/dist/server/lib/pdf-renderer-generation.js +298 -0
  1402. package/dist/server/lib/pdf-renderer-generation.js.map +1 -0
  1403. package/dist/server/lib/pdf-renderer-layout.d.ts +2 -0
  1404. package/dist/server/lib/pdf-renderer-layout.js +144 -0
  1405. package/dist/server/lib/pdf-renderer-layout.js.map +1 -0
  1406. package/dist/server/lib/pdf-renderer-validation.d.ts +14 -0
  1407. package/dist/server/lib/pdf-renderer-validation.js +220 -0
  1408. package/dist/server/lib/pdf-renderer-validation.js.map +1 -0
  1409. package/dist/server/lib/pdf-renderer.js +952 -788
  1410. package/dist/server/lib/pdf-renderer.js.map +1 -0
  1411. package/dist/server/lib/prompt-sanitizer.js +188 -108
  1412. package/dist/server/lib/prompt-sanitizer.js.map +1 -0
  1413. package/dist/server/lib/provider-capabilities.js +136 -138
  1414. package/dist/server/lib/provider-capabilities.js.map +1 -0
  1415. package/dist/server/lib/provider-failover.js +190 -168
  1416. package/dist/server/lib/provider-failover.js.map +1 -0
  1417. package/dist/server/lib/rate-limiter.js +186 -117
  1418. package/dist/server/lib/rate-limiter.js.map +1 -0
  1419. package/dist/server/lib/react-pdf-layout-elements.d.ts +8 -0
  1420. package/dist/server/lib/react-pdf-layout-elements.js +366 -0
  1421. package/dist/server/lib/react-pdf-layout-elements.js.map +1 -0
  1422. package/dist/server/lib/react-pdf-layout-labels.d.ts +38 -0
  1423. package/dist/server/lib/react-pdf-layout-labels.js +217 -0
  1424. package/dist/server/lib/react-pdf-layout-labels.js.map +1 -0
  1425. package/dist/server/lib/react-pdf-layout.js +551 -382
  1426. package/dist/server/lib/react-pdf-layout.js.map +1 -0
  1427. package/dist/server/lib/server-agent-loop-init.d.ts +28 -0
  1428. package/dist/server/lib/server-agent-loop-init.js +138 -0
  1429. package/dist/server/lib/server-agent-loop-init.js.map +1 -0
  1430. package/dist/server/lib/server-agent-loop-v2.d.ts +110 -0
  1431. package/dist/server/lib/server-agent-loop-v2.js +340 -0
  1432. package/dist/server/lib/server-agent-loop-v2.js.map +1 -0
  1433. package/dist/server/lib/server-agent-loop.d.ts +4 -1
  1434. package/dist/server/lib/server-agent-loop.js +907 -634
  1435. package/dist/server/lib/server-agent-loop.js.map +1 -0
  1436. package/dist/server/lib/server-subagent-loop.d.ts +3 -0
  1437. package/dist/server/lib/server-subagent-loop.js +267 -0
  1438. package/dist/server/lib/server-subagent-loop.js.map +1 -0
  1439. package/dist/server/lib/server-subagent.js +260 -164
  1440. package/dist/server/lib/server-subagent.js.map +1 -0
  1441. package/dist/server/lib/session-checkpoint.js +105 -96
  1442. package/dist/server/lib/session-checkpoint.js.map +1 -0
  1443. package/dist/server/lib/session-types.d.ts +106 -0
  1444. package/dist/server/lib/session-types.js +2 -0
  1445. package/dist/server/lib/session-types.js.map +1 -0
  1446. package/dist/server/lib/ssrf-guard.js +193 -184
  1447. package/dist/server/lib/ssrf-guard.js.map +1 -0
  1448. package/dist/server/lib/supabase-client.js +94 -82
  1449. package/dist/server/lib/supabase-client.js.map +1 -0
  1450. package/dist/server/lib/template-resolver.js +154 -176
  1451. package/dist/server/lib/template-resolver.js.map +1 -0
  1452. package/dist/server/lib/utils.js +242 -133
  1453. package/dist/server/lib/utils.js.map +1 -0
  1454. package/dist/server/local-agent-gateway-api.d.ts +34 -0
  1455. package/dist/server/local-agent-gateway-api.js +156 -0
  1456. package/dist/server/local-agent-gateway-api.js.map +1 -0
  1457. package/dist/server/local-agent-gateway-handler.d.ts +3 -0
  1458. package/dist/server/local-agent-gateway-handler.js +292 -0
  1459. package/dist/server/local-agent-gateway-handler.js.map +1 -0
  1460. package/dist/server/local-agent-gateway-init.d.ts +21 -0
  1461. package/dist/server/local-agent-gateway-init.js +143 -0
  1462. package/dist/server/local-agent-gateway-init.js.map +1 -0
  1463. package/dist/server/local-agent-gateway-messages.d.ts +12 -0
  1464. package/dist/server/local-agent-gateway-messages.js +108 -0
  1465. package/dist/server/local-agent-gateway-messages.js.map +1 -0
  1466. package/dist/server/local-agent-gateway-stats.d.ts +53 -0
  1467. package/dist/server/local-agent-gateway-stats.js +129 -0
  1468. package/dist/server/local-agent-gateway-stats.js.map +1 -0
  1469. package/dist/server/local-agent-gateway-types.d.ts +94 -0
  1470. package/dist/server/local-agent-gateway-types.js +78 -0
  1471. package/dist/server/local-agent-gateway-types.js.map +1 -0
  1472. package/dist/server/local-agent-gateway.d.ts +8 -2
  1473. package/dist/server/local-agent-gateway.js +825 -627
  1474. package/dist/server/local-agent-gateway.js.map +1 -0
  1475. package/dist/server/providers/__tests__/anthropic-adapter.test.js +228 -0
  1476. package/dist/server/providers/__tests__/anthropic-betas-toolchoice.test.js +257 -0
  1477. package/dist/server/providers/__tests__/anthropic-errors.test.js +262 -0
  1478. package/dist/server/providers/__tests__/anthropic-stream-core.test.js +275 -0
  1479. package/dist/server/providers/__tests__/anthropic-streaming-betas.test.js +247 -0
  1480. package/dist/server/providers/__tests__/anthropic-streaming-core.test.js +275 -0
  1481. package/dist/server/providers/__tests__/bedrock-config.test.js +177 -0
  1482. package/dist/server/providers/__tests__/bedrock-stream-behavior-streaming.test.js +272 -0
  1483. package/dist/server/providers/__tests__/bedrock-stream-behavior-toolchoice.test.js +214 -0
  1484. package/dist/server/providers/__tests__/bedrock-stream-behavior.part2.test.js +165 -0
  1485. package/dist/server/providers/__tests__/bedrock-stream-behavior.test.js +309 -0
  1486. package/dist/server/providers/__tests__/bedrock-stream-body-credentials.test.js +170 -0
  1487. package/dist/server/providers/__tests__/bedrock-stream-body-extras.test.js +183 -0
  1488. package/dist/server/providers/__tests__/bedrock-stream-body-request.test.js +305 -0
  1489. package/dist/server/providers/__tests__/bedrock-stream-body.part2.test.js +305 -0
  1490. package/dist/server/providers/__tests__/bedrock-stream-body.test.js +175 -0
  1491. package/dist/server/providers/__tests__/bedrock-stream-errors.test.js +165 -0
  1492. package/dist/server/providers/__tests__/gemini-config-methods.test.js +182 -0
  1493. package/dist/server/providers/__tests__/gemini-config-streaming.test.js +257 -0
  1494. package/dist/server/providers/__tests__/gemini-conversion-messages.test.js +247 -0
  1495. package/dist/server/providers/__tests__/gemini-conversion-schema.test.js +365 -0
  1496. package/dist/server/providers/__tests__/gemini-tools-choice.test.js +221 -0
  1497. package/dist/server/providers/__tests__/gemini-tools-fn.test.js +252 -0
  1498. package/dist/server/providers/__tests__/openai-config.test.js +194 -0
  1499. package/dist/server/providers/__tests__/openai-conversion.test.js +276 -0
  1500. package/dist/server/providers/__tests__/openai-messages.test.js +261 -0
  1501. package/dist/server/providers/__tests__/openai-streaming.test.js +394 -0
  1502. package/dist/server/providers/__tests__/openai-tools-cache.test.js +227 -0
  1503. package/dist/server/providers/__tests__/registry.test.js +183 -0
  1504. package/dist/server/providers/__tests__/shared.test.js +297 -0
  1505. package/dist/server/providers/anthropic.js +250 -172
  1506. package/dist/server/providers/anthropic.js.map +1 -0
  1507. package/dist/server/providers/bedrock.js +217 -158
  1508. package/dist/server/providers/bedrock.js.map +1 -0
  1509. package/dist/server/providers/gemini-conversion.d.ts +17 -0
  1510. package/dist/server/providers/gemini-conversion.js +185 -0
  1511. package/dist/server/providers/gemini-conversion.js.map +1 -0
  1512. package/dist/server/providers/gemini-streaming.d.ts +18 -0
  1513. package/dist/server/providers/gemini-streaming.js +187 -0
  1514. package/dist/server/providers/gemini-streaming.js.map +1 -0
  1515. package/dist/server/providers/gemini.js +548 -418
  1516. package/dist/server/providers/gemini.js.map +1 -0
  1517. package/dist/server/providers/openai-adapter.d.ts +15 -0
  1518. package/dist/server/providers/openai-adapter.js +423 -0
  1519. package/dist/server/providers/openai-adapter.js.map +1 -0
  1520. package/dist/server/providers/openai-conversion.d.ts +21 -0
  1521. package/dist/server/providers/openai-conversion.js +191 -0
  1522. package/dist/server/providers/openai-conversion.js.map +1 -0
  1523. package/dist/server/providers/openai.js +571 -437
  1524. package/dist/server/providers/openai.js.map +1 -0
  1525. package/dist/server/providers/registry.js +23 -18
  1526. package/dist/server/providers/registry.js.map +1 -0
  1527. package/dist/server/providers/shared.js +123 -95
  1528. package/dist/server/providers/shared.js.map +1 -0
  1529. package/dist/server/providers/types.js +1 -11
  1530. package/dist/server/providers/types.js.map +1 -0
  1531. package/dist/server/proxy-handlers.js +209 -165
  1532. package/dist/server/proxy-handlers.js.map +1 -0
  1533. package/dist/server/server-agent.d.ts +21 -0
  1534. package/dist/server/server-agent.js +162 -0
  1535. package/dist/server/server-agent.js.map +1 -0
  1536. package/dist/server/server-chat.d.ts +6 -0
  1537. package/dist/server/server-chat.js +210 -0
  1538. package/dist/server/server-chat.js.map +1 -0
  1539. package/dist/server/server-cost-guard.d.ts +16 -0
  1540. package/dist/server/server-cost-guard.js +141 -0
  1541. package/dist/server/server-cost-guard.js.map +1 -0
  1542. package/dist/server/server-executors.d.ts +10 -0
  1543. package/dist/server/server-executors.js +110 -0
  1544. package/dist/server/server-executors.js.map +1 -0
  1545. package/dist/server/server-helpers.d.ts +49 -0
  1546. package/dist/server/server-helpers.js +210 -0
  1547. package/dist/server/server-helpers.js.map +1 -0
  1548. package/dist/server/server-persist.d.ts +43 -0
  1549. package/dist/server/server-persist.js +249 -0
  1550. package/dist/server/server-persist.js.map +1 -0
  1551. package/dist/server/server-rate-limit.d.ts +9 -0
  1552. package/dist/server/server-rate-limit.js +77 -0
  1553. package/dist/server/server-rate-limit.js.map +1 -0
  1554. package/dist/server/server-routes-approvals.d.ts +4 -0
  1555. package/dist/server/server-routes-approvals.js +238 -0
  1556. package/dist/server/server-routes-approvals.js.map +1 -0
  1557. package/dist/server/server-routes-auth.d.ts +7 -0
  1558. package/dist/server/server-routes-auth.js +532 -0
  1559. package/dist/server/server-routes-auth.js.map +1 -0
  1560. package/dist/server/server-routes-events.d.ts +4 -0
  1561. package/dist/server/server-routes-events.js +167 -0
  1562. package/dist/server/server-routes-events.js.map +1 -0
  1563. package/dist/server/server-routes-public.d.ts +24 -0
  1564. package/dist/server/server-routes-public.js +453 -0
  1565. package/dist/server/server-routes-public.js.map +1 -0
  1566. package/dist/server/server-routes-waitpoints.d.ts +4 -0
  1567. package/dist/server/server-routes-waitpoints.js +190 -0
  1568. package/dist/server/server-routes-waitpoints.js.map +1 -0
  1569. package/dist/server/server-routes-webchat.d.ts +14 -0
  1570. package/dist/server/server-routes-webchat.js +307 -0
  1571. package/dist/server/server-routes-webchat.js.map +1 -0
  1572. package/dist/server/server-routes-workflows.d.ts +5 -0
  1573. package/dist/server/server-routes-workflows.js +289 -0
  1574. package/dist/server/server-routes-workflows.js.map +1 -0
  1575. package/dist/server/server-sse.d.ts +13 -0
  1576. package/dist/server/server-sse.js +197 -0
  1577. package/dist/server/server-sse.js.map +1 -0
  1578. package/dist/server/server-store-circuit-breaker.d.ts +32 -0
  1579. package/dist/server/server-store-circuit-breaker.js +211 -0
  1580. package/dist/server/server-store-circuit-breaker.js.map +1 -0
  1581. package/dist/server/server-store.d.ts +5 -0
  1582. package/dist/server/server-store.js +71 -0
  1583. package/dist/server/server-store.js.map +1 -0
  1584. package/dist/server/server-trace.d.ts +41 -0
  1585. package/dist/server/server-trace.js +133 -0
  1586. package/dist/server/server-trace.js.map +1 -0
  1587. package/dist/server/server-worker.d.ts +4 -0
  1588. package/dist/server/server-worker.js +127 -0
  1589. package/dist/server/server-worker.js.map +1 -0
  1590. package/dist/server/session-events.d.ts +27 -0
  1591. package/dist/server/session-events.js +69 -0
  1592. package/dist/server/session-events.js.map +1 -0
  1593. package/dist/server/session-manager.d.ts +52 -0
  1594. package/dist/server/session-manager.js +502 -0
  1595. package/dist/server/session-manager.js.map +1 -0
  1596. package/dist/server/tool-router-discovery.d.ts +33 -0
  1597. package/dist/server/tool-router-discovery.js +218 -0
  1598. package/dist/server/tool-router-discovery.js.map +1 -0
  1599. package/dist/server/tool-router-types.d.ts +91 -0
  1600. package/dist/server/tool-router-types.js +336 -0
  1601. package/dist/server/tool-router-types.js.map +1 -0
  1602. package/dist/server/tool-router-user-tools.d.ts +16 -0
  1603. package/dist/server/tool-router-user-tools.js +269 -0
  1604. package/dist/server/tool-router-user-tools.js.map +1 -0
  1605. package/dist/server/tool-router.js +959 -599
  1606. package/dist/server/tool-router.js.map +1 -0
  1607. package/dist/server/validation-schemas.d.ts +12 -0
  1608. package/dist/server/validation-schemas.js +251 -0
  1609. package/dist/server/validation-schemas.js.map +1 -0
  1610. package/dist/server/validation.js +248 -188
  1611. package/dist/server/validation.js.map +1 -0
  1612. package/dist/server/worker.js +202 -133
  1613. package/dist/server/worker.js.map +1 -0
  1614. package/dist/setup.d.ts +2 -2
  1615. package/dist/setup.js +151 -147
  1616. package/dist/setup.js.map +1 -0
  1617. package/dist/shared/agent-core-config.d.ts +12 -0
  1618. package/dist/shared/agent-core-config.js +77 -0
  1619. package/dist/shared/agent-core-config.js.map +1 -0
  1620. package/dist/shared/agent-core-config.test.js +132 -0
  1621. package/dist/shared/agent-core-context-thinking.test.js +293 -0
  1622. package/dist/shared/agent-core-loop-calls.test.js +174 -0
  1623. package/dist/shared/agent-core-loop-detector-bail.test.js +201 -0
  1624. package/dist/shared/agent-core-loop-detector.test.js +195 -0
  1625. package/dist/shared/agent-core-loop-errors.test.js +258 -0
  1626. package/dist/shared/agent-core-loop.d.ts +53 -0
  1627. package/dist/shared/agent-core-loop.js +251 -0
  1628. package/dist/shared/agent-core-loop.js.map +1 -0
  1629. package/dist/shared/agent-core-pricing.test.js +191 -0
  1630. package/dist/shared/agent-core-sanitize-retry.test.js +129 -0
  1631. package/dist/shared/agent-core-tools.d.ts +32 -0
  1632. package/dist/shared/agent-core-tools.js +323 -0
  1633. package/dist/shared/agent-core-tools.js.map +1 -0
  1634. package/dist/shared/agent-core-types.d.ts +182 -0
  1635. package/dist/shared/agent-core-types.js +265 -0
  1636. package/dist/shared/agent-core-types.js.map +1 -0
  1637. package/dist/shared/agent-core.d.ts +115 -26
  1638. package/dist/shared/agent-core.js +956 -522
  1639. package/dist/shared/agent-core.js.map +1 -0
  1640. package/dist/shared/agent-loop-base.d.ts +130 -0
  1641. package/dist/shared/agent-loop-base.js +347 -0
  1642. package/dist/shared/agent-loop-base.js.map +1 -0
  1643. package/dist/shared/anthropic-types.js +1 -6
  1644. package/dist/shared/anthropic-types.js.map +1 -0
  1645. package/dist/shared/api-client-build-request.test.js +228 -0
  1646. package/dist/shared/api-client-build-system-caching.test.js +107 -0
  1647. package/dist/shared/api-client-build.test.js +223 -0
  1648. package/dist/shared/api-client-config.d.ts +21 -0
  1649. package/dist/shared/api-client-helpers.d.ts +57 -0
  1650. package/dist/shared/api-client-helpers.test.js +261 -0
  1651. package/dist/shared/api-client-proxy-happy.test.js +255 -0
  1652. package/dist/shared/api-client-proxy-retry.test.js +307 -0
  1653. package/dist/shared/api-client-proxy.d.ts +26 -0
  1654. package/dist/shared/api-client-proxy.test.js +255 -0
  1655. package/dist/shared/api-client-retry.test.js +307 -0
  1656. package/dist/shared/api-client-system-trimming.test.js +261 -0
  1657. package/dist/shared/api-client-trimming.d.ts +36 -0
  1658. package/dist/shared/api-client.d.ts +16 -9
  1659. package/dist/shared/api-client.js +419 -327
  1660. package/dist/shared/api-client.js.map +1 -0
  1661. package/dist/shared/api-client.test.js +228 -0
  1662. package/dist/shared/compaction-thinking.test.js +315 -0
  1663. package/dist/shared/compaction-trimming.test.js +223 -0
  1664. package/dist/shared/compaction.d.ts +36 -0
  1665. package/dist/shared/compaction.js +138 -0
  1666. package/dist/shared/compaction.js.map +1 -0
  1667. package/dist/shared/constants.js +67 -64
  1668. package/dist/shared/constants.js.map +1 -0
  1669. package/dist/shared/exec-validator.d.ts +29 -0
  1670. package/dist/shared/exec-validator.js +106 -0
  1671. package/dist/shared/exec-validator.js.map +1 -0
  1672. package/dist/shared/imsg-constants.d.ts +8 -0
  1673. package/dist/shared/imsg-constants.js +13 -0
  1674. package/dist/shared/imsg-constants.js.map +1 -0
  1675. package/dist/shared/sse-parser-callbacks.test.js +422 -0
  1676. package/dist/shared/sse-parser-collect.test.js +252 -0
  1677. package/dist/shared/sse-parser-e2e.test.js +558 -0
  1678. package/dist/shared/sse-parser-parse.test.js +253 -0
  1679. package/dist/shared/sse-parser.js +221 -219
  1680. package/dist/shared/sse-parser.js.map +1 -0
  1681. package/dist/shared/tool-dispatch-advanced-batch-build.test.js +405 -0
  1682. package/dist/shared/tool-dispatch-advanced.test.js +320 -0
  1683. package/dist/shared/tool-dispatch-basic.test.js +278 -0
  1684. package/dist/shared/tool-dispatch-content.d.ts +14 -0
  1685. package/dist/shared/tool-dispatch-parallel.test.js +378 -0
  1686. package/dist/shared/tool-dispatch.d.ts +4 -0
  1687. package/dist/shared/tool-dispatch.js +226 -165
  1688. package/dist/shared/tool-dispatch.js.map +1 -0
  1689. package/dist/shared/types.js +1 -6
  1690. package/dist/shared/types.js.map +1 -0
  1691. package/dist/types/cli-highlight.d.js +2 -0
  1692. package/dist/types/cli-highlight.d.js.map +1 -0
  1693. package/dist/types/diff.d.js +2 -0
  1694. package/dist/types/diff.d.js.map +1 -0
  1695. package/dist/types/pdf-parse.d.js +2 -0
  1696. package/dist/types/pdf-parse.d.js.map +1 -0
  1697. package/dist/updater.d.ts +1 -1
  1698. package/dist/updater.js +118 -92
  1699. package/dist/updater.js.map +1 -0
  1700. package/dist/webchat/__tests__/widget-messaging.test.js +323 -0
  1701. package/dist/webchat/__tests__/widget.test.js +273 -0
  1702. package/dist/webchat/widget-styles.d.ts +7 -0
  1703. package/dist/webchat/widget-styles.js +11 -0
  1704. package/dist/webchat/widget-styles.js.map +1 -0
  1705. package/dist/webchat/widget.js +227 -380
  1706. package/dist/webchat/widget.js.map +1 -0
  1707. package/package.json +24 -11
  1708. package/src/cli/services/builtin-skills/verify.md +72 -0
  1709. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  1710. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  1711. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  1712. package/vendor/ink/build/apply-styles.js +175 -0
  1713. package/vendor/ink/build/build-layout.js +77 -0
  1714. package/vendor/ink/build/calculate-wrapped-text.js +53 -0
  1715. package/vendor/ink/build/colorize.d.ts +3 -0
  1716. package/vendor/ink/build/colorize.js +48 -0
  1717. package/vendor/ink/build/colorize.js.map +1 -0
  1718. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  1719. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  1720. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  1721. package/vendor/ink/build/components/App.d.ts +18 -0
  1722. package/vendor/ink/build/components/App.js +351 -0
  1723. package/vendor/ink/build/components/App.js.map +1 -0
  1724. package/vendor/ink/build/components/AppContext.d.ts +15 -0
  1725. package/vendor/ink/build/components/AppContext.js +11 -0
  1726. package/vendor/ink/build/components/AppContext.js.map +1 -0
  1727. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  1728. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  1729. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  1730. package/vendor/ink/build/components/Box.d.ts +117 -0
  1731. package/vendor/ink/build/components/Box.js +34 -0
  1732. package/vendor/ink/build/components/Box.js.map +1 -0
  1733. package/vendor/ink/build/components/Color.js +62 -0
  1734. package/vendor/ink/build/components/Cursor.d.ts +83 -0
  1735. package/vendor/ink/build/components/Cursor.js +53 -0
  1736. package/vendor/ink/build/components/Cursor.js.map +1 -0
  1737. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  1738. package/vendor/ink/build/components/CursorContext.js +8 -0
  1739. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  1740. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  1741. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  1742. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  1743. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  1744. package/vendor/ink/build/components/ErrorOverview.js +84 -0
  1745. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  1746. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  1747. package/vendor/ink/build/components/FocusContext.js +17 -0
  1748. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  1749. package/vendor/ink/build/components/Newline.d.ts +13 -0
  1750. package/vendor/ink/build/components/Newline.js +8 -0
  1751. package/vendor/ink/build/components/Newline.js.map +1 -0
  1752. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  1753. package/vendor/ink/build/components/Spacer.js +11 -0
  1754. package/vendor/ink/build/components/Spacer.js.map +1 -0
  1755. package/vendor/ink/build/components/Static.d.ts +24 -0
  1756. package/vendor/ink/build/components/Static.js +28 -0
  1757. package/vendor/ink/build/components/Static.js.map +1 -0
  1758. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  1759. package/vendor/ink/build/components/StderrContext.js +13 -0
  1760. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  1761. package/vendor/ink/build/components/StdinContext.d.ts +22 -0
  1762. package/vendor/ink/build/components/StdinContext.js +19 -0
  1763. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  1764. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  1765. package/vendor/ink/build/components/StdoutContext.js +13 -0
  1766. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  1767. package/vendor/ink/build/components/Text.d.ts +55 -0
  1768. package/vendor/ink/build/components/Text.js +50 -0
  1769. package/vendor/ink/build/components/Text.js.map +1 -0
  1770. package/vendor/ink/build/components/Transform.d.ts +16 -0
  1771. package/vendor/ink/build/components/Transform.js +15 -0
  1772. package/vendor/ink/build/components/Transform.js.map +1 -0
  1773. package/vendor/ink/build/cursor-helpers.d.ts +38 -0
  1774. package/vendor/ink/build/cursor-helpers.js +56 -0
  1775. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  1776. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  1777. package/vendor/ink/build/devtools-window-polyfill.js +65 -0
  1778. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  1779. package/vendor/ink/build/devtools.d.ts +1 -0
  1780. package/vendor/ink/build/devtools.js +11 -0
  1781. package/vendor/ink/build/devtools.js.map +1 -0
  1782. package/vendor/ink/build/dom.d.ts +56 -0
  1783. package/vendor/ink/build/dom.js +124 -0
  1784. package/vendor/ink/build/dom.js.map +1 -0
  1785. package/vendor/ink/build/experimental/apply-style.js +140 -0
  1786. package/vendor/ink/build/experimental/dom.js +123 -0
  1787. package/vendor/ink/build/experimental/output.js +91 -0
  1788. package/vendor/ink/build/experimental/reconciler.js +141 -0
  1789. package/vendor/ink/build/experimental/renderer.js +81 -0
  1790. package/vendor/ink/build/get-max-width.d.ts +3 -0
  1791. package/vendor/ink/build/get-max-width.js +10 -0
  1792. package/vendor/ink/build/get-max-width.js.map +1 -0
  1793. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  1794. package/vendor/ink/build/hooks/use-app.js +8 -0
  1795. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  1796. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  1797. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  1798. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  1799. package/vendor/ink/build/hooks/use-focus-manager.d.ts +28 -0
  1800. package/vendor/ink/build/hooks/use-focus-manager.js +17 -0
  1801. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  1802. package/vendor/ink/build/hooks/use-focus.d.ts +29 -0
  1803. package/vendor/ink/build/hooks/use-focus.js +42 -0
  1804. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  1805. package/vendor/ink/build/hooks/use-input.d.ts +131 -0
  1806. package/vendor/ink/build/hooks/use-input.js +124 -0
  1807. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  1808. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +5 -0
  1809. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +11 -0
  1810. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  1811. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  1812. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  1813. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  1814. package/vendor/ink/build/hooks/use-stdin.d.ts +5 -0
  1815. package/vendor/ink/build/hooks/use-stdin.js +8 -0
  1816. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  1817. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  1818. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  1819. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  1820. package/vendor/ink/build/hooks/useInput.js +38 -0
  1821. package/vendor/ink/build/index.d.ts +34 -0
  1822. package/vendor/ink/build/index.js +20 -0
  1823. package/vendor/ink/build/index.js.map +1 -0
  1824. package/vendor/ink/build/ink.d.ts +90 -0
  1825. package/vendor/ink/build/ink.js +677 -0
  1826. package/vendor/ink/build/ink.js.map +1 -0
  1827. package/vendor/ink/build/input-parser.d.ts +7 -0
  1828. package/vendor/ink/build/input-parser.js +154 -0
  1829. package/vendor/ink/build/input-parser.js.map +1 -0
  1830. package/vendor/ink/build/instance.js +205 -0
  1831. package/vendor/ink/build/instances.d.ts +3 -0
  1832. package/vendor/ink/build/instances.js +8 -0
  1833. package/vendor/ink/build/instances.js.map +1 -0
  1834. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  1835. package/vendor/ink/build/kitty-keyboard.js +32 -0
  1836. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  1837. package/vendor/ink/build/layout.d.ts +7 -0
  1838. package/vendor/ink/build/layout.js +33 -0
  1839. package/vendor/ink/build/layout.js.map +1 -0
  1840. package/vendor/ink/build/log-update.d.ts +19 -0
  1841. package/vendor/ink/build/log-update.js +250 -0
  1842. package/vendor/ink/build/log-update.js.map +1 -0
  1843. package/vendor/ink/build/measure-element.d.ts +16 -0
  1844. package/vendor/ink/build/measure-element.js +9 -0
  1845. package/vendor/ink/build/measure-element.js.map +1 -0
  1846. package/vendor/ink/build/measure-text.d.ts +6 -0
  1847. package/vendor/ink/build/measure-text.js +21 -0
  1848. package/vendor/ink/build/measure-text.js.map +1 -0
  1849. package/vendor/ink/build/options.d.ts +52 -0
  1850. package/vendor/ink/build/options.js +2 -0
  1851. package/vendor/ink/build/options.js.map +1 -0
  1852. package/vendor/ink/build/output.d.ts +35 -0
  1853. package/vendor/ink/build/output.js +183 -0
  1854. package/vendor/ink/build/output.js.map +1 -0
  1855. package/vendor/ink/build/parse-keypress.d.ts +22 -0
  1856. package/vendor/ink/build/parse-keypress.js +493 -0
  1857. package/vendor/ink/build/parse-keypress.js.map +1 -0
  1858. package/vendor/ink/build/reconciler.d.ts +4 -0
  1859. package/vendor/ink/build/reconciler.js +274 -0
  1860. package/vendor/ink/build/reconciler.js.map +1 -0
  1861. package/vendor/ink/build/render-background.d.ts +4 -0
  1862. package/vendor/ink/build/render-background.js +25 -0
  1863. package/vendor/ink/build/render-background.js.map +1 -0
  1864. package/vendor/ink/build/render-border.d.ts +4 -0
  1865. package/vendor/ink/build/render-border.js +73 -0
  1866. package/vendor/ink/build/render-border.js.map +1 -0
  1867. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  1868. package/vendor/ink/build/render-node-to-output.js +147 -0
  1869. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  1870. package/vendor/ink/build/render-to-string.d.ts +38 -0
  1871. package/vendor/ink/build/render-to-string.js +115 -0
  1872. package/vendor/ink/build/render-to-string.js.map +1 -0
  1873. package/vendor/ink/build/render.d.ts +121 -0
  1874. package/vendor/ink/build/render.js +55 -0
  1875. package/vendor/ink/build/render.js.map +1 -0
  1876. package/vendor/ink/build/renderer.d.ts +8 -0
  1877. package/vendor/ink/build/renderer.js +55 -0
  1878. package/vendor/ink/build/renderer.js.map +1 -0
  1879. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  1880. package/vendor/ink/build/sanitize-ansi.js +27 -0
  1881. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  1882. package/vendor/ink/build/screen-reader-update.d.ts +13 -0
  1883. package/vendor/ink/build/screen-reader-update.js +38 -0
  1884. package/vendor/ink/build/screen-reader-update.js.map +1 -0
  1885. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  1886. package/vendor/ink/build/squash-text-nodes.js +36 -0
  1887. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  1888. package/vendor/ink/build/styles.d.ts +240 -0
  1889. package/vendor/ink/build/styles.js +232 -0
  1890. package/vendor/ink/build/styles.js.map +1 -0
  1891. package/vendor/ink/build/utils.d.ts +2 -0
  1892. package/vendor/ink/build/utils.js +4 -0
  1893. package/vendor/ink/build/utils.js.map +1 -0
  1894. package/vendor/ink/build/wrap-text.d.ts +3 -0
  1895. package/vendor/ink/build/wrap-text.js +31 -0
  1896. package/vendor/ink/build/wrap-text.js.map +1 -0
  1897. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  1898. package/vendor/ink/build/write-synchronized.js +7 -0
  1899. package/vendor/ink/build/write-synchronized.js.map +1 -0
  1900. package/vendor/ink/license +10 -0
  1901. package/vendor/ink/node_modules/@types/node/LICENSE +21 -0
  1902. package/vendor/ink/node_modules/@types/node/README.md +15 -0
  1903. package/vendor/ink/node_modules/@types/node/assert/strict.d.ts +105 -0
  1904. package/vendor/ink/node_modules/@types/node/assert.d.ts +955 -0
  1905. package/vendor/ink/node_modules/@types/node/async_hooks.d.ts +623 -0
  1906. package/vendor/ink/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  1907. package/vendor/ink/node_modules/@types/node/buffer.d.ts +1810 -0
  1908. package/vendor/ink/node_modules/@types/node/child_process.d.ts +1428 -0
  1909. package/vendor/ink/node_modules/@types/node/cluster.d.ts +486 -0
  1910. package/vendor/ink/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  1911. package/vendor/ink/node_modules/@types/node/console.d.ts +151 -0
  1912. package/vendor/ink/node_modules/@types/node/constants.d.ts +20 -0
  1913. package/vendor/ink/node_modules/@types/node/crypto.d.ts +4065 -0
  1914. package/vendor/ink/node_modules/@types/node/dgram.d.ts +564 -0
  1915. package/vendor/ink/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  1916. package/vendor/ink/node_modules/@types/node/dns/promises.d.ts +503 -0
  1917. package/vendor/ink/node_modules/@types/node/dns.d.ts +922 -0
  1918. package/vendor/ink/node_modules/@types/node/domain.d.ts +166 -0
  1919. package/vendor/ink/node_modules/@types/node/events.d.ts +1054 -0
  1920. package/vendor/ink/node_modules/@types/node/fs/promises.d.ts +1329 -0
  1921. package/vendor/ink/node_modules/@types/node/fs.d.ts +4676 -0
  1922. package/vendor/ink/node_modules/@types/node/globals.d.ts +150 -0
  1923. package/vendor/ink/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  1924. package/vendor/ink/node_modules/@types/node/http.d.ts +2167 -0
  1925. package/vendor/ink/node_modules/@types/node/http2.d.ts +2480 -0
  1926. package/vendor/ink/node_modules/@types/node/https.d.ts +405 -0
  1927. package/vendor/ink/node_modules/@types/node/index.d.ts +115 -0
  1928. package/vendor/ink/node_modules/@types/node/inspector/promises.d.ts +41 -0
  1929. package/vendor/ink/node_modules/@types/node/inspector.d.ts +224 -0
  1930. package/vendor/ink/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  1931. package/vendor/ink/node_modules/@types/node/module.d.ts +819 -0
  1932. package/vendor/ink/node_modules/@types/node/net.d.ts +933 -0
  1933. package/vendor/ink/node_modules/@types/node/os.d.ts +507 -0
  1934. package/vendor/ink/node_modules/@types/node/package.json +155 -0
  1935. package/vendor/ink/node_modules/@types/node/path/posix.d.ts +8 -0
  1936. package/vendor/ink/node_modules/@types/node/path/win32.d.ts +8 -0
  1937. package/vendor/ink/node_modules/@types/node/path.d.ts +187 -0
  1938. package/vendor/ink/node_modules/@types/node/perf_hooks.d.ts +643 -0
  1939. package/vendor/ink/node_modules/@types/node/process.d.ts +2156 -0
  1940. package/vendor/ink/node_modules/@types/node/punycode.d.ts +117 -0
  1941. package/vendor/ink/node_modules/@types/node/querystring.d.ts +152 -0
  1942. package/vendor/ink/node_modules/@types/node/quic.d.ts +910 -0
  1943. package/vendor/ink/node_modules/@types/node/readline/promises.d.ts +161 -0
  1944. package/vendor/ink/node_modules/@types/node/readline.d.ts +541 -0
  1945. package/vendor/ink/node_modules/@types/node/repl.d.ts +415 -0
  1946. package/vendor/ink/node_modules/@types/node/sea.d.ts +162 -0
  1947. package/vendor/ink/node_modules/@types/node/sqlite.d.ts +955 -0
  1948. package/vendor/ink/node_modules/@types/node/stream/consumers.d.ts +38 -0
  1949. package/vendor/ink/node_modules/@types/node/stream/promises.d.ts +211 -0
  1950. package/vendor/ink/node_modules/@types/node/stream/web.d.ts +296 -0
  1951. package/vendor/ink/node_modules/@types/node/stream.d.ts +1760 -0
  1952. package/vendor/ink/node_modules/@types/node/string_decoder.d.ts +67 -0
  1953. package/vendor/ink/node_modules/@types/node/test/reporters.d.ts +96 -0
  1954. package/vendor/ink/node_modules/@types/node/test.d.ts +2240 -0
  1955. package/vendor/ink/node_modules/@types/node/timers/promises.d.ts +108 -0
  1956. package/vendor/ink/node_modules/@types/node/timers.d.ts +159 -0
  1957. package/vendor/ink/node_modules/@types/node/tls.d.ts +1198 -0
  1958. package/vendor/ink/node_modules/@types/node/trace_events.d.ts +197 -0
  1959. package/vendor/ink/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  1960. package/vendor/ink/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  1961. package/vendor/ink/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  1962. package/vendor/ink/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  1963. package/vendor/ink/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  1964. package/vendor/ink/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  1965. package/vendor/ink/node_modules/@types/node/tty.d.ts +250 -0
  1966. package/vendor/ink/node_modules/@types/node/url.d.ts +519 -0
  1967. package/vendor/ink/node_modules/@types/node/util/types.d.ts +558 -0
  1968. package/vendor/ink/node_modules/@types/node/util.d.ts +1662 -0
  1969. package/vendor/ink/node_modules/@types/node/v8.d.ts +983 -0
  1970. package/vendor/ink/node_modules/@types/node/vm.d.ts +1208 -0
  1971. package/vendor/ink/node_modules/@types/node/wasi.d.ts +202 -0
  1972. package/vendor/ink/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  1973. package/vendor/ink/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  1974. package/vendor/ink/node_modules/@types/node/web-globals/console.d.ts +9 -0
  1975. package/vendor/ink/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  1976. package/vendor/ink/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  1977. package/vendor/ink/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  1978. package/vendor/ink/node_modules/@types/node/web-globals/events.d.ts +106 -0
  1979. package/vendor/ink/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
  1980. package/vendor/ink/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  1981. package/vendor/ink/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  1982. package/vendor/ink/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  1983. package/vendor/ink/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  1984. package/vendor/ink/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  1985. package/vendor/ink/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  1986. package/vendor/ink/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  1987. package/vendor/ink/node_modules/@types/node/web-globals/url.d.ts +24 -0
  1988. package/vendor/ink/node_modules/@types/node/worker_threads.d.ts +717 -0
  1989. package/vendor/ink/node_modules/@types/node/zlib.d.ts +618 -0
  1990. package/vendor/ink/node_modules/node-pty/LICENSE +69 -0
  1991. package/vendor/ink/node_modules/node-pty/README.md +164 -0
  1992. package/vendor/ink/node_modules/node-pty/binding.gyp +150 -0
  1993. package/vendor/ink/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  1994. package/vendor/ink/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  1995. package/vendor/ink/node_modules/node-pty/lib/index.js +52 -0
  1996. package/vendor/ink/node_modules/node-pty/lib/interfaces.js +7 -0
  1997. package/vendor/ink/node_modules/node-pty/lib/shared/conout.js +11 -0
  1998. package/vendor/ink/node_modules/node-pty/lib/terminal.js +190 -0
  1999. package/vendor/ink/node_modules/node-pty/lib/types.js +7 -0
  2000. package/vendor/ink/node_modules/node-pty/lib/unixTerminal.js +349 -0
  2001. package/vendor/ink/node_modules/node-pty/lib/utils.js +39 -0
  2002. package/vendor/ink/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  2003. package/vendor/ink/node_modules/node-pty/lib/windowsPtyAgent.js +287 -0
  2004. package/vendor/ink/node_modules/node-pty/lib/windowsTerminal.js +201 -0
  2005. package/vendor/ink/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  2006. package/vendor/ink/node_modules/node-pty/package.json +65 -0
  2007. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  2008. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  2009. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  2010. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  2011. package/vendor/ink/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  2012. package/vendor/ink/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  2013. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  2014. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  2015. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  2016. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  2017. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  2018. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  2019. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  2020. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  2021. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  2022. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  2023. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  2024. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  2025. package/vendor/ink/node_modules/node-pty/scripts/post-install.js +76 -0
  2026. package/vendor/ink/node_modules/node-pty/scripts/prebuild.js +34 -0
  2027. package/vendor/ink/node_modules/node-pty/src/unix/pty.cc +875 -0
  2028. package/vendor/ink/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  2029. package/vendor/ink/node_modules/node-pty/src/win/conpty.cc +582 -0
  2030. package/vendor/ink/node_modules/node-pty/src/win/conpty.h +41 -0
  2031. package/vendor/ink/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  2032. package/vendor/ink/node_modules/node-pty/src/win/path_util.cc +95 -0
  2033. package/vendor/ink/node_modules/node-pty/src/win/path_util.h +26 -0
  2034. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  2035. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  2036. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  2037. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  2038. package/vendor/ink/node_modules/node-pty/typings/node-pty.d.ts +215 -0
  2039. package/vendor/ink/node_modules/undici-types/LICENSE +21 -0
  2040. package/vendor/ink/node_modules/undici-types/README.md +6 -0
  2041. package/vendor/ink/node_modules/undici-types/agent.d.ts +32 -0
  2042. package/vendor/ink/node_modules/undici-types/api.d.ts +43 -0
  2043. package/vendor/ink/node_modules/undici-types/balanced-pool.d.ts +30 -0
  2044. package/vendor/ink/node_modules/undici-types/cache-interceptor.d.ts +173 -0
  2045. package/vendor/ink/node_modules/undici-types/cache.d.ts +36 -0
  2046. package/vendor/ink/node_modules/undici-types/client-stats.d.ts +15 -0
  2047. package/vendor/ink/node_modules/undici-types/client.d.ts +108 -0
  2048. package/vendor/ink/node_modules/undici-types/connector.d.ts +34 -0
  2049. package/vendor/ink/node_modules/undici-types/content-type.d.ts +21 -0
  2050. package/vendor/ink/node_modules/undici-types/cookies.d.ts +30 -0
  2051. package/vendor/ink/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  2052. package/vendor/ink/node_modules/undici-types/dispatcher.d.ts +276 -0
  2053. package/vendor/ink/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  2054. package/vendor/ink/node_modules/undici-types/errors.d.ts +161 -0
  2055. package/vendor/ink/node_modules/undici-types/eventsource.d.ts +66 -0
  2056. package/vendor/ink/node_modules/undici-types/fetch.d.ts +211 -0
  2057. package/vendor/ink/node_modules/undici-types/formdata.d.ts +108 -0
  2058. package/vendor/ink/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  2059. package/vendor/ink/node_modules/undici-types/global-origin.d.ts +7 -0
  2060. package/vendor/ink/node_modules/undici-types/h2c-client.d.ts +73 -0
  2061. package/vendor/ink/node_modules/undici-types/handlers.d.ts +15 -0
  2062. package/vendor/ink/node_modules/undici-types/header.d.ts +160 -0
  2063. package/vendor/ink/node_modules/undici-types/index.d.ts +88 -0
  2064. package/vendor/ink/node_modules/undici-types/interceptors.d.ts +73 -0
  2065. package/vendor/ink/node_modules/undici-types/mock-agent.d.ts +68 -0
  2066. package/vendor/ink/node_modules/undici-types/mock-call-history.d.ts +111 -0
  2067. package/vendor/ink/node_modules/undici-types/mock-client.d.ts +27 -0
  2068. package/vendor/ink/node_modules/undici-types/mock-errors.d.ts +12 -0
  2069. package/vendor/ink/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  2070. package/vendor/ink/node_modules/undici-types/mock-pool.d.ts +27 -0
  2071. package/vendor/ink/node_modules/undici-types/package.json +55 -0
  2072. package/vendor/ink/node_modules/undici-types/patch.d.ts +29 -0
  2073. package/vendor/ink/node_modules/undici-types/pool-stats.d.ts +19 -0
  2074. package/vendor/ink/node_modules/undici-types/pool.d.ts +41 -0
  2075. package/vendor/ink/node_modules/undici-types/proxy-agent.d.ts +29 -0
  2076. package/vendor/ink/node_modules/undici-types/readable.d.ts +68 -0
  2077. package/vendor/ink/node_modules/undici-types/retry-agent.d.ts +8 -0
  2078. package/vendor/ink/node_modules/undici-types/retry-handler.d.ts +125 -0
  2079. package/vendor/ink/node_modules/undici-types/round-robin-pool.d.ts +41 -0
  2080. package/vendor/ink/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  2081. package/vendor/ink/node_modules/undici-types/util.d.ts +18 -0
  2082. package/vendor/ink/node_modules/undici-types/utility.d.ts +7 -0
  2083. package/vendor/ink/node_modules/undici-types/webidl.d.ts +341 -0
  2084. package/vendor/ink/node_modules/undici-types/websocket.d.ts +186 -0
  2085. package/vendor/ink/package.json +201 -0
  2086. package/vendor/ink/readme.md +2636 -0
  2087. package/bin/swag-agent.js +0 -9
  2088. package/dist/server/lib/pg-rate-limiter.d.ts +0 -21
  2089. package/dist/server/lib/pg-rate-limiter.js +0 -86
@@ -3,2702 +3,1808 @@
3
3
  // model-aware context management, cost tracking, compaction block handling
4
4
  //
5
5
  // Shares agent-core with CLI via src/shared/agent-core.ts
6
+
6
7
  import http from "node:http";
7
- import { randomUUID, timingSafeEqual, createHash } from "node:crypto";
8
+ import { randomUUID } from "node:crypto";
8
9
  import Anthropic from "@anthropic-ai/sdk";
9
10
  import { createLogger } from "./lib/logger.js";
10
11
  const log = createLogger("server");
11
- import { sanitizeError, resolveAgentLoopConfig, } from "../shared/agent-core.js";
12
- import { MODELS } from "../shared/constants.js";
12
+ import { sanitizeError } from "../shared/agent-core.js";
13
13
  import { handleProxy } from "./proxy-handlers.js";
14
14
  import { handleNodeRoutes, setNodeAgentInvoker } from "./handlers/nodes.js";
15
15
  import { handleTranscribe } from "./handlers/transcription.js";
16
16
  import { handleBillingRoutes, incrementUsage, checkPlanLimits } from "./handlers/billing.js";
17
+ import { handleSessionRoutes } from "./handlers/sessions-handlers.js";
17
18
  import { generateCompaction } from "./lib/compaction-service.js";
18
19
  import { initLocalAgentGateway, shutdownGateway as shutdownAgentGateway, getGatewayStats } from "./local-agent-gateway.js";
19
20
  import { initSupabase, getServiceClient } from "./lib/supabase-client.js";
20
21
  import { loadCheckpoint, markOrphaned } from "./lib/session-checkpoint.js";
21
22
  import { rateLimiter } from "./lib/rate-limiter.js";
22
23
  import { sanitizeAndLog } from "./lib/prompt-sanitizer.js";
23
- import { processWorkflowSteps, processWaitingSteps, handleWebhookIngestion, executeInlineChain, setToolExecutor, setAgentExecutor, setTokenBroadcaster, setStepErrorBroadcaster, verifyGuestApprovalSignature, initWorkerPool, getPoolStats, shutdownPool, processScheduleTriggers, enforceWorkflowTimeouts, processEventTriggers, cleanupOrphanedSteps, processDlqRetries } from "./handlers/workflows.js";
24
- import { runServerAgentLoop } from "./lib/server-agent-loop.js";
25
- import { loadTools, loadUserTools, getToolsForAgent, executeTool, loadAgentConfig, setExtendedToolsCache, getExtendedToolsIndex, flushSpans, } from "./tool-router.js";
26
- import { queueSpan, auditRowToSpan, classifyErrorType } from "./lib/clickhouse-buffer.js";
27
- import pg from "pg";
24
+ import { generateOpenApiSpec } from "./handlers/api-docs.js";
25
+ import { processWorkflowSteps, handleWebhookIngestion, executeInlineChain, initWorkerPool, getPoolStats, shutdownPool, verifyGuestApprovalSignature } from "./handlers/workflows.js";
26
+ import { loadUserTools, executeTool, flushSpans } from "./tool-router.js";
27
+ import { queueSpan, auditRowToSpan } from "./lib/clickhouse-buffer.js";
28
+
29
+ // Extracted modules — single source of truth (no inline duplicates)
30
+ import { safeCompare, getCorsHeaders, jsonResponse, readBody } from "./server-helpers.js";
31
+ import { sendIpRateLimit } from "./server-rate-limit.js";
32
+ import { resolveAndValidateStoreId } from "./server-store.js";
33
+ import { getSseClients, safeSseWrite, sendWorkflowSSE, getTotalSseClients, sseCleanupInterval, MAX_SSE_CLIENTS_PER_RUN, MAX_SSE_TOTAL_CLIENTS, setupPgListen, closePgClient, pgListenReady } from "./server-sse.js";
34
+ import { handleAgentChat } from "./server-chat.js";
35
+ import { wireToolExecutor, wireAgentExecutor, wireBroadcasters, invokeAgentForChannel } from "./server-executors.js";
36
+ import { startWorkerLoop, stopWorkerLoop } from "./server-worker.js";
37
+ import { getCircuitBreakerStats } from "./server-store-circuit-breaker.js";
38
+
28
39
  // ============================================================================
29
40
  // PROCESS ERROR HANDLERS
30
41
  // ============================================================================
42
+
31
43
  process.on("unhandledRejection", (reason, _promise) => {
32
- log.error({ err: reason }, "unhandled rejection");
44
+ log.error({
45
+ err: reason
46
+ }, "unhandled rejection");
33
47
  });
34
- process.on("uncaughtException", (err) => {
35
- log.fatal({ err }, "uncaught exception");
36
- process.exit(1);
48
+ process.on("uncaughtException", err => {
49
+ log.fatal({
50
+ err
51
+ }, "uncaught exception");
52
+ process.exit(1);
37
53
  });
54
+
38
55
  // ============================================================================
39
56
  // ENV CONFIG
40
57
  // ============================================================================
58
+
41
59
  const PORT = parseInt(process.env.PORT || "8080", 10);
42
60
  const SUPABASE_URL = process.env.SUPABASE_URL;
43
61
  const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY;
44
62
  const SERVICE_ROLE_JWT = process.env.SERVICE_ROLE_JWT || "";
45
63
  const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
46
- const ALLOWED_ORIGINS = (process.env.ALLOWED_ORIGINS || "http://localhost:3000,http://127.0.0.1:3000").split(",").map(s => s.trim());
47
64
  const FLY_INTERNAL_SECRET = process.env.FLY_INTERNAL_SECRET || "";
48
65
  if (!FLY_INTERNAL_SECRET) {
49
- console.warn("[SECURITY] FLY_INTERNAL_SECRET is not set — internal endpoints are unprotected");
66
+ console.warn("[SECURITY] FLY_INTERNAL_SECRET is not set — internal endpoints are unprotected");
50
67
  }
68
+
51
69
  // ============================================================================
52
70
  // READINESS STATE
53
71
  // ============================================================================
54
- let pgListenReady = false;
72
+
55
73
  let workerPoolReady = false;
56
74
  function isReady() {
57
- return workerPoolReady; // PG listen is optional (SSE only)
75
+ return workerPoolReady; // PG listen is optional (SSE only)
58
76
  }
77
+
59
78
  // Webchat agent invoker — set later to avoid circular deps (same as node agent invoker)
60
79
  let webchatAgentInvoker = null;
80
+
61
81
  // ============================================================================
62
- // RATE LIMITING — Phase 7.2 token-bucket (see lib/rate-limiter.ts)
82
+ // HTTP SERVER
63
83
  // ============================================================================
64
- /** Check IP rate limit and send 429 with proper headers if exceeded */
65
- function sendIpRateLimit(res, ip, headers) {
66
- const result = rateLimiter.checkRequest(`ip:${ip}`, "unauthenticated");
67
- if (result.allowed)
68
- return false; // not rate-limited
69
- res.writeHead(429, {
70
- "Retry-After": String(Math.ceil(result.retryAfterMs / 1000) || 1),
71
- "X-RateLimit-Remaining": "0",
72
- "Content-Type": "application/json",
73
- ...headers,
84
+
85
+ // Connection tracking for graceful shutdown draining
86
+ let activeRequests = 0;
87
+ const server = http.createServer(async (req, res) => {
88
+ activeRequests++;
89
+ res.on("close", () => {
90
+ activeRequests--;
91
+ });
92
+ const origin = req.headers.origin || "";
93
+ const corsHeaders = getCorsHeaders(origin);
94
+
95
+ // Health check — readiness-aware for Fly.io
96
+ if (req.method === "GET" && (req.url === "/" || req.url === "/health")) {
97
+ const ready = isReady();
98
+ const status = ready ? 200 : 503;
99
+ const agentStats = getGatewayStats();
100
+ const cbStats = getCircuitBreakerStats();
101
+ jsonResponse(res, status, {
102
+ status: ready ? "ok" : "starting",
103
+ version: process.env.npm_package_version || "6.4.0",
104
+ uptime: Math.floor(process.uptime()),
105
+ pg_listen: pgListenReady,
106
+ worker_pool: workerPoolReady,
107
+ local_agents: agentStats.total_agents,
108
+ circuit_breakers: cbStats
109
+ }, corsHeaders);
110
+ return;
111
+ }
112
+
113
+ // OpenAPI spec — public, no auth required (for Scalar docs viewer)
114
+ if (req.method === "GET" && req.url === "/openapi.json") {
115
+ const spec = generateOpenApiSpec();
116
+ res.writeHead(200, {
117
+ ...corsHeaders,
118
+ "Content-Type": "application/json",
119
+ "Cache-Control": "public, max-age=3600"
74
120
  });
75
- res.end(JSON.stringify({ error: "Too many requests" }));
76
- return true; // was rate-limited
77
- }
78
- // Agent chat rate limiting — per-store + global concurrent cap
79
- const agentChatLimiter = new Map();
80
- const AGENT_CHAT_MAX_PER_MIN = 60;
81
- const AGENT_CHAT_MAX_CONCURRENT = 10;
82
- let agentChatConcurrent = 0;
83
- // Periodically clean up stale entries from agentChatLimiter to prevent unbounded Map growth
84
- setInterval(() => {
85
- const now = Date.now();
86
- for (const [key, entry] of agentChatLimiter) {
87
- if (now - entry.windowStart > 120_000) {
88
- agentChatLimiter.delete(key);
89
- }
90
- }
91
- }, 60_000);
92
- function checkAgentChatRateLimit(storeId) {
93
- // Concurrent check
94
- if (agentChatConcurrent >= AGENT_CHAT_MAX_CONCURRENT) {
95
- return { allowed: false, error: "Too many concurrent agent sessions. Please wait." };
96
- }
97
- // Per-store per-minute check
98
- const now = Date.now();
99
- let entry = agentChatLimiter.get(storeId);
100
- if (!entry || now - entry.windowStart > 60_000) {
101
- entry = { count: 0, windowStart: now };
102
- agentChatLimiter.set(storeId, entry);
103
- }
104
- entry.count++;
105
- if (entry.count > AGENT_CHAT_MAX_PER_MIN) {
106
- return { allowed: false, error: `Rate limit exceeded: ${AGENT_CHAT_MAX_PER_MIN}/min for agent chat` };
107
- }
108
- return { allowed: true };
109
- }
110
- // Timing-safe secret comparison to prevent timing attacks
111
- // Hash both values to fixed length before comparing — avoids leaking secret length
112
- function safeCompare(a, b) {
113
- if (!a || !b)
114
- return false;
115
- const hashA = createHash("sha256").update(a).digest();
116
- const hashB = createHash("sha256").update(b).digest();
117
- return timingSafeEqual(hashA, hashB);
118
- }
119
- // Tool registry, user tools, executor, and agent loader are in ./tool-router.ts
120
- // ============================================================================
121
- // STORE CONTEXT — Server-derived store resolution (Apple-style)
122
- // The server NEVER blindly trusts a client-supplied storeId. Instead it:
123
- // 1. Resolves the user's stores from the `users` table via auth.uid()
124
- // 2. Validates the client hint against the resolved set
125
- // 3. Falls back to the user's first store if no hint provided
126
- // 4. Returns null if no store can be resolved (caller must fail closed)
127
- // ============================================================================
128
- async function resolveAndValidateStoreId(supabase, clientStoreId, user, isServiceRole, _token, requestUserId) {
129
- // Service-role callers: trusted ONLY for internal server-to-server calls
130
- // (workflows, cron jobs) that have NO associated user. When a userId is
131
- // present (e.g. MCP CLI using env-var service role key), we MUST still
132
- // validate store membership to prevent cross-tenant access.
133
- if (isServiceRole) {
134
- const srUserId = user?.id || requestUserId;
135
- if (!srUserId)
136
- return clientStoreId || null; // true internal call — pass through
137
- if (!clientStoreId)
138
- return null; // user-context call without store hint
139
- // Validate the user actually belongs to the requested store
140
- const { data: membership } = await supabase
141
- .from("store_members")
142
- .select("id")
143
- .eq("store_id", clientStoreId)
144
- .eq("user_id", srUserId)
145
- .limit(1)
146
- .maybeSingle();
147
- if (membership)
148
- return clientStoreId;
149
- // Fallback: check users table (legacy single-store pattern)
150
- const { data: userRow } = await supabase
151
- .from("users")
152
- .select("store_id")
153
- .eq("auth_user_id", srUserId)
154
- .eq("store_id", clientStoreId)
155
- .maybeSingle();
156
- if (userRow)
157
- return clientStoreId;
158
- log.warn({ userId: srUserId, clientStoreId }, "resolveStoreId: service-role caller userId not authorized for store");
159
- return null;
160
- }
161
- if (!user?.id)
162
- return null;
163
- // Resolve user's actual stores from the `users` table (auth_user_id = auth.uid())
164
- const { data: userStores, error } = await supabase
165
- .from("users")
166
- .select("store_id")
167
- .eq("auth_user_id", user.id)
168
- .not("store_id", "is", null);
169
- if (error || !userStores?.length) {
170
- log.warn({ userId: user.id, error }, "resolveStoreId: user has no stores");
171
- return null;
172
- }
173
- const storeIds = userStores.map((r) => r.store_id);
174
- // If client provided a hint, validate it's in the user's set
175
- if (clientStoreId) {
176
- if (storeIds.includes(clientStoreId)) {
177
- return clientStoreId;
178
- }
179
- log.warn({ userId: user.id, clientStoreId, userStores: storeIds }, "resolveStoreId: client storeId not in user's stores");
180
- return null; // Reject don't silently fall back
181
- }
182
- // No client hint — use first store (single-store users), or require explicit selection
183
- if (storeIds.length === 1) {
184
- return storeIds[0];
185
- }
186
- // Multi-store user without a hint — can't guess
187
- log.warn({ userId: user.id, storeCount: storeIds.length }, "resolveStoreId: multi-store user must specify storeId");
188
- return null;
189
- }
190
- // ============================================================================
191
- // CORS
192
- // ============================================================================
193
- function getCorsHeaders(origin) {
194
- const headers = {
195
- "X-Content-Type-Options": "nosniff",
196
- "X-Frame-Options": "DENY",
197
- "X-XSS-Protection": "0",
198
- "Referrer-Policy": "strict-origin-when-cross-origin",
121
+ res.end(JSON.stringify(spec));
122
+ return;
123
+ }
124
+
125
+ // CORS preflight
126
+ if (req.method === "OPTIONS") {
127
+ res.writeHead(204, corsHeaders);
128
+ res.end();
129
+ return;
130
+ }
131
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
132
+ const pathname = url.pathname;
133
+
134
+ // ================================================================
135
+ // Phase 3: SSE stream for workflow run progress
136
+ // GET /workflows/runs/:id/stream
137
+ // ================================================================
138
+ if (req.method === "GET" && pathname.match(/^\/workflows\/runs\/[a-f0-9-]+\/stream$/)) {
139
+ const runId = pathname.split("/")[3];
140
+
141
+ // Auth check
142
+ const authHeader = req.headers.authorization;
143
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
144
+ const isInternal = safeCompare(token, FLY_INTERNAL_SECRET) || safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
145
+ if (!isInternal && !token) {
146
+ jsonResponse(res, 401, {
147
+ error: "Missing authorization"
148
+ }, corsHeaders);
149
+ return;
150
+ }
151
+ if (!isInternal) {
152
+ const sb = getServiceClient();
153
+ const {
154
+ data: {
155
+ user: authUser
156
+ },
157
+ error: authError
158
+ } = await sb.auth.getUser(token);
159
+ if (authError || !authUser) {
160
+ jsonResponse(res, 401, {
161
+ error: "Invalid or expired token"
162
+ }, corsHeaders);
163
+ return;
164
+ }
165
+ // P1 FIX: Verify user belongs to the run's store (prevent cross-store SSE snooping)
166
+ const {
167
+ data: sseRun
168
+ } = await sb.from("workflow_runs").select("store_id").eq("id", runId).single();
169
+ if (sseRun) {
170
+ const {
171
+ data: membership
172
+ } = await sb.from("store_members").select("id").eq("store_id", sseRun.store_id).eq("user_id", authUser.id).single();
173
+ if (!membership) {
174
+ jsonResponse(res, 403, {
175
+ error: "Not authorized to view this run"
176
+ }, corsHeaders);
177
+ return;
178
+ }
179
+ }
180
+ }
181
+
182
+ // H6 FIX: Enforce per-run and total client limits
183
+ const sseClients = getSseClients();
184
+ const existingClients = sseClients.get(runId)?.size || 0;
185
+ if (existingClients >= MAX_SSE_CLIENTS_PER_RUN) {
186
+ jsonResponse(res, 429, {
187
+ error: "Too many SSE clients for this run"
188
+ }, corsHeaders);
189
+ return;
190
+ }
191
+ if (getTotalSseClients() >= MAX_SSE_TOTAL_CLIENTS) {
192
+ jsonResponse(res, 429, {
193
+ error: "Too many total SSE connections"
194
+ }, corsHeaders);
195
+ return;
196
+ }
197
+
198
+ // Start SSE stream
199
+ res.writeHead(200, {
200
+ "Content-Type": "text/event-stream",
201
+ "Cache-Control": "no-cache",
202
+ Connection: "keep-alive",
203
+ ...corsHeaders
204
+ });
205
+
206
+ // Send snapshot
207
+ const sb = getServiceClient();
208
+ const {
209
+ data: run
210
+ } = await sb.from("workflow_runs").select("id, workflow_id, status, trigger_type, current_step_key, error_message, error_step_key, started_at, completed_at, duration_ms").eq("id", runId).single();
211
+ const {
212
+ data: stepRuns
213
+ } = await sb.from("workflow_step_runs").select("id, step_key, step_type, status, error_message, duration_ms, started_at, completed_at").eq("run_id", runId).order("created_at", {
214
+ ascending: true
215
+ });
216
+ sendWorkflowSSE(res, {
217
+ type: "snapshot",
218
+ run,
219
+ steps: stepRuns || []
220
+ });
221
+
222
+ // Register client
223
+ if (!sseClients.has(runId)) sseClients.set(runId, new Set());
224
+ sseClients.get(runId).add(res);
225
+
226
+ // Cleanup on disconnect
227
+ const cleanup = () => {
228
+ clearInterval(heartbeat);
229
+ const clients = sseClients.get(runId);
230
+ if (clients) {
231
+ clients.delete(res);
232
+ if (clients.size === 0) sseClients.delete(runId);
233
+ }
199
234
  };
200
- if (ALLOWED_ORIGINS.includes("*")) {
201
- headers["Access-Control-Allow-Origin"] = "*";
202
- }
203
- else if (origin && ALLOWED_ORIGINS.includes(origin)) {
204
- headers["Access-Control-Allow-Origin"] = origin;
205
- headers["Vary"] = "Origin";
206
- }
207
- // If origin doesn't match, no CORS header = browser blocks the request
208
- headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS";
209
- headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization, X-Store-Id";
210
- return headers;
211
- }
212
- // ============================================================================
213
- // PHASE 3: SSE STREAMING real-time workflow run progress
214
- // ============================================================================
215
- // Map<runId, Set<ServerResponse>> for multiplexing SSE clients
216
- const sseClients = new Map();
217
- const MAX_SSE_CLIENTS_PER_RUN = 10;
218
- const MAX_SSE_TOTAL_CLIENTS = 100;
219
- /** Safe SSE write — returns false and calls cleanup if the connection is dead */
220
- function safeSseWrite(res, data, cleanup) {
235
+
236
+ // Heartbeat — uses safeSseWrite so dead connections are cleaned up immediately
237
+ const heartbeat = setInterval(() => {
238
+ if (!safeSseWrite(res, `: heartbeat\n\n`, cleanup)) {
239
+ clearInterval(heartbeat);
240
+ }
241
+ }, 15_000);
242
+ req.on("close", cleanup);
243
+ req.on("error", cleanup);
244
+ return;
245
+ }
246
+
247
+ // ================================================================
248
+ // Guest approval signed URL, no auth required (GET)
249
+ // GET /approvals/guest/:id?action=approve&expires=...&sig=...
250
+ // ================================================================
251
+ const guestApprovalMatch = pathname.match(/^\/approvals\/guest\/([a-f0-9-]+)$/);
252
+ if (guestApprovalMatch && req.method === "GET") {
253
+ const clientIp = req.headers["x-forwarded-for"]?.toString().split(",")[0]?.trim() || req.socket.remoteAddress || "unknown";
254
+ if (sendIpRateLimit(res, clientIp, corsHeaders)) return;
255
+ const stepRunId = guestApprovalMatch[1];
256
+ const urlParams = new URL(req.url || "", `http://${req.headers.host}`).searchParams;
257
+ const action = urlParams.get("action") || "";
258
+ const expires = urlParams.get("expires") || "";
259
+ const sig = urlParams.get("sig") || "";
260
+ if (!action || !expires || !sig) {
261
+ jsonResponse(res, 400, {
262
+ error: "Missing action, expires, or sig parameter"
263
+ }, corsHeaders);
264
+ return;
265
+ }
266
+ if (new Date(expires) < new Date()) {
267
+ jsonResponse(res, 410, {
268
+ error: "This approval link has expired"
269
+ }, corsHeaders);
270
+ return;
271
+ }
272
+ if (!verifyGuestApprovalSignature(stepRunId, action, expires, sig)) {
273
+ jsonResponse(res, 403, {
274
+ error: "Invalid signature"
275
+ }, corsHeaders);
276
+ return;
277
+ }
278
+ const guestSupabase = getServiceClient();
279
+ const {
280
+ data: approval
281
+ } = await guestSupabase.from("workflow_approval_requests").select("id, store_id, run_id, status").eq("step_run_id", stepRunId).limit(1);
282
+ if (!approval?.length) {
283
+ jsonResponse(res, 404, {
284
+ error: "Approval not found"
285
+ }, corsHeaders);
286
+ return;
287
+ }
288
+ if (approval[0].status !== "pending") {
289
+ jsonResponse(res, 409, {
290
+ error: `Approval already ${approval[0].status}`
291
+ }, corsHeaders);
292
+ return;
293
+ }
294
+ const isApprove = action === "approve" || action === "approved";
295
+ const {
296
+ data: guestResult,
297
+ error: guestErr
298
+ } = await guestSupabase.rpc("respond_to_approval", {
299
+ p_approval_id: approval[0].id,
300
+ p_store_id: approval[0].store_id,
301
+ p_response: isApprove ? "approved" : "rejected",
302
+ p_response_data: {
303
+ guest: true,
304
+ action
305
+ },
306
+ p_responded_by: null
307
+ });
308
+ if (guestErr) {
309
+ jsonResponse(res, 500, {
310
+ success: false,
311
+ error: guestErr.message
312
+ }, corsHeaders);
313
+ return;
314
+ }
315
+ if (guestResult?.success && approval[0].run_id) {
316
+ try {
317
+ await executeInlineChain(guestSupabase, approval[0].run_id);
318
+ } catch (err) {
319
+ log.error({
320
+ err: err.message,
321
+ runId: approval[0].run_id
322
+ }, "inline chain failed after guest approval");
323
+ }
324
+ }
325
+ res.writeHead(200, {
326
+ "Content-Type": "text/html",
327
+ ...corsHeaders
328
+ });
329
+ res.end(`<!DOCTYPE html><html><body style="font-family:system-ui;text-align:center;padding:40px">
330
+ <h2>${isApprove ? "Approved" : "Rejected"}</h2>
331
+ <p>Your response has been recorded. You can close this window.</p>
332
+ </body></html>`);
333
+ return;
334
+ }
335
+
336
+ // ================================================================
337
+ // Webchat — anonymous access for embedded chat widgets
338
+ // POST /webchat/channels/:id/messages — send message (+ agent auto-reply)
339
+ // GET /webchat/channels/:id/history — load conversation history
340
+ // GET /webchat/widget.js — serve compiled widget JS
341
+ // ================================================================
342
+ // Webchat CORS: allow any origin (widget is embedded on customer sites)
343
+ const webchatCors = {
344
+ ...corsHeaders,
345
+ "Access-Control-Allow-Origin": "*"
346
+ };
347
+ const webchatMsgMatch = pathname.match(/^\/webchat\/channels\/([a-f0-9-]+)\/messages$/);
348
+ if (webchatMsgMatch && req.method === "POST") {
349
+ const clientIp = req.headers["x-forwarded-for"]?.toString().split(",")[0]?.trim() || req.socket.remoteAddress || "unknown";
350
+ if (sendIpRateLimit(res, clientIp, webchatCors)) return;
351
+ let rawBody;
221
352
  try {
222
- if (res.destroyed || res.writableEnded) {
223
- cleanup();
224
- return false;
225
- }
226
- res.write(data);
227
- return true;
228
- }
229
- catch {
230
- cleanup();
231
- return false;
232
- }
233
- }
234
- function sendWorkflowSSE(res, data) {
353
+ rawBody = await readBody(req);
354
+ } catch {
355
+ jsonResponse(res, 413, {
356
+ error: "Request body too large"
357
+ }, webchatCors);
358
+ return;
359
+ }
360
+ const channelId = webchatMsgMatch[1];
361
+ let wcBody;
235
362
  try {
236
- if (res.destroyed || res.writableEnded)
237
- return;
238
- res.write(`data: ${JSON.stringify(data)}\n\n`);
239
- }
240
- catch { /* client disconnected — benign */ }
241
- }
242
- function broadcastToRun(runId, data) {
243
- const clients = sseClients.get(runId);
244
- if (!clients?.size)
245
- return;
246
- // H6 FIX: Prune dead connections during broadcast
247
- for (const res of clients) {
248
- if (res.destroyed || res.writableEnded) {
249
- clients.delete(res);
250
- continue;
251
- }
252
- sendWorkflowSSE(res, data);
253
- }
254
- if (clients.size === 0)
255
- sseClients.delete(runId);
256
- }
257
- function getTotalSseClients() {
258
- let total = 0;
259
- for (const clients of sseClients.values())
260
- total += clients.size;
261
- return total;
262
- }
263
- // H6 FIX: Periodic stale connection cleanup (every 60s)
264
- const sseCleanupInterval = setInterval(() => {
265
- for (const [rid, clients] of sseClients) {
266
- for (const res of clients) {
267
- if (res.destroyed || res.writableEnded) {
268
- clients.delete(res);
269
- }
270
- }
271
- if (clients.size === 0)
272
- sseClients.delete(rid);
273
- }
274
- }, 60_000);
275
- // pg LISTEN for real-time notifications
276
- const DATABASE_URL = process.env.DATABASE_URL || "";
277
- let pgClient = null;
278
- let pgReconnectAttempts = 0;
279
- const MAX_PG_RECONNECT_DELAY = 10_000; // 10s max — keep SSE reconnect snappy
280
- async function setupPgListen() {
281
- if (!DATABASE_URL) {
282
- log.info("DATABASE_URL not set — SSE streaming disabled, using worker-only mode");
363
+ wcBody = JSON.parse(rawBody);
364
+ } catch {
365
+ jsonResponse(res, 400, {
366
+ error: "Invalid JSON"
367
+ }, webchatCors);
368
+ return;
369
+ }
370
+ if (!wcBody.content || typeof wcBody.content !== "string") {
371
+ jsonResponse(res, 400, {
372
+ error: "content (string) is required"
373
+ }, webchatCors);
374
+ return;
375
+ }
376
+ if (wcBody.content.length > 5000) {
377
+ jsonResponse(res, 400, {
378
+ error: "Message too long (max 5000 characters)"
379
+ }, webchatCors);
380
+ return;
381
+ }
382
+ const supabase = getServiceClient();
383
+
384
+ // Verify channel exists and is a webchat channel
385
+ const {
386
+ data: channel
387
+ } = await supabase.from("channels").select("id, store_id, node_id, agent_id, type, config").eq("id", channelId).eq("type", "webchat").single();
388
+ if (!channel) {
389
+ jsonResponse(res, 404, {
390
+ error: "Webchat channel not found"
391
+ }, webchatCors);
392
+ return;
393
+ }
394
+
395
+ // Rate limit per store (best-effort)
396
+ try {
397
+ const planCheck = await checkPlanLimits(supabase, channel.store_id, "message");
398
+ if (!planCheck.allowed) {
399
+ jsonResponse(res, 429, {
400
+ error: planCheck.reason || "Plan limit reached"
401
+ }, webchatCors);
283
402
  return;
284
- }
403
+ }
404
+ } catch {/* billing tables may not exist yet */}
405
+ const senderId = wcBody.sender_id || "anonymous";
406
+
407
+ // Resolve conversation: reuse if sender had activity < 30 min ago, else new UUID
408
+ let conversationId = wcBody.conversation_id || "";
409
+ if (!conversationId) {
410
+ const thirtyMinAgo = new Date(Date.now() - 30 * 60 * 1000).toISOString();
411
+ const {
412
+ data: recent
413
+ } = 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", {
414
+ ascending: false
415
+ }).limit(1);
416
+ conversationId = recent?.length && recent[0].conversation_id || randomUUID();
417
+ }
418
+
419
+ // Insert inbound message
420
+ const {
421
+ data: message,
422
+ error: msgErr
423
+ } = await supabase.from("channel_messages").insert({
424
+ store_id: channel.store_id,
425
+ channel_id: channelId,
426
+ direction: "inbound",
427
+ sender_id: senderId,
428
+ sender_name: wcBody.sender_name || "Visitor",
429
+ content: wcBody.content,
430
+ content_type: "text",
431
+ metadata: {
432
+ source: "webchat",
433
+ ip: clientIp,
434
+ widget_version: "1.0.0"
435
+ },
436
+ agent_id: channel.agent_id,
437
+ conversation_id: conversationId
438
+ }).select("id, direction, content, conversation_id, created_at").single();
439
+ if (msgErr) {
440
+ jsonResponse(res, 500, {
441
+ error: msgErr.message
442
+ }, webchatCors);
443
+ return;
444
+ }
445
+
446
+ // Track usage + channel stats (best-effort)
447
+ incrementUsage(supabase, channel.store_id, {
448
+ messages_in: 1
449
+ }).catch(() => {});
285
450
  try {
286
- // Strip sslmode from URL (pg v8 treats sslmode=require as verify-full) and set ssl manually
287
- const cleanUrl = DATABASE_URL.replace(/[?&]sslmode=[^&]*/g, "").replace(/\?$/, "");
288
- pgClient = new pg.Client({ connectionString: cleanUrl, ssl: { rejectUnauthorized: false } });
289
- await pgClient.connect();
290
- await pgClient.query("LISTEN workflow_step_event");
291
- await pgClient.query("LISTEN workflow_run_event");
292
- await pgClient.query("LISTEN workflow_step_pending");
293
- await pgClient.query("LISTEN workflow_event");
294
- await pgClient.query("LISTEN automation_event");
295
- // Reset reconnect counter on successful connection
296
- pgReconnectAttempts = 0;
297
- pgListenReady = true;
298
- // Debounced event trigger processing — fires at most once per 100ms
299
- let eventTriggerTimer = null;
300
- function debouncedEventProcess() {
301
- if (eventTriggerTimer)
302
- return;
303
- eventTriggerTimer = setTimeout(async () => {
304
- eventTriggerTimer = null;
305
- try {
306
- const sb = getServiceClient();
307
- const count = await processEventTriggers(sb);
308
- if (count > 0)
309
- log.info({ count }, "instant event processing");
310
- }
311
- catch (err) {
312
- log.error({ err: err.message }, "event trigger processing error");
313
- }
314
- }, 100);
315
- }
316
- pgClient.on("notification", (msg) => {
317
- if (!msg.payload)
318
- return;
319
- try {
320
- const data = JSON.parse(msg.payload);
321
- // Automation event — trigger immediate processing
322
- if (msg.channel === "automation_event") {
323
- debouncedEventProcess();
324
- return;
325
- }
326
- const runId = data.run_id;
327
- if (!runId)
328
- return;
329
- if (msg.channel === "workflow_step_event") {
330
- broadcastToRun(runId, { type: "step_update", ...data });
331
- }
332
- else if (msg.channel === "workflow_run_event") {
333
- broadcastToRun(runId, { type: "run_update", ...data });
334
- }
335
- else if (msg.channel === "workflow_event") {
336
- broadcastToRun(runId, { type: "event", event_type: data.event_type, ...data });
337
- }
338
- else if (msg.channel === "workflow_step_pending") {
339
- // Phase 3.1: NOTIFY-driven step execution immediate pickup (~50ms vs 5s polling)
340
- const sb = getServiceClient();
341
- processWorkflowSteps(sb, 1).catch((err) => {
342
- log.error({ err: err.message, runId }, "NOTIFY-driven step processing failed");
343
- });
344
- }
345
- }
346
- catch (err) {
347
- log.error({ err: err.message }, "failed to parse pg notification");
348
- }
349
- });
350
- pgClient.on("error", (err) => {
351
- log.error({ err: err.message }, "pg-listen connection error");
352
- pgClient = null;
353
- // Reconnect with exponential backoff
354
- pgReconnectAttempts++;
355
- const delay = Math.min(1000 * Math.pow(2, pgReconnectAttempts - 1), MAX_PG_RECONNECT_DELAY);
356
- log.warn({ delayMs: delay, attempt: pgReconnectAttempts }, "pg-listen reconnecting");
357
- setTimeout(() => setupPgListen(), delay);
358
- });
359
- log.info("pg-listen active on workflow_step_event, workflow_run_event, workflow_step_pending, workflow_event, automation_event");
360
- }
361
- catch (err) {
362
- log.error({ err: err.message }, "pg-listen failed to connect");
363
- pgClient = null;
364
- // Reconnect with exponential backoff on initial connection failure too
365
- pgReconnectAttempts++;
366
- const delay = Math.min(1000 * Math.pow(2, pgReconnectAttempts - 1), MAX_PG_RECONNECT_DELAY);
367
- log.warn({ delayMs: delay, attempt: pgReconnectAttempts }, "pg-listen reconnecting");
368
- setTimeout(() => setupPgListen(), delay);
369
- }
370
- }
371
- // ============================================================================
372
- // HELPERS
373
- // ============================================================================
374
- function getAnthropicClient(agent) {
375
- const key = agent.api_key || ANTHROPIC_API_KEY;
376
- return new Anthropic({ apiKey: key, timeout: 15 * 60 * 1000 }); // 15 min for tool-heavy requests
377
- }
378
- function sendSSE(res, event) {
451
+ await supabase.rpc("increment_channel_stats", {
452
+ p_channel_id: channelId
453
+ });
454
+ } catch {/* ok */}
455
+
456
+ // Auto-invoke agent if assigned
457
+ let agentResponse = null;
458
+ if (channel.agent_id && webchatAgentInvoker) {
459
+ try {
460
+ const result = await webchatAgentInvoker(supabase, channel.agent_id, wcBody.content, channel.store_id, conversationId);
461
+ if (result.success && result.response) {
462
+ const {
463
+ data: outMsg
464
+ } = await supabase.from("channel_messages").insert({
465
+ store_id: channel.store_id,
466
+ channel_id: channelId,
467
+ direction: "outbound",
468
+ sender_id: "agent",
469
+ sender_name: "AI Agent",
470
+ content: result.response,
471
+ content_type: "text",
472
+ metadata: {
473
+ agent_id: channel.agent_id,
474
+ auto_response: true,
475
+ source: "webchat"
476
+ },
477
+ agent_id: channel.agent_id,
478
+ conversation_id: conversationId
479
+ }).select("id, direction, content, conversation_id, created_at").single();
480
+ agentResponse = outMsg;
481
+ incrementUsage(supabase, channel.store_id, {
482
+ messages_out: 1,
483
+ agent_invocations: 1
484
+ }).catch(() => {});
485
+ }
486
+ } catch (err) {
487
+ log.error({
488
+ err: err.message
489
+ }, "webchat agent error");
490
+ }
491
+ }
492
+ jsonResponse(res, 201, {
493
+ success: true,
494
+ message,
495
+ agent_response: agentResponse,
496
+ conversation_id: conversationId
497
+ }, webchatCors);
498
+ return;
499
+ }
500
+
501
+ // GET /webchat/channels/:id/history load conversation history for widget
502
+ const webchatHistoryMatch = pathname.match(/^\/webchat\/channels\/([a-f0-9-]+)\/history$/);
503
+ if (webchatHistoryMatch && req.method === "GET") {
504
+ const clientIp = req.headers["x-forwarded-for"]?.toString().split(",")[0]?.trim() || req.socket.remoteAddress || "unknown";
505
+ if (sendIpRateLimit(res, clientIp, webchatCors)) return;
506
+ const channelId = webchatHistoryMatch[1];
507
+ const params = url.searchParams;
508
+ const senderId = params.get("sender_id") || "";
509
+ const reqConvId = params.get("conversation_id") || "";
510
+ if (!senderId) {
511
+ jsonResponse(res, 400, {
512
+ error: "sender_id query parameter required"
513
+ }, webchatCors);
514
+ return;
515
+ }
516
+ const supabase = getServiceClient();
517
+
518
+ // Verify channel is webchat type
519
+ const {
520
+ data: wcChannel
521
+ } = await supabase.from("channels").select("id, type").eq("id", channelId).eq("type", "webchat").single();
522
+ if (!wcChannel) {
523
+ jsonResponse(res, 404, {
524
+ error: "Webchat channel not found"
525
+ }, webchatCors);
526
+ return;
527
+ }
528
+
529
+ // Get messages by conversation_id if available, or by sender
530
+ // P0 FIX: Always filter by sender_id to prevent cross-sender data leak
531
+ let query = supabase.from("channel_messages").select("id, direction, sender_id, sender_name, content, content_type, created_at").eq("channel_id", channelId);
532
+
533
+ // P0 FIX: Validate senderId against strict pattern to prevent PostgREST filter injection
534
+ // Only allow alphanumeric characters, hyphens, and underscores (blocks .eq., .neq., dots, etc.)
535
+ if (!/^[a-zA-Z0-9_-]+$/.test(senderId)) {
536
+ jsonResponse(res, 400, {
537
+ error: "Invalid sender_id format"
538
+ }, webchatCors);
539
+ return;
540
+ }
541
+ if (reqConvId) {
542
+ // P0 FIX: Use parameterized .in() filter instead of string interpolation in .or()
543
+ query = query.eq("conversation_id", reqConvId).in("sender_id", [senderId, "agent"]);
544
+ } else {
545
+ query = query.in("sender_id", [senderId, "agent"]);
546
+ }
547
+ const {
548
+ data: messages,
549
+ error: histErr
550
+ } = await query.order("created_at", {
551
+ ascending: true
552
+ }).limit(50);
553
+ if (histErr) {
554
+ jsonResponse(res, 500, {
555
+ error: histErr.message
556
+ }, webchatCors);
557
+ return;
558
+ }
559
+ jsonResponse(res, 200, {
560
+ success: true,
561
+ messages: messages || []
562
+ }, webchatCors);
563
+ return;
564
+ }
565
+
566
+ // GET /webchat/widget.js — serve compiled widget JavaScript
567
+ if (pathname === "/webchat/widget.js" && req.method === "GET") {
568
+ const fs = await import("node:fs");
569
+ const path = await import("node:path");
570
+ // Try multiple possible locations for the built widget file
571
+ const possiblePaths = [path.join(import.meta.dirname || __dirname, "../../dist/webchat/widget.js"), path.join(import.meta.dirname || __dirname, "../../../dist/webchat/widget.js"), path.join(process.cwd(), "dist/webchat/widget.js")];
572
+ let widgetJs = null;
573
+ for (const p of possiblePaths) {
574
+ try {
575
+ widgetJs = fs.readFileSync(p, "utf-8");
576
+ break;
577
+ } catch {/* try next */}
578
+ }
579
+ if (widgetJs) {
580
+ res.writeHead(200, {
581
+ "Content-Type": "application/javascript",
582
+ "Cache-Control": "public, max-age=3600",
583
+ ...webchatCors
584
+ });
585
+ res.end(widgetJs);
586
+ } else {
587
+ res.writeHead(200, {
588
+ "Content-Type": "application/javascript",
589
+ ...webchatCors
590
+ });
591
+ res.end('console.warn("[WhaleChat] Widget JS not built. Run: npx esbuild src/webchat/widget.ts --bundle --minify --outfile=dist/webchat/widget.js");');
592
+ }
593
+ return;
594
+ }
595
+
596
+ // ================================================================
597
+ // Billing & Usage routes
598
+ // ================================================================
599
+ if (pathname.startsWith("/usage") || pathname.startsWith("/billing/")) {
600
+ const authHeader = req.headers.authorization;
601
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
602
+ if (!token) {
603
+ req.resume();
604
+ jsonResponse(res, 401, {
605
+ error: "Missing authorization"
606
+ }, corsHeaders);
607
+ return;
608
+ }
609
+ let rawBody;
379
610
  try {
380
- if (res.destroyed || res.writableEnded)
381
- return;
382
- res.write(`data: ${JSON.stringify(event)}\n\n`);
383
- }
384
- catch { /* client disconnected — benign */ }
385
- }
386
- function jsonResponse(res, status, data, corsHeaders) {
387
- res.writeHead(status, { "Content-Type": "application/json", ...corsHeaders });
388
- res.end(JSON.stringify(data));
389
- }
390
- /**
391
- * Strip large base64 data fields from a JSON string up to `keepFrom` offset.
392
- * Uses a linear indexOf scan — safe on strings of any size (no regex stack overflow).
393
- * Handles both `"data":"<base64>"` (Anthropic image blocks) and
394
- * `"__IMAGE__<mime>__<base64>"` text values (Read tool marker format).
395
- */
396
- function stripLargeBase64Fields(raw, keepFrom) {
397
- const MIN_DATA_LEN = 8_000;
398
- // ── Pass 1: Strip "data":"<large base64>" fields (Anthropic image source blocks) ──
399
- let result = raw;
400
- {
401
- const DATA_MARKER = '"data":"';
402
- const parts = [];
403
- let pos = 0;
404
- while (pos < keepFrom) {
405
- const idx = result.indexOf(DATA_MARKER, pos);
406
- if (idx === -1 || idx >= keepFrom) {
407
- parts.push(result.slice(pos, keepFrom));
408
- pos = keepFrom;
409
- break;
410
- }
411
- parts.push(result.slice(pos, idx + DATA_MARKER.length));
412
- pos = idx + DATA_MARKER.length;
413
- // Find closing quote (base64 has no backslashes, so simple scan is safe)
414
- let end = pos;
415
- while (end < result.length && result[end] !== '"')
416
- end++;
417
- if (end - pos >= MIN_DATA_LEN && end <= keepFrom) {
418
- // Large data field in the prune zone — replace with empty
419
- parts.push('"');
420
- pos = end + 1; // skip past the original closing quote (already replaced)
421
- }
422
- // else: small field or near keepFrom boundary — leave intact
423
- }
424
- parts.push(result.slice(keepFrom)); // always keep tail intact
425
- result = parts.join("");
426
- }
427
- // ── Pass 2: Strip __IMAGE__<mime>__<large base64> text markers ──
428
- // If the client-side tool-dispatch regex failed to convert these to image
429
- // content blocks, they stay as huge text strings in tool_result content.
430
- // They appear in JSON as: "__IMAGE__image/png__iVBOR...very long..."
431
- {
432
- const IMG_MARKER = "__IMAGE__";
433
- const adjustedKeepFrom = Math.min(keepFrom, result.length);
434
- const parts = [];
435
- let pos = 0;
436
- while (pos < adjustedKeepFrom) {
437
- const idx = result.indexOf(IMG_MARKER, pos);
438
- if (idx === -1 || idx >= adjustedKeepFrom) {
439
- parts.push(result.slice(pos, adjustedKeepFrom));
440
- pos = adjustedKeepFrom;
441
- break;
442
- }
443
- parts.push(result.slice(pos, idx));
444
- // Find the end of this text value (closing quote of the JSON string)
445
- let end = idx;
446
- while (end < result.length && result[end] !== '"')
447
- end++;
448
- if (end - idx >= MIN_DATA_LEN && end <= adjustedKeepFrom) {
449
- // Large __IMAGE__ marker in the prune zone — replace with placeholder
450
- parts.push("[image pruned]");
451
- pos = end; // land on closing quote; next iteration emits it
452
- }
453
- else {
454
- // Small or near boundary — keep intact
455
- parts.push(result.slice(idx, idx + IMG_MARKER.length));
456
- pos = idx + IMG_MARKER.length;
457
- }
458
- }
459
- parts.push(result.slice(adjustedKeepFrom));
460
- result = parts.join("");
461
- }
462
- return result;
463
- }
464
- async function readBody(req) {
465
- // 500MB hard limit — conversation history with many large images can be very large.
466
- // For bodies over 10MB we prune old base64 image data from the raw string before
467
- // JSON.parse, so subsequent requests in the same conversation stay bounded.
468
- const MAX_BODY = 524_288_000; // 500MB
469
- return new Promise((resolve, reject) => {
470
- const chunks = [];
471
- let size = 0;
472
- let rejected = false;
473
- req.on("data", (chunk) => {
474
- size += chunk.length;
475
- if (size > MAX_BODY && !rejected) {
476
- rejected = true;
477
- req.resume(); // drain to avoid Fly proxy 502
478
- reject(new Error("Request body too large (max 500MB)"));
479
- return;
480
- }
481
- if (!rejected)
482
- chunks.push(chunk);
483
- });
484
- req.on("end", () => {
485
- if (rejected)
486
- return;
487
- // Detect body truncation: if Content-Length was sent and we received less
488
- const declaredLen = parseInt(req.headers["content-length"] || "0", 10);
489
- if (declaredLen > 0 && size < declaredLen) {
490
- log.error({ declaredLen, receivedLen: size }, "Request body truncated during transmission");
491
- reject(new Error(`Body truncated: expected ${declaredLen} bytes, got ${size}`));
492
- return;
493
- }
494
- let raw = Buffer.concat(chunks).toString("utf8");
495
- // Strip old base64 data from history when body is large.
496
- // Threshold lowered to 1MB — image-heavy conversations (e.g. reading
497
- // product photos) easily exceed 10MB with just a few images.
498
- // Keep the last 1MB intact (current user message with fresh images).
499
- // Uses a linear scan to avoid String.replace() stack overflow on huge strings.
500
- if (raw.length > 1_000_000) {
501
- const keepFrom = Math.max(0, raw.length - 1_048_576); // keep last 1MB
502
- raw = stripLargeBase64Fields(raw, keepFrom);
503
- log.info({ originalBytes: size, prunedBytes: Buffer.byteLength(raw) }, "Pruned old base64 data from request body");
504
- }
505
- resolve(raw);
506
- });
507
- req.on("error", reject);
508
- });
509
- }
510
- // ============================================================================
511
- // HISTORY COMPACTION
512
- // ============================================================================
513
- /**
514
- * Compact conversation history to fit within a total character budget.
515
- *
516
- * Does NOT truncate individual messages or tool results — Anthropic's
517
- * context_management API handles clearing old tool uses (clear_tool_uses_20250919)
518
- * and compacting context (compact_20260112) when the context window grows.
519
- *
520
- * This function only enforces a total budget by walking newest→oldest and
521
- * dropping the oldest messages that don't fit.
522
- */
523
- function compactHistory(history, maxHistoryChars) {
524
- if (!history?.length)
525
- return [];
526
- let totalChars = 0;
527
- const compacted = [];
528
- for (let i = history.length - 1; i >= 0; i--) {
529
- const msg = history[i];
530
- const msgChars = JSON.stringify(msg.content).length;
531
- if (totalChars + msgChars > maxHistoryChars)
532
- break;
533
- totalChars += msgChars;
534
- compacted.unshift(msg);
535
- }
536
- // Ensure starts with user message
537
- while (compacted.length > 0 && compacted[0].role !== "user")
538
- compacted.shift();
539
- return compacted;
540
- }
541
- // ============================================================================
542
- // SHARED AGENT HELPERS — used by both SSE chat and channel agent paths
543
- // ============================================================================
544
- /** Build the full system prompt for an agent. Both SSE chat and channel paths
545
- * call this so the prompt logic is never duplicated. */
546
- async function buildAgentSystemPrompt(supabase, agent, storeId, message, tools, opts) {
547
- // --- STATIC portion (cache-friendly, same across conversations) ---
548
- // Sanitize the DB-stored agent system prompt to prevent injection attacks
549
- const rawAgentPrompt = agent.system_prompt || "You are a helpful assistant.";
550
- let systemPrompt = sanitizeAndLog(rawAgentPrompt, "buildAgentSystemPrompt", { agentId: agent.id });
551
- if (storeId)
552
- systemPrompt += `\n\nYou are operating for store_id: ${storeId}. Always include this in tool calls that require it.`;
553
- if (!agent.can_modify)
554
- systemPrompt += "\n\nIMPORTANT: You have read-only access. Do not attempt to modify any data.";
555
- if (agent.tone && agent.tone !== "professional")
556
- systemPrompt += `\n\nTone: Respond in a ${agent.tone} tone.`;
557
- if (agent.verbosity === "concise")
558
- systemPrompt += "\n\nBe concise — short answers, minimal explanation.";
559
- else if (agent.verbosity === "verbose")
560
- systemPrompt += "\n\nBe thorough — provide detailed answers with full context.";
561
- if (agent.context_config) {
562
- const ctx = agent.context_config;
563
- if (ctx.includeLocations && ctx.locationIds?.length)
564
- systemPrompt += `\n\nFocus on these locations: ${ctx.locationIds.join(", ")}`;
565
- if (ctx.includeCustomers && ctx.customerSegments?.length)
566
- systemPrompt += `\n\nFocus on these customer segments: ${ctx.customerSegments.join(", ")}`;
567
- }
568
- // Tool manifest — core tools (full schemas already loaded)
569
- const toolNames = tools.map(t => t.name);
570
- systemPrompt += `\n\n## Available Tools\nYou have ${toolNames.length} core tools available in this session:\n${toolNames.join(", ")}\n\nFor local machine operations, use the \`local_agent\` tool (exec, tools, discover actions). For cloud security tools, use \`kali\`. Do NOT reference CLI-local tools like read_file, write_file, edit_file, glob, grep, run_command — those are not available in this environment.`;
571
- // Extended tools index — names + one-line descriptions only (saves ~20K tokens)
572
- const extendedTools = opts?.extendedTools || [];
573
- if (extendedTools.length > 0) {
574
- systemPrompt += `\n\n## Extended Tools (call discover_tools to activate)\nThese tools are available but not loaded yet. Call \`discover_tools\` with the tool name(s) before using them:\n`;
575
- for (const t of extendedTools) {
576
- // First sentence only for compact display
577
- const shortDesc = t.description.split(".")[0];
578
- systemPrompt += `- **${t.name}**: ${shortDesc}\n`;
579
- }
580
- }
581
- // --- DYNAMIC portion (changes per conversation, prepended to user message) ---
582
- const dynamicParts = [];
583
- // Client-provided session context (SSE chat sends this from SwiftUI/web)
584
- if (opts?.clientContext && typeof opts.clientContext === "object") {
585
- const context = opts.clientContext;
586
- const ctxParts = [];
587
- if (context.storeName)
588
- ctxParts.push(`Store: ${context.storeName}`);
589
- if (context.locationName) {
590
- let loc = `Location: ${context.locationName}`;
591
- if (context.locationAddress)
592
- loc += ` (${context.locationAddress})`;
593
- if (context.locationType)
594
- loc += ` [${context.locationType}]`;
595
- ctxParts.push(loc);
596
- }
597
- if (context.userName) {
598
- ctxParts.push(`User: ${context.userName}${opts.userEmail ? ` (${opts.userEmail})` : ""}`);
599
- }
600
- else if (opts.userEmail) {
601
- ctxParts.push(`User: ${opts.userEmail}`);
602
- }
603
- if (context.conversationType) {
604
- ctxParts.push(`Channel: ${context.conversationTitle || context.conversationType} (${context.conversationType})`);
605
- }
606
- if (ctxParts.length) {
607
- dynamicParts.push(`## Current Session Context\n${ctxParts.join("\n")}`);
608
- }
609
- }
610
- // Parallel DB lookups: customer fetch + memory recall
611
- const customerPromise = (opts?.senderContext?.customerId && storeId)
612
- ? Promise.resolve(supabase.from("v_store_customers")
613
- .select("first_name, last_name, email, phone, loyalty_tier, total_orders, total_spent, lifetime_value")
614
- .eq("id", opts.senderContext.customerId).eq("store_id", storeId).single()).then(({ data }) => data).catch(() => null)
615
- : Promise.resolve(null);
616
- const memoryPromise = storeId
617
- ? Promise.resolve(supabase.rpc("recall_memory", {
618
- p_agent_id: agent.id,
619
- p_query: message.substring(0, 200),
620
- p_type: null,
621
- p_limit: 5,
622
- })).then(({ data }) => data).catch((err) => { log.warn({ err: err.message }, "memory recall failed"); return null; })
623
- : Promise.resolve(null);
624
- const [cust, memories] = await Promise.all([customerPromise, memoryPromise]);
625
- // Channel-specific customer context injection
626
- if (cust && opts?.senderContext?.customerId) {
627
- dynamicParts.push(`## Current Customer\nName: ${cust.first_name} ${cust.last_name}\nEmail: ${cust.email || "N/A"}\nPhone: ${cust.phone || "N/A"}\nLoyalty: ${cust.loyalty_tier || "None"}\nOrders: ${cust.total_orders || 0}\nSpent: $${(cust.total_spent || 0).toFixed(2)}\nCustomer ID: ${opts.senderContext.customerId}`);
628
- }
629
- else if (opts?.senderContext) {
630
- const sc = opts.senderContext;
631
- dynamicParts.push(`## Sender\nID: ${sc.senderId}\nName: ${sc.senderName || "Unknown"}\nChannel: ${sc.channelType || "unknown"}${sc.channelName ? ` (${sc.channelName})` : ""}\n(No customer record matched — use CRM tools to look them up if needed)`);
632
- }
633
- // Memory recall — inject relevant memories (capped at 5, 100 chars each)
634
- if (memories?.length) {
635
- const memBlock = memories.map((m) => `- [${m.memory_type}] ${m.key}: ${JSON.stringify(m.value).substring(0, 100)}`).join("\n");
636
- dynamicParts.push(`## Agent Memory\nRelevant memories from previous conversations:\n${memBlock}`);
637
- }
638
- const dynamicContext = dynamicParts.join("\n\n");
639
- return { systemPrompt, dynamicContext };
640
- }
641
- /** Persist everything after an agent turn — messages, audit, memory, cost.
642
- * Called by both SSE chat and channel paths so nothing is ever missed. */
643
- async function persistAgentTurn(supabase, agent, opts) {
644
- const { conversationId, storeId, agentId, agentModel, traceId, message, result, source, chatStartTime, chatEndTime, userId, userEmail, senderContext } = opts;
645
- // ── Persist user + assistant messages to ai_messages ──
611
+ rawBody = await readBody(req);
612
+ } catch {
613
+ jsonResponse(res, 413, {
614
+ error: "Request body too large"
615
+ }, corsHeaders);
616
+ return;
617
+ }
618
+ let billingBody = null;
646
619
  try {
647
- await supabase.from("ai_messages").insert([
648
- {
649
- conversation_id: conversationId, role: "user",
650
- content: [{ type: "text", text: message }],
651
- token_count: Math.ceil(message.length / 4),
652
- },
653
- {
654
- conversation_id: conversationId, role: "assistant",
655
- content: [{ type: "text", text: result.finalText || "" }],
656
- is_tool_use: result.toolCallCount > 0,
657
- tool_names: result.toolsUsed?.length ? result.toolsUsed : null,
658
- token_count: result.tokens.input + result.tokens.output,
659
- },
660
- ]);
661
- }
662
- catch (err) {
663
- log.error({ err: err.message }, "message persist failed");
664
- }
665
- // ── Update conversation metadata ──
620
+ if (rawBody) billingBody = JSON.parse(rawBody);
621
+ } catch {
622
+ jsonResponse(res, 400, {
623
+ error: "Invalid JSON"
624
+ }, corsHeaders);
625
+ return;
626
+ }
627
+ const supabase = getServiceClient();
628
+ // Extract userId from JWT for checkout/portal endpoints
629
+ let billingUserId;
666
630
  try {
667
- await supabase.from("ai_conversations").update({
668
- metadata: {
669
- agentName: agent.name,
670
- source,
671
- model: agentModel,
672
- lastTurnTokens: result.tokens.input + result.tokens.output,
673
- lastToolCalls: result.toolCallCount,
674
- lastDurationMs: chatEndTime - chatStartTime,
675
- // Channel-specific (null when SSE chat — that's fine, no clutter)
676
- ...(senderContext ? {
677
- channel_type: senderContext.channelType || null,
678
- channel_id: senderContext.channelId || null,
679
- channel_name: senderContext.channelName || null,
680
- sender_id: senderContext.senderId || null,
681
- customer_id: senderContext.customerId || null,
682
- customer_name: senderContext.customerName || null,
683
- } : {}),
684
- },
685
- }).eq("id", conversationId);
686
- }
687
- catch (err) {
688
- log.error({ err: err.message }, "conversation update failed");
689
- }
690
- // ── Telemetry: user message ClickHouse ──
631
+ const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString());
632
+ billingUserId = payload.sub;
633
+ } catch {/* not a JWT — service role key */}
634
+ const result = await handleBillingRoutes(pathname, req.method || "GET", billingBody, supabase, {
635
+ userId: billingUserId,
636
+ isServiceRole: safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT)
637
+ });
638
+ if (result) {
639
+ jsonResponse(res, result.status, result.body, corsHeaders);
640
+ } else {
641
+ jsonResponse(res, 404, {
642
+ error: `No route: ${req.method} ${pathname}`
643
+ }, corsHeaders);
644
+ }
645
+ return;
646
+ }
647
+
648
+ // ================================================================
649
+ // Node & Channel management routes (supports GET, POST, PUT, DELETE)
650
+ // Must be before the method gate since GET /channels/:id/messages is valid
651
+ // ================================================================
652
+ if (pathname.startsWith("/nodes") || pathname.startsWith("/channels")) {
653
+ const authHeader = req.headers.authorization;
654
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
655
+ if (!token) {
656
+ req.resume();
657
+ jsonResponse(res, 401, {
658
+ error: "Missing authorization"
659
+ }, corsHeaders);
660
+ return;
661
+ }
662
+ let rawBody;
691
663
  try {
692
- queueSpan(auditRowToSpan({
693
- action: "chat.user_message",
694
- severity: "info",
695
- store_id: storeId || null,
696
- resource_type: "chat_message",
697
- resource_id: agentId,
698
- request_id: traceId,
699
- conversation_id: conversationId,
700
- user_id: userId || null,
701
- user_email: userEmail || null,
702
- source,
703
- service_name: "agent-server",
704
- span_kind: "INTERNAL",
705
- status_code: "OK",
706
- start_time: new Date().toISOString(),
707
- end_time: new Date().toISOString(),
708
- duration_ms: 0,
709
- input_bytes: typeof message === "string" ? message.length : 0,
710
- details: {
711
- message_preview: message.substring(0, 200),
712
- agent_id: agentId,
713
- model: agentModel,
714
- conversation_id: conversationId,
715
- ...(senderContext ? {
716
- channel_type: senderContext.channelType || null,
717
- sender_id: senderContext.senderId || null,
718
- customer_id: senderContext.customerId || null,
719
- } : {}),
720
- },
721
- }));
722
- }
723
- catch (err) {
724
- log.error({ err: err.message }, "audit user_message failed");
725
- }
726
- // ── Telemetry: assistant response → ClickHouse ──
664
+ rawBody = await readBody(req);
665
+ } catch {
666
+ jsonResponse(res, 413, {
667
+ error: "Request body too large"
668
+ }, corsHeaders);
669
+ return;
670
+ }
671
+ let nodeBody = null;
727
672
  try {
728
- queueSpan(auditRowToSpan({
729
- action: "chat.assistant_response",
730
- severity: "info",
731
- store_id: storeId || null,
732
- resource_type: "chat_message",
733
- resource_id: agentId,
734
- request_id: traceId,
735
- conversation_id: conversationId,
736
- duration_ms: chatEndTime - chatStartTime,
737
- user_id: userId || null,
738
- user_email: userEmail || null,
739
- source,
740
- input_tokens: result.tokens.input,
741
- output_tokens: result.tokens.output,
742
- total_cost: result.costUsd,
743
- model: agentModel,
744
- trace_id: traceId,
745
- span_kind: "INTERNAL",
746
- service_name: "agent-server",
747
- status_code: "OK",
748
- start_time: new Date(chatStartTime).toISOString(),
749
- end_time: new Date(chatEndTime).toISOString(),
750
- stop_reason: result.stopReason || undefined,
751
- turn_number: result.turnCount || 1,
752
- details: {
753
- response_preview: (result.finalText || "").substring(0, 500),
754
- agent_id: agentId,
755
- model: agentModel,
756
- "gen_ai.request.model": agentModel,
757
- "gen_ai.usage.input_tokens": result.tokens.input,
758
- "gen_ai.usage.output_tokens": result.tokens.output,
759
- "gen_ai.usage.cache_creation_tokens": result.tokens.cacheCreation || 0,
760
- "gen_ai.usage.cache_read_tokens": result.tokens.cacheRead || 0,
761
- "gen_ai.usage.cost": result.costUsd,
762
- turn_count: result.turnCount || 1,
763
- tool_calls: result.toolCallCount,
764
- tool_names: result.toolsUsed,
765
- conversation_id: conversationId,
766
- session_cost_usd: result.costUsd,
767
- cache_creation_tokens: result.tokens.cacheCreation || 0,
768
- cache_read_tokens: result.tokens.cacheRead || 0,
769
- cache_hit_rate: result.tokens.input > 0
770
- ? Math.round((result.tokens.cacheRead || 0) / ((result.tokens.cacheRead || 0) + result.tokens.input) * 10000) / 100
771
- : 0,
772
- cache_cost_savings_pct: result.tokens.input > 0 && (result.tokens.cacheRead || 0) > 0
773
- ? Math.round((result.tokens.cacheRead || 0) * 0.9 / ((result.tokens.cacheRead || 0) + result.tokens.input) * 10000) / 100
774
- : 0,
775
- loop_detector_stats: result.loopDetectorStats || null,
776
- turns: result.turns || [],
777
- ...(senderContext ? {
778
- channel_type: senderContext.channelType || null,
779
- channel_id: senderContext.channelId || null,
780
- channel_name: senderContext.channelName || null,
781
- sender_id: senderContext.senderId || null,
782
- customer_id: senderContext.customerId || null,
783
- customer_name: senderContext.customerName || null,
784
- } : {}),
785
- },
786
- }));
787
- }
788
- catch (err) {
789
- log.error({ err: err.message }, "audit assistant_response failed");
790
- }
791
- // ── Memory extraction — awaited with retry ──
792
- if (storeId && result.finalText && result.finalText.length > 50) {
793
- try {
794
- await extractAndStoreMemories(supabase, getAnthropicClient(agent), agentId, storeId, message, result.finalText);
795
- }
796
- catch (err1) {
797
- // Retry once after 2s
798
- try {
799
- await new Promise(r => setTimeout(r, 2000));
800
- await extractAndStoreMemories(supabase, getAnthropicClient(agent), agentId, storeId, message, result.finalText);
801
- }
802
- catch (err2) {
803
- log.error({ err: err2.message }, "memory extract failed after retry");
804
- queueSpan(auditRowToSpan({
805
- action: "memory.extraction_failed", severity: "warning",
806
- store_id: storeId || null, resource_type: "agent_memory",
807
- resource_id: agentId, conversation_id: conversationId,
808
- user_id: userId || null, user_email: userEmail || null,
809
- error_type: classifyErrorType(err2.message) || undefined,
810
- service_name: "agent-server", span_kind: "INTERNAL", status_code: "ERROR",
811
- start_time: new Date().toISOString(), end_time: new Date().toISOString(),
812
- error_message: err2.message,
813
- details: { error: err2.message, user_message_preview: message.substring(0, 100) },
814
- }));
815
- }
816
- }
817
- }
818
- // ── Cost budget tracking — awaited with retry ──
819
- if (storeId && result.costUsd > 0) {
820
- try {
821
- await updateCostBudgets(supabase, storeId, agentId, result.costUsd);
822
- }
823
- catch (err1) {
824
- try {
825
- await new Promise(r => setTimeout(r, 1000));
826
- await updateCostBudgets(supabase, storeId, agentId, result.costUsd);
827
- }
828
- catch (err2) {
829
- log.error({ err: err2.message }, "cost budget update failed after retry");
830
- // Flag conversation metadata so budget sync can be reconciled later
831
- await supabase.from("ai_conversations").update({
832
- metadata: { budget_sync_failed: true, failed_cost_usd: result.costUsd },
833
- }).eq("id", conversationId).then(() => { });
834
- }
835
- }
836
- }
837
- }
838
- // ============================================================================
839
- // AGENT CHAT HANDLER
840
- // ============================================================================
841
- async function handleAgentChat(req, res, supabase, body, user, isServiceRole, token, corsHeaders) {
842
- const { agentId, message, conversationHistory, source, conversationId, context, attachments } = body;
843
- let storeId = body.storeId;
844
- if (!agentId || !message) {
845
- jsonResponse(res, 400, { error: "agentId and message required" }, corsHeaders);
673
+ if (rawBody) nodeBody = JSON.parse(rawBody);
674
+ } catch {
675
+ jsonResponse(res, 400, {
676
+ error: "Invalid JSON"
677
+ }, corsHeaders);
678
+ return;
679
+ }
680
+ const supabase = getServiceClient();
681
+ let userId;
682
+ const isServiceRole = safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
683
+ if (!isServiceRole) {
684
+ const {
685
+ data: {
686
+ user: authUser
687
+ }
688
+ } = await supabase.auth.getUser(token);
689
+ if (authUser) userId = authUser.id;
690
+ }
691
+ const result = await handleNodeRoutes(pathname, req.method || "GET", nodeBody, supabase, {
692
+ userId,
693
+ isServiceRole,
694
+ rawToken: token
695
+ }, url.searchParams);
696
+ if (result) {
697
+ jsonResponse(res, result.status, result.body, corsHeaders);
698
+ } else {
699
+ jsonResponse(res, 404, {
700
+ error: `No route: ${req.method} ${pathname}`
701
+ }, corsHeaders);
702
+ }
703
+ return;
704
+ }
705
+
706
+ // ================================================================
707
+ // Session API routes (supports GET, POST)
708
+ // Must be before the method gate since GET /sessions is valid
709
+ // ================================================================
710
+ if (pathname.startsWith("/sessions")) {
711
+ const authHeader = req.headers.authorization;
712
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
713
+ if (!token) {
714
+ req.resume();
715
+ jsonResponse(res, 401, {
716
+ error: "Missing authorization"
717
+ }, corsHeaders);
718
+ return;
719
+ }
720
+ const supabase = getServiceClient();
721
+ let userId;
722
+ const isServiceRole = safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
723
+ if (!isServiceRole) {
724
+ const {
725
+ data: {
726
+ user: authUser
727
+ }
728
+ } = await supabase.auth.getUser(token);
729
+ if (authUser) userId = authUser.id;
730
+ }
731
+ const handled = await handleSessionRoutes(req, res, pathname, url, corsHeaders, supabase, {
732
+ userId,
733
+ isServiceRole
734
+ });
735
+ if (!handled) {
736
+ jsonResponse(res, 404, {
737
+ error: `No route: ${req.method} ${pathname}`
738
+ }, corsHeaders);
739
+ }
740
+ return;
741
+ }
742
+ if (req.method !== "POST" && req.method !== "DELETE" && req.method !== "PUT") {
743
+ jsonResponse(res, 405, {
744
+ error: "Method not allowed"
745
+ }, corsHeaders);
746
+ return;
747
+ }
748
+ try {
749
+ // ================================================================
750
+ // Phase 4.2: Resume conversation from checkpoint
751
+ // POST /conversations/:id/resume
752
+ // ================================================================
753
+ const resumeMatch = pathname.match(/^\/conversations\/([a-f0-9-]+)\/resume$/);
754
+ if (resumeMatch && req.method === "POST") {
755
+ const convId = resumeMatch[1];
756
+ const authHeader = req.headers.authorization;
757
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
758
+ const isInternal = safeCompare(token, FLY_INTERNAL_SECRET) || safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
759
+ if (!isInternal && !token) {
760
+ jsonResponse(res, 401, {
761
+ error: "Missing authorization"
762
+ }, corsHeaders);
846
763
  return;
847
- }
848
- if (typeof message === "string" && message.length > 100_000) {
849
- jsonResponse(res, 400, { error: "Message too long (max 100K characters)" }, corsHeaders);
764
+ }
765
+ const supabase = getServiceClient();
766
+ if (!isInternal) {
767
+ const {
768
+ data: {
769
+ user: authUser
770
+ },
771
+ error: authError
772
+ } = await supabase.auth.getUser(token);
773
+ if (authError || !authUser) {
774
+ jsonResponse(res, 401, {
775
+ error: "Invalid or expired token"
776
+ }, corsHeaders);
777
+ return;
778
+ }
779
+ }
780
+
781
+ // Drain the request body (even if we don't use it) to prevent Fly proxy stalls
782
+ try {
783
+ await readBody(req);
784
+ } catch {/* body not needed */}
785
+ const checkpoint = await loadCheckpoint(supabase, convId);
786
+ if (!checkpoint) {
787
+ jsonResponse(res, 404, {
788
+ error: "No checkpoint found for this conversation"
789
+ }, corsHeaders);
850
790
  return;
851
- }
852
- // Server-derived store resolution — never trust client blindly
853
- const resolvedStoreId = await resolveAndValidateStoreId(supabase, storeId, user, isServiceRole, token, body.userId);
854
- // For service-role callers (workflows, internal), also try body.userId fallback
855
- if (!resolvedStoreId && isServiceRole && body.userId) {
856
- const { data: srStores } = await supabase
857
- .from("users")
858
- .select("store_id")
859
- .eq("auth_user_id", body.userId)
860
- .not("store_id", "is", null)
861
- .limit(1);
862
- if (srStores?.length) {
863
- storeId = srStores[0].store_id;
864
- }
865
- }
866
- else {
867
- storeId = resolvedStoreId || undefined;
868
- }
869
- // Fail closed: non-service-role requests MUST have a resolved store
870
- if (!storeId && !isServiceRole) {
871
- jsonResponse(res, 400, { error: "storeId required for agent chat" }, corsHeaders);
791
+ }
792
+
793
+ // Parse messages back from serialized form
794
+ let parsedMessages;
795
+ try {
796
+ parsedMessages = JSON.parse(checkpoint.messages);
797
+ } catch {
798
+ jsonResponse(res, 422, {
799
+ error: "Checkpoint messages corrupted"
800
+ }, corsHeaders);
872
801
  return;
873
- }
874
- log.info({ storeId: storeId || "NONE", source: body.source || "unknown", isServiceRole, userId: user?.id || body.userId || "NONE" }, "agent-chat request");
875
- // Agent chat rate limiting — per-store + concurrent cap
876
- const rateLimitStoreId = storeId || agentId; // fallback to agentId if no store
877
- const rateCheck = checkAgentChatRateLimit(rateLimitStoreId);
878
- if (!rateCheck.allowed) {
879
- res.writeHead(429, { "Content-Type": "application/json", ...corsHeaders });
880
- res.end(JSON.stringify({ error: rateCheck.error }));
802
+ }
803
+ jsonResponse(res, 200, {
804
+ success: true,
805
+ conversation_id: checkpoint.conversation_id,
806
+ turn: checkpoint.turn,
807
+ messages: parsedMessages,
808
+ tokens_used: checkpoint.tokens_used,
809
+ cost_so_far: checkpoint.cost_so_far,
810
+ tools_used: checkpoint.tools_used,
811
+ checkpointed_at: checkpoint.created_at
812
+ }, corsHeaders);
813
+ return;
814
+ }
815
+
816
+ // ================================================================
817
+ // Phase 2: Approval response endpoint
818
+ // POST /approvals/:id/respond
819
+ // ================================================================
820
+ const approvalMatch = pathname.match(/^\/approvals\/([a-f0-9-]+)\/respond$/);
821
+ if (approvalMatch) {
822
+ const approvalId = approvalMatch[1];
823
+ const authHeader = req.headers.authorization;
824
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
825
+ const isInternal = safeCompare(token, FLY_INTERNAL_SECRET) || safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
826
+ if (!isInternal && !token) {
827
+ jsonResponse(res, 401, {
828
+ error: "Missing authorization"
829
+ }, corsHeaders);
881
830
  return;
882
- }
883
- agentChatConcurrent++;
884
- try {
885
- const userId = user?.id || body.userId || "";
886
- const userEmail = user?.email || body.userEmail || null;
887
- const agent = await loadAgentConfig(supabase, agentId, storeId);
888
- if (!agent) {
889
- jsonResponse(res, 404, { error: "Agent not found" }, corsHeaders);
890
- return;
891
- }
892
- const { core: coreTools, extended: extendedTools } = await loadTools(supabase);
893
- setExtendedToolsCache(extendedTools); // Populate discover_tools handler cache
894
- const { rows: userToolRows, defs: userToolDefs } = storeId
895
- ? await loadUserTools(supabase, storeId)
896
- : { rows: [], defs: [] };
897
- const tools = getToolsForAgent(agent, coreTools, userToolDefs);
898
- const traceId = randomUUID();
899
- const agentModel = agent.model || MODELS.SONNET;
900
- // Resolve or create conversation
901
- let activeConversationId;
902
- if (conversationId) {
903
- activeConversationId = conversationId;
904
- }
905
- else {
906
- let conv = await supabase
907
- .from("ai_conversations")
908
- .insert({
909
- store_id: storeId || null,
910
- user_id: userId || null,
911
- agent_id: agentId,
912
- title: message.substring(0, 100),
913
- metadata: { agentName: agent.name, source: source || "whale_chat" },
914
- })
915
- .select("id")
916
- .single();
917
- if (conv.error) {
918
- log.error({ err: conv.error.message, details: conv.error.details, hint: conv.error.hint, storeId, userId, agentId }, "conversation create failed");
919
- // Retry without user_id (may be FK constraint)
920
- conv = await supabase
921
- .from("ai_conversations")
922
- .insert({
923
- store_id: storeId || null,
924
- agent_id: agentId,
925
- title: message.substring(0, 100),
926
- metadata: { agentName: agent.name, source: source || "whale_chat", userId, userEmail },
927
- })
928
- .select("id")
929
- .single();
930
- if (conv.error) {
931
- log.error({ err: conv.error.message, details: conv.error.details, hint: conv.error.hint }, "conversation retry create failed");
932
- }
933
- }
934
- activeConversationId = conv.data?.id || randomUUID();
935
- log.info({ conversationId: activeConversationId, fromDb: !!conv.data?.id }, "conversation resolved");
936
- }
937
- // Build system prompt — shared helper ensures SSE chat + channel paths are identical
938
- const { systemPrompt, dynamicContext } = await buildAgentSystemPrompt(supabase, agent, storeId, message, tools, {
939
- clientContext: context, userId, userEmail, extendedTools: getExtendedToolsIndex(),
940
- });
941
- const anthropic = getAnthropicClient(agent);
942
- // Resolve all behavioral config from DB — single source of truth
943
- const resolved = resolveAgentLoopConfig(agent, "sse");
944
- if (resolved.defaultsUsed.length > 0) {
945
- log.info({ agentId, defaults: resolved.defaultsUsed }, "agent config defaults applied");
946
- }
947
- const MAX_HISTORY_CHARS = resolved.maxHistoryChars;
948
- // Build user message — multi-modal if image attachments present
949
- let userContent;
950
- if (attachments?.length) {
951
- const contentBlocks = [];
952
- for (const att of attachments) {
953
- if (att.type === "image" && att.media_type && att.data) {
954
- contentBlocks.push({
955
- type: "image",
956
- source: { type: "base64", media_type: att.media_type, data: att.data },
957
- });
958
- }
959
- }
960
- contentBlocks.push({ type: "text", text: message });
961
- userContent = contentBlocks;
962
- }
963
- else {
964
- userContent = message;
965
- }
966
- // Prepend dynamic context to user message to keep system prompt static (cache-friendly)
967
- const contextPrefix = dynamicContext ? `[Context]\n${dynamicContext}\n\n[User Message]\n` : "";
968
- const finalUserContent = typeof userContent === "string"
969
- ? contextPrefix + userContent
970
- : [...(contextPrefix ? [{ type: "text", text: contextPrefix }] : []), ...userContent];
971
- const messages = [
972
- ...compactHistory(conversationHistory || [], MAX_HISTORY_CHARS),
973
- { role: "user", content: finalUserContent },
974
- ];
975
- // Start SSE stream
976
- res.writeHead(200, {
977
- "Content-Type": "text/event-stream",
978
- "Cache-Control": "no-cache",
979
- Connection: "keep-alive",
980
- ...corsHeaders,
981
- });
982
- // Client disconnect detection
983
- let clientDisconnected = false;
984
- req.on("close", () => { clientDisconnected = true; });
985
- const startedAt = Date.now();
986
- const chatStartTime = Date.now();
831
+ }
832
+ const supabase = getServiceClient();
833
+ let userId = null;
834
+ if (!isInternal) {
835
+ const {
836
+ data: {
837
+ user: authUser
838
+ },
839
+ error: authError
840
+ } = await supabase.auth.getUser(token);
841
+ if (authError || !authUser) {
842
+ jsonResponse(res, 401, {
843
+ error: "Invalid or expired token"
844
+ }, corsHeaders);
845
+ return;
846
+ }
847
+ userId = authUser.id;
848
+ }
849
+ let rawBody;
850
+ try {
851
+ rawBody = await readBody(req);
852
+ } catch {
853
+ jsonResponse(res, 413, {
854
+ error: "Request body too large"
855
+ }, corsHeaders);
856
+ return;
857
+ }
858
+ let body;
859
+ try {
860
+ body = JSON.parse(rawBody);
861
+ } catch {
862
+ jsonResponse(res, 400, {
863
+ error: "Invalid JSON"
864
+ }, corsHeaders);
865
+ return;
866
+ }
867
+ if (!body.status) {
868
+ jsonResponse(res, 400, {
869
+ error: "status required (approved/rejected)"
870
+ }, corsHeaders);
871
+ return;
872
+ }
873
+
874
+ // Get store_id from approval
875
+ const {
876
+ data: approval
877
+ } = await supabase.from("workflow_approval_requests").select("store_id, run_id").eq("id", approvalId).single();
878
+ if (!approval) {
879
+ jsonResponse(res, 404, {
880
+ error: "Approval not found"
881
+ }, corsHeaders);
882
+ return;
883
+ }
884
+
885
+ // P1 FIX: Verify user has access to the approval's store
886
+ if (!isInternal && userId) {
887
+ const {
888
+ data: approvalMembership
889
+ } = await supabase.from("store_members").select("id").eq("store_id", approval.store_id).eq("user_id", userId).single();
890
+ if (!approvalMembership) {
891
+ jsonResponse(res, 403, {
892
+ error: "Not authorized to respond to this approval"
893
+ }, corsHeaders);
894
+ return;
895
+ }
896
+ }
897
+ const {
898
+ data: result,
899
+ error
900
+ } = await supabase.rpc("respond_to_approval", {
901
+ p_approval_id: approvalId,
902
+ p_store_id: approval.store_id,
903
+ p_response: body.status,
904
+ p_response_data: body.response_data || {},
905
+ p_responded_by: userId || body.responded_by || null
906
+ });
907
+ if (error) {
908
+ jsonResponse(res, 500, {
909
+ success: false,
910
+ error: error.message
911
+ }, corsHeaders);
912
+ return;
913
+ }
914
+
915
+ // Inline resume execute next step immediately
916
+ if (result?.success && approval.run_id) {
987
917
  try {
988
- const result = await runServerAgentLoop({
989
- anthropic,
990
- supabase,
991
- model: agentModel,
992
- systemPrompt,
993
- messages,
994
- tools,
995
- extendedTools,
996
- // All behavioral knobs from resolved config — DB is single source of truth
997
- maxTurns: resolved.maxTurns,
998
- temperature: resolved.temperature,
999
- maxTokens: resolved.maxTokens,
1000
- maxConcurrentTools: resolved.maxConcurrentTools,
1001
- enableDelegation: resolved.enableDelegation,
1002
- enableModelRouting: resolved.enableModelRouting,
1003
- contextOverrides: resolved.contextOverrides,
1004
- subagentMaxTokens: resolved.subagentMaxTokens,
1005
- subagentMaxTurns: resolved.subagentMaxTurns,
1006
- subagentTemperature: resolved.subagentTemperature,
1007
- storeId,
1008
- traceId,
1009
- userId,
1010
- userEmail,
1011
- source,
1012
- conversationId: activeConversationId,
1013
- agentId,
1014
- executeTool: async (toolName, args, sourceOverride, onToolProgress) => {
1015
- const toolArgs = { ...args };
1016
- if (!toolArgs.store_id && storeId)
1017
- toolArgs.store_id = storeId;
1018
- return executeTool(supabase, toolName, toolArgs, storeId, traceId, userId, userEmail, sourceOverride || source, activeConversationId, userToolRows, agentId, onToolProgress, true);
1019
- },
1020
- onToolProgress: (name, progress) => sendSSE(res, { type: "tool_progress", name, progress }),
1021
- onText: (text) => sendSSE(res, { type: "text", text }),
1022
- onToolStart: (name, input) => {
1023
- // Only send when input is available — deduplicates streaming double-fire
1024
- // (sse-parser fires onToolStart twice: once on content_block_start without input,
1025
- // once on content_block_stop with parsed input)
1026
- if (input !== undefined) {
1027
- sendSSE(res, { type: "tool_start", name, input });
1028
- }
1029
- },
1030
- onToolResult: (name, success, r) => sendSSE(res, { type: "tool_result", name, success, result: r }),
1031
- onSubagentProgress: (evt) => {
1032
- sendSSE(res, { type: "subagent", subagentId: evt.subagentId, subagentEvent: evt.event, name: evt.toolName });
1033
- },
1034
- clientDisconnected: { get value() { return clientDisconnected; } },
1035
- startedAt,
1036
- maxDurationMs: resolved.maxDurationMs,
1037
- });
1038
- // Send usage SSE
1039
- sendSSE(res, {
1040
- type: "usage",
1041
- usage: {
1042
- input_tokens: result.tokens.input,
1043
- output_tokens: result.tokens.output,
1044
- cache_creation_tokens: result.tokens.cacheCreation,
1045
- cache_read_tokens: result.tokens.cacheRead,
1046
- cost_usd: result.costUsd,
1047
- },
1048
- });
1049
- // Persist everything — shared helper ensures SSE chat + channel paths are identical
1050
- await persistAgentTurn(supabase, agent, {
1051
- conversationId: activeConversationId,
1052
- storeId, agentId, agentModel, traceId, message, result,
1053
- source: source || "whale_chat",
1054
- chatStartTime, chatEndTime: Date.now(),
1055
- userId, userEmail,
1056
- });
1057
- sendSSE(res, { type: "done", conversationId: activeConversationId });
1058
- }
1059
- catch (err) {
1060
- sendSSE(res, { type: "error", error: sanitizeError(err) });
918
+ await executeInlineChain(supabase, approval.run_id);
919
+ } catch (err) {
920
+ log.error({
921
+ err: err.message
922
+ }, "inline chain failed after approval");
1061
923
  }
1062
- res.end();
1063
- }
1064
- finally {
1065
- agentChatConcurrent--;
924
+ }
925
+ jsonResponse(res, result?.success ? 200 : 422, result, corsHeaders);
926
+ return;
1066
927
  }
1067
- }
1068
- // ============================================================================
1069
- // MEMORY EXTRACTIONextract key facts after agent conversation
1070
- // ============================================================================
1071
- async function extractAndStoreMemories(supabase, anthropic, agentId, storeId, userMessage, assistantResponse) {
1072
- const extraction = await anthropic.messages.create({
1073
- model: "claude-haiku-4-5-20251001",
1074
- max_tokens: 500,
1075
- system: `Extract key facts worth remembering from this conversation turn.
1076
- Return JSON array: [{"key": "short_key", "value": {"detail": "..."}, "type": "short_term|long_term|entity"}]
1077
- Rules:
1078
- - Only extract genuinely useful facts (preferences, decisions, corrections, entities)
1079
- - "entity" for people/businesses/products mentioned
1080
- - "long_term" for preferences, patterns, decisions
1081
- - "short_term" for context that may expire
1082
- - Return [] if nothing worth remembering
1083
- - Max 3 items per turn`,
1084
- messages: [{
1085
- role: "user",
1086
- content: `User: ${userMessage.substring(0, 500)}\n\nAssistant: ${assistantResponse.substring(0, 1000)}`
1087
- }],
1088
- });
1089
- const text = extraction.content.find(b => b.type === "text")?.text || "[]";
1090
- const match = text.match(/\[[\s\S]*\]/);
1091
- if (!match)
928
+
929
+ // ================================================================
930
+ // Waitpoint completionAPI endpoint
931
+ // POST /waitpoints/:token/complete
932
+ // ================================================================
933
+ const waitpointMatch = pathname.match(/^\/waitpoints\/([a-f0-9-]+)\/complete$/);
934
+ if (waitpointMatch) {
935
+ const token = waitpointMatch[1];
936
+ const authHeader = req.headers.authorization;
937
+ const authToken = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
938
+ const isInternal = safeCompare(authToken, FLY_INTERNAL_SECRET) || safeCompare(authToken, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(authToken, SERVICE_ROLE_JWT);
939
+ if (!isInternal && !authToken) {
940
+ jsonResponse(res, 401, {
941
+ error: "Missing authorization"
942
+ }, corsHeaders);
1092
943
  return;
1093
- let items;
1094
- try {
1095
- items = JSON.parse(match[0]);
1096
- }
1097
- catch {
1098
- return; // Malformed JSON from extraction
1099
- }
1100
- for (const item of items.slice(0, 3)) {
1101
- if (!item.key)
1102
- continue;
1103
- await supabase.rpc("store_memory", {
1104
- p_agent_id: agentId,
1105
- p_store_id: storeId,
1106
- p_type: item.type || "short_term",
1107
- p_key: item.key,
1108
- p_value: item.value || {},
1109
- });
1110
- }
1111
- }
1112
- // ============================================================================
1113
- // COST BUDGET TRACKING — increment active budgets after each conversation
1114
- // ============================================================================
1115
- // P0 FIX: Atomic cost budget increment via RPC (fixes TOCTOU race on concurrent updates)
1116
- async function updateCostBudgets(supabase, storeId, agentId, costUsd) {
1117
- const { error } = await supabase.rpc("increment_cost_budget", {
1118
- p_store_id: storeId,
1119
- p_agent_id: agentId,
1120
- p_cost_usd: costUsd,
1121
- });
1122
- if (error) {
1123
- console.error("[cost-budget] increment_cost_budget RPC failed:", error.message);
1124
- }
1125
- }
1126
- // ============================================================================
1127
- // HTTP SERVER
1128
- // ============================================================================
1129
- // Connection tracking for graceful shutdown draining
1130
- let activeRequests = 0;
1131
- const server = http.createServer(async (req, res) => {
1132
- activeRequests++;
1133
- res.on("close", () => { activeRequests--; });
1134
- const origin = req.headers.origin || "";
1135
- const corsHeaders = getCorsHeaders(origin);
1136
- // Health check — readiness-aware for Fly.io
1137
- if (req.method === "GET" && (req.url === "/" || req.url === "/health")) {
1138
- const ready = isReady();
1139
- const status = ready ? 200 : 503;
1140
- const agentStats = getGatewayStats();
1141
- jsonResponse(res, status, {
1142
- status: ready ? "ok" : "starting",
1143
- version: process.env.npm_package_version || "6.4.0",
1144
- uptime: Math.floor(process.uptime()),
1145
- pg_listen: pgListenReady,
1146
- worker_pool: workerPoolReady,
1147
- local_agents: agentStats.total_agents,
944
+ }
945
+ const supabase = getServiceClient();
946
+ let rawBody;
947
+ try {
948
+ rawBody = await readBody(req);
949
+ } catch {
950
+ jsonResponse(res, 413, {
951
+ error: "Request body too large"
1148
952
  }, corsHeaders);
1149
953
  return;
1150
- }
1151
- // CORS preflight
1152
- if (req.method === "OPTIONS") {
1153
- res.writeHead(204, corsHeaders);
1154
- res.end();
954
+ }
955
+ let body;
956
+ try {
957
+ body = JSON.parse(rawBody || "{}");
958
+ } catch {
959
+ jsonResponse(res, 400, {
960
+ error: "Invalid JSON"
961
+ }, corsHeaders);
1155
962
  return;
1156
- }
1157
- const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
1158
- const pathname = url.pathname;
963
+ }
964
+
965
+ // Find waitpoint
966
+ const {
967
+ data: wp
968
+ } = await supabase.from("waitpoint_tokens").select("id, run_id, step_run_id, store_id, expires_at, status").eq("token", token).single();
969
+ if (!wp) {
970
+ jsonResponse(res, 404, {
971
+ error: "Waitpoint token not found"
972
+ }, corsHeaders);
973
+ return;
974
+ }
975
+ if (wp.status === "completed") {
976
+ jsonResponse(res, 409, {
977
+ error: "Waitpoint already completed"
978
+ }, corsHeaders);
979
+ return;
980
+ }
981
+ if (new Date(wp.expires_at) < new Date()) {
982
+ jsonResponse(res, 410, {
983
+ error: "Waitpoint expired"
984
+ }, corsHeaders);
985
+ return;
986
+ }
987
+
988
+ // Complete it
989
+ await supabase.from("waitpoint_tokens").update({
990
+ status: "completed",
991
+ completion_data: body.data || {},
992
+ completed_at: new Date().toISOString()
993
+ }).eq("id", wp.id);
994
+ await supabase.from("workflow_step_runs").update({
995
+ status: "pending",
996
+ input: {
997
+ waitpoint_completed: true,
998
+ waitpoint_data: body.data || {}
999
+ }
1000
+ }).eq("id", wp.step_run_id).eq("status", "waiting");
1001
+
1002
+ // Inline resume
1003
+ try {
1004
+ await executeInlineChain(supabase, wp.run_id);
1005
+ } catch (err) {
1006
+ log.error({
1007
+ err: err.message,
1008
+ runId: wp.run_id
1009
+ }, "inline chain failed after waitpoint");
1010
+ }
1011
+ jsonResponse(res, 200, {
1012
+ success: true,
1013
+ run_id: wp.run_id
1014
+ }, corsHeaders);
1015
+ return;
1016
+ }
1017
+
1159
1018
  // ================================================================
1160
- // Phase 3: SSE stream for workflow run progress
1161
- // GET /workflows/runs/:id/stream
1019
+ // Webhook ingestion no auth required (uses HMAC verification)
1020
+ // POST /webhooks/:slug
1162
1021
  // ================================================================
1163
- if (req.method === "GET" && pathname.match(/^\/workflows\/runs\/[a-f0-9-]+\/stream$/)) {
1164
- const runId = pathname.split("/")[3];
1165
- // Auth check
1166
- const authHeader = req.headers.authorization;
1167
- const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1168
- const isInternal = safeCompare(token, FLY_INTERNAL_SECRET) || safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
1169
- if (!isInternal && !token) {
1170
- jsonResponse(res, 401, { error: "Missing authorization" }, corsHeaders);
1171
- return;
1172
- }
1173
- if (!isInternal) {
1174
- const sb = getServiceClient();
1175
- const { data: { user: authUser }, error: authError } = await sb.auth.getUser(token);
1176
- if (authError || !authUser) {
1177
- jsonResponse(res, 401, { error: "Invalid or expired token" }, corsHeaders);
1178
- return;
1179
- }
1180
- // P1 FIX: Verify user belongs to the run's store (prevent cross-store SSE snooping)
1181
- const { data: sseRun } = await sb.from("workflow_runs")
1182
- .select("store_id").eq("id", runId).single();
1183
- if (sseRun) {
1184
- const { data: membership } = await sb.from("store_members")
1185
- .select("id").eq("store_id", sseRun.store_id).eq("user_id", authUser.id).single();
1186
- if (!membership) {
1187
- jsonResponse(res, 403, { error: "Not authorized to view this run" }, corsHeaders);
1188
- return;
1189
- }
1190
- }
1191
- }
1192
- // H6 FIX: Enforce per-run and total client limits
1193
- const existingClients = sseClients.get(runId)?.size || 0;
1194
- if (existingClients >= MAX_SSE_CLIENTS_PER_RUN) {
1195
- jsonResponse(res, 429, { error: "Too many SSE clients for this run" }, corsHeaders);
1196
- return;
1197
- }
1198
- if (getTotalSseClients() >= MAX_SSE_TOTAL_CLIENTS) {
1199
- jsonResponse(res, 429, { error: "Too many total SSE connections" }, corsHeaders);
1200
- return;
1201
- }
1202
- // Start SSE stream
1203
- res.writeHead(200, {
1204
- "Content-Type": "text/event-stream",
1205
- "Cache-Control": "no-cache",
1206
- Connection: "keep-alive",
1207
- ...corsHeaders,
1208
- });
1209
- // Send snapshot
1210
- const sb = getServiceClient();
1211
- const { data: run } = await sb.from("workflow_runs")
1212
- .select("id, workflow_id, status, trigger_type, current_step_key, error_message, error_step_key, started_at, completed_at, duration_ms")
1213
- .eq("id", runId).single();
1214
- const { data: stepRuns } = await sb.from("workflow_step_runs")
1215
- .select("id, step_key, step_type, status, error_message, duration_ms, started_at, completed_at")
1216
- .eq("run_id", runId).order("created_at", { ascending: true });
1217
- sendWorkflowSSE(res, { type: "snapshot", run, steps: stepRuns || [] });
1218
- // Register client
1219
- if (!sseClients.has(runId))
1220
- sseClients.set(runId, new Set());
1221
- sseClients.get(runId).add(res);
1222
- // Cleanup on disconnect
1223
- const cleanup = () => {
1224
- clearInterval(heartbeat);
1225
- const clients = sseClients.get(runId);
1226
- if (clients) {
1227
- clients.delete(res);
1228
- if (clients.size === 0)
1229
- sseClients.delete(runId);
1230
- }
1231
- };
1232
- // Heartbeat — uses safeSseWrite so dead connections are cleaned up immediately
1233
- const heartbeat = setInterval(() => {
1234
- if (!safeSseWrite(res, `: heartbeat\n\n`, cleanup)) {
1235
- clearInterval(heartbeat);
1236
- }
1237
- }, 15_000);
1238
- req.on("close", cleanup);
1239
- req.on("error", cleanup);
1022
+ const webhookMatch = pathname.match(/^\/webhooks\/([a-zA-Z0-9_-]+)$/);
1023
+ if (webhookMatch) {
1024
+ const whClientIp = req.headers["x-forwarded-for"]?.toString().split(",")[0]?.trim() || req.socket.remoteAddress || "unknown";
1025
+ if (sendIpRateLimit(res, whClientIp, corsHeaders)) return;
1026
+ const slug = webhookMatch[1];
1027
+ let rawBody;
1028
+ try {
1029
+ rawBody = await readBody(req);
1030
+ } catch {
1031
+ jsonResponse(res, 413, {
1032
+ error: "Request body too large"
1033
+ }, corsHeaders);
1240
1034
  return;
1035
+ }
1036
+ const supabase = getServiceClient();
1037
+ const headers = {};
1038
+ for (const [k, v] of Object.entries(req.headers)) {
1039
+ if (typeof v === "string") headers[k] = v;
1040
+ }
1041
+ const result = await handleWebhookIngestion(supabase, slug, rawBody, headers);
1042
+
1043
+ // Phase 1: Inline execution for webhook-triggered workflows
1044
+ if (result.body.run_id && result.status === 200) {
1045
+ try {
1046
+ await executeInlineChain(supabase, result.body.run_id);
1047
+ } catch (err) {
1048
+ log.error({
1049
+ err: err.message
1050
+ }, "webhook inline chain error");
1051
+ }
1052
+ }
1053
+ jsonResponse(res, result.status, result.body, corsHeaders);
1054
+ return;
1241
1055
  }
1056
+
1242
1057
  // ================================================================
1243
- // Guest approvalsigned URL, no auth required (GET)
1244
- // GET /approvals/guest/:id?action=approve&expires=...&sig=...
1058
+ // Fire eventservice-role or internal auth
1059
+ // POST /events
1245
1060
  // ================================================================
1246
- const guestApprovalMatch = pathname.match(/^\/approvals\/guest\/([a-f0-9-]+)$/);
1247
- if (guestApprovalMatch && req.method === "GET") {
1248
- const clientIp = req.headers["x-forwarded-for"]?.toString().split(",")[0]?.trim() || req.socket.remoteAddress || "unknown";
1249
- if (sendIpRateLimit(res, clientIp, corsHeaders))
1250
- return;
1251
- const stepRunId = guestApprovalMatch[1];
1252
- const urlParams = new URL(req.url || "", `http://${req.headers.host}`).searchParams;
1253
- const action = urlParams.get("action") || "";
1254
- const expires = urlParams.get("expires") || "";
1255
- const sig = urlParams.get("sig") || "";
1256
- if (!action || !expires || !sig) {
1257
- jsonResponse(res, 400, { error: "Missing action, expires, or sig parameter" }, corsHeaders);
1258
- return;
1259
- }
1260
- if (new Date(expires) < new Date()) {
1261
- jsonResponse(res, 410, { error: "This approval link has expired" }, corsHeaders);
1262
- return;
1263
- }
1264
- if (!verifyGuestApprovalSignature(stepRunId, action, expires, sig)) {
1265
- jsonResponse(res, 403, { error: "Invalid signature" }, corsHeaders);
1266
- return;
1267
- }
1268
- const guestSupabase = getServiceClient();
1269
- const { data: approval } = await guestSupabase.from("workflow_approval_requests")
1270
- .select("id, store_id, run_id, status").eq("step_run_id", stepRunId).limit(1);
1271
- if (!approval?.length) {
1272
- jsonResponse(res, 404, { error: "Approval not found" }, corsHeaders);
1273
- return;
1274
- }
1275
- if (approval[0].status !== "pending") {
1276
- jsonResponse(res, 409, { error: `Approval already ${approval[0].status}` }, corsHeaders);
1277
- return;
1278
- }
1279
- const isApprove = action === "approve" || action === "approved";
1280
- const { data: guestResult, error: guestErr } = await guestSupabase.rpc("respond_to_approval", {
1281
- p_approval_id: approval[0].id,
1282
- p_store_id: approval[0].store_id,
1283
- p_response: isApprove ? "approved" : "rejected",
1284
- p_response_data: { guest: true, action },
1285
- p_responded_by: null,
1286
- });
1287
- if (guestErr) {
1288
- jsonResponse(res, 500, { success: false, error: guestErr.message }, corsHeaders);
1289
- return;
1290
- }
1291
- if (guestResult?.success && approval[0].run_id) {
1292
- try {
1293
- await executeInlineChain(guestSupabase, approval[0].run_id);
1294
- }
1295
- catch (err) {
1296
- log.error({ err: err.message, runId: approval[0].run_id }, "inline chain failed after guest approval");
1297
- }
1298
- }
1299
- res.writeHead(200, { "Content-Type": "text/html", ...corsHeaders });
1300
- res.end(`<!DOCTYPE html><html><body style="font-family:system-ui;text-align:center;padding:40px">
1301
- <h2>${isApprove ? "Approved" : "Rejected"}</h2>
1302
- <p>Your response has been recorded. You can close this window.</p>
1303
- </body></html>`);
1061
+ if (pathname === "/events") {
1062
+ const authHeader = req.headers.authorization;
1063
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1064
+ const isInternal = safeCompare(token, FLY_INTERNAL_SECRET) || safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
1065
+ if (!isInternal && !token) {
1066
+ jsonResponse(res, 401, {
1067
+ error: "Missing authorization"
1068
+ }, corsHeaders);
1304
1069
  return;
1070
+ }
1071
+ const supabase = getServiceClient();
1072
+
1073
+ // Verify user auth if not internal
1074
+ if (!isInternal) {
1075
+ const {
1076
+ data: {
1077
+ user: authUser
1078
+ },
1079
+ error: authError
1080
+ } = await supabase.auth.getUser(token);
1081
+ if (authError || !authUser) {
1082
+ jsonResponse(res, 401, {
1083
+ error: "Invalid or expired token"
1084
+ }, corsHeaders);
1085
+ return;
1086
+ }
1087
+ }
1088
+ let rawBody;
1089
+ try {
1090
+ rawBody = await readBody(req);
1091
+ } catch {
1092
+ jsonResponse(res, 413, {
1093
+ error: "Request body too large"
1094
+ }, corsHeaders);
1095
+ return;
1096
+ }
1097
+ let body;
1098
+ try {
1099
+ body = JSON.parse(rawBody || "{}");
1100
+ } catch {
1101
+ jsonResponse(res, 400, {
1102
+ error: "Invalid JSON"
1103
+ }, corsHeaders);
1104
+ return;
1105
+ }
1106
+ if (!body.store_id || !body.event_type) {
1107
+ jsonResponse(res, 400, {
1108
+ error: "store_id and event_type required"
1109
+ }, corsHeaders);
1110
+ return;
1111
+ }
1112
+
1113
+ // Idempotency: if client provides idempotency_key, check for duplicate
1114
+ if (body.idempotency_key) {
1115
+ const {
1116
+ data: existing
1117
+ } = await supabase.from("workflow_events").select("id").eq("idempotency_key", body.idempotency_key).limit(1);
1118
+ if (existing && existing.length > 0) {
1119
+ jsonResponse(res, 200, {
1120
+ success: true,
1121
+ event_id: existing[0].id,
1122
+ deduplicated: true
1123
+ }, corsHeaders);
1124
+ return;
1125
+ }
1126
+ }
1127
+ const {
1128
+ data: eventId,
1129
+ error: fireErr
1130
+ } = await supabase.rpc("fire_event", {
1131
+ p_store_id: body.store_id,
1132
+ p_event_type: body.event_type,
1133
+ p_event_payload: body.payload || {},
1134
+ p_source: body.source || "api"
1135
+ });
1136
+ if (fireErr) {
1137
+ jsonResponse(res, 500, {
1138
+ success: false,
1139
+ error: fireErr.message
1140
+ }, corsHeaders);
1141
+ } else {
1142
+ jsonResponse(res, 200, {
1143
+ success: true,
1144
+ event_id: eventId
1145
+ }, corsHeaders);
1146
+ }
1147
+ return;
1305
1148
  }
1149
+
1306
1150
  // ================================================================
1307
- // Webchat anonymous access for embedded chat widgets
1308
- // POST /webchat/channels/:id/messages — send message (+ agent auto-reply)
1309
- // GET /webchat/channels/:id/history — load conversation history
1310
- // GET /webchat/widget.js — serve compiled widget JS
1151
+ // Internal workflow processing verified by internal secret
1152
+ // POST /workflows/process
1311
1153
  // ================================================================
1312
- // Webchat CORS: allow any origin (widget is embedded on customer sites)
1313
- const webchatCors = { ...corsHeaders, "Access-Control-Allow-Origin": "*" };
1314
- const webchatMsgMatch = pathname.match(/^\/webchat\/channels\/([a-f0-9-]+)\/messages$/);
1315
- if (webchatMsgMatch && req.method === "POST") {
1316
- const clientIp = req.headers["x-forwarded-for"]?.toString().split(",")[0]?.trim() || req.socket.remoteAddress || "unknown";
1317
- if (sendIpRateLimit(res, clientIp, webchatCors))
1318
- return;
1319
- let rawBody;
1320
- try {
1321
- rawBody = await readBody(req);
1322
- }
1323
- catch {
1324
- jsonResponse(res, 413, { error: "Request body too large" }, webchatCors);
1325
- return;
1326
- }
1327
- const channelId = webchatMsgMatch[1];
1328
- let wcBody;
1329
- try {
1330
- wcBody = JSON.parse(rawBody);
1331
- }
1332
- catch {
1333
- jsonResponse(res, 400, { error: "Invalid JSON" }, webchatCors);
1334
- return;
1335
- }
1336
- if (!wcBody.content || typeof wcBody.content !== "string") {
1337
- jsonResponse(res, 400, { error: "content (string) is required" }, webchatCors);
1338
- return;
1339
- }
1340
- if (wcBody.content.length > 5000) {
1341
- jsonResponse(res, 400, { error: "Message too long (max 5000 characters)" }, webchatCors);
1342
- return;
1343
- }
1344
- const supabase = getServiceClient();
1345
- // Verify channel exists and is a webchat channel
1346
- const { data: channel } = await supabase
1347
- .from("channels")
1348
- .select("id, store_id, node_id, agent_id, type, config")
1349
- .eq("id", channelId)
1350
- .eq("type", "webchat")
1351
- .single();
1352
- if (!channel) {
1353
- jsonResponse(res, 404, { error: "Webchat channel not found" }, webchatCors);
1354
- return;
1355
- }
1356
- // Rate limit per store (best-effort)
1357
- try {
1358
- const planCheck = await checkPlanLimits(supabase, channel.store_id, "message");
1359
- if (!planCheck.allowed) {
1360
- jsonResponse(res, 429, { error: planCheck.reason || "Plan limit reached" }, webchatCors);
1361
- return;
1362
- }
1363
- }
1364
- catch { /* billing tables may not exist yet */ }
1365
- const senderId = wcBody.sender_id || "anonymous";
1366
- // Resolve conversation: reuse if sender had activity < 30 min ago, else new UUID
1367
- let conversationId = wcBody.conversation_id || "";
1368
- if (!conversationId) {
1369
- const thirtyMinAgo = new Date(Date.now() - 30 * 60 * 1000).toISOString();
1370
- const { data: recent } = await supabase
1371
- .from("channel_messages")
1372
- .select("conversation_id")
1373
- .eq("channel_id", channelId)
1374
- .eq("sender_id", senderId)
1375
- .gt("created_at", thirtyMinAgo)
1376
- .not("conversation_id", "is", null)
1377
- .order("created_at", { ascending: false })
1378
- .limit(1);
1379
- conversationId = (recent?.length && recent[0].conversation_id) || randomUUID();
1380
- }
1381
- // Insert inbound message
1382
- const { data: message, error: msgErr } = await supabase
1383
- .from("channel_messages")
1384
- .insert({
1385
- store_id: channel.store_id,
1386
- channel_id: channelId,
1387
- direction: "inbound",
1388
- sender_id: senderId,
1389
- sender_name: wcBody.sender_name || "Visitor",
1390
- content: wcBody.content,
1391
- content_type: "text",
1392
- metadata: { source: "webchat", ip: clientIp, widget_version: "1.0.0" },
1393
- agent_id: channel.agent_id,
1394
- conversation_id: conversationId,
1395
- })
1396
- .select("id, direction, content, conversation_id, created_at")
1397
- .single();
1398
- if (msgErr) {
1399
- jsonResponse(res, 500, { error: msgErr.message }, webchatCors);
1400
- return;
1401
- }
1402
- // Track usage + channel stats (best-effort)
1403
- incrementUsage(supabase, channel.store_id, { messages_in: 1 }).catch(() => { });
1404
- try {
1405
- await supabase.rpc("increment_channel_stats", { p_channel_id: channelId });
1406
- }
1407
- catch { /* ok */ }
1408
- // Auto-invoke agent if assigned
1409
- let agentResponse = null;
1410
- if (channel.agent_id && webchatAgentInvoker) {
1411
- try {
1412
- const result = await webchatAgentInvoker(supabase, channel.agent_id, wcBody.content, channel.store_id, conversationId);
1413
- if (result.success && result.response) {
1414
- const { data: outMsg } = await supabase
1415
- .from("channel_messages")
1416
- .insert({
1417
- store_id: channel.store_id,
1418
- channel_id: channelId,
1419
- direction: "outbound",
1420
- sender_id: "agent",
1421
- sender_name: "AI Agent",
1422
- content: result.response,
1423
- content_type: "text",
1424
- metadata: { agent_id: channel.agent_id, auto_response: true, source: "webchat" },
1425
- agent_id: channel.agent_id,
1426
- conversation_id: conversationId,
1427
- })
1428
- .select("id, direction, content, conversation_id, created_at")
1429
- .single();
1430
- agentResponse = outMsg;
1431
- incrementUsage(supabase, channel.store_id, { messages_out: 1, agent_invocations: 1 }).catch(() => { });
1432
- }
1433
- }
1434
- catch (err) {
1435
- log.error({ err: err.message }, "webchat agent error");
1436
- }
1437
- }
1438
- jsonResponse(res, 201, { success: true, message, agent_response: agentResponse, conversation_id: conversationId }, webchatCors);
1439
- return;
1440
- }
1441
- // GET /webchat/channels/:id/history — load conversation history for widget
1442
- const webchatHistoryMatch = pathname.match(/^\/webchat\/channels\/([a-f0-9-]+)\/history$/);
1443
- if (webchatHistoryMatch && req.method === "GET") {
1444
- const clientIp = req.headers["x-forwarded-for"]?.toString().split(",")[0]?.trim() || req.socket.remoteAddress || "unknown";
1445
- if (sendIpRateLimit(res, clientIp, webchatCors))
1446
- return;
1447
- const channelId = webchatHistoryMatch[1];
1448
- const params = url.searchParams;
1449
- const senderId = params.get("sender_id") || "";
1450
- const reqConvId = params.get("conversation_id") || "";
1451
- if (!senderId) {
1452
- jsonResponse(res, 400, { error: "sender_id query parameter required" }, webchatCors);
1453
- return;
1454
- }
1455
- const supabase = getServiceClient();
1456
- // Verify channel is webchat type
1457
- const { data: wcChannel } = await supabase
1458
- .from("channels")
1459
- .select("id, type")
1460
- .eq("id", channelId)
1461
- .eq("type", "webchat")
1462
- .single();
1463
- if (!wcChannel) {
1464
- jsonResponse(res, 404, { error: "Webchat channel not found" }, webchatCors);
1465
- return;
1466
- }
1467
- // Get messages — by conversation_id if available, or by sender
1468
- // P0 FIX: Always filter by sender_id to prevent cross-sender data leak
1469
- let query = supabase
1470
- .from("channel_messages")
1471
- .select("id, direction, sender_id, sender_name, content, content_type, created_at")
1472
- .eq("channel_id", channelId);
1473
- // P0 FIX: Validate senderId against strict pattern to prevent PostgREST filter injection
1474
- // Only allow alphanumeric characters, hyphens, and underscores (blocks .eq., .neq., dots, etc.)
1475
- if (!/^[a-zA-Z0-9_-]+$/.test(senderId)) {
1476
- jsonResponse(res, 400, { error: "Invalid sender_id format" }, webchatCors);
1477
- return;
1478
- }
1479
- if (reqConvId) {
1480
- // P0 FIX: Use parameterized .in() filter instead of string interpolation in .or()
1481
- query = query.eq("conversation_id", reqConvId)
1482
- .in("sender_id", [senderId, "agent"]);
1483
- }
1484
- else {
1485
- query = query.in("sender_id", [senderId, "agent"]);
1486
- }
1487
- const { data: messages, error: histErr } = await query
1488
- .order("created_at", { ascending: true })
1489
- .limit(50);
1490
- if (histErr) {
1491
- jsonResponse(res, 500, { error: histErr.message }, webchatCors);
1492
- return;
1493
- }
1494
- jsonResponse(res, 200, { success: true, messages: messages || [] }, webchatCors);
1154
+ if (pathname === "/workflows/process") {
1155
+ const authHeader = req.headers.authorization;
1156
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1157
+ if (!FLY_INTERNAL_SECRET || !safeCompare(token, FLY_INTERNAL_SECRET) && !safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) && !safeCompare(token, SERVICE_ROLE_JWT)) {
1158
+ jsonResponse(res, 401, {
1159
+ error: "Unauthorized"
1160
+ }, corsHeaders);
1495
1161
  return;
1496
- }
1497
- // GET /webchat/widget.js — serve compiled widget JavaScript
1498
- if (pathname === "/webchat/widget.js" && req.method === "GET") {
1499
- const fs = await import("node:fs");
1500
- const path = await import("node:path");
1501
- // Try multiple possible locations for the built widget file
1502
- const possiblePaths = [
1503
- path.join(import.meta.dirname || __dirname, "../../dist/webchat/widget.js"),
1504
- path.join(import.meta.dirname || __dirname, "../../../dist/webchat/widget.js"),
1505
- path.join(process.cwd(), "dist/webchat/widget.js"),
1506
- ];
1507
- let widgetJs = null;
1508
- for (const p of possiblePaths) {
1509
- try {
1510
- widgetJs = fs.readFileSync(p, "utf-8");
1511
- break;
1512
- }
1513
- catch { /* try next */ }
1514
- }
1515
- if (widgetJs) {
1516
- res.writeHead(200, {
1517
- "Content-Type": "application/javascript",
1518
- "Cache-Control": "public, max-age=3600",
1519
- ...webchatCors,
1520
- });
1521
- res.end(widgetJs);
1522
- }
1523
- else {
1524
- res.writeHead(200, {
1525
- "Content-Type": "application/javascript",
1526
- ...webchatCors,
1527
- });
1528
- res.end('console.warn("[WhaleChat] Widget JS not built. Run: npx esbuild src/webchat/widget.ts --bundle --minify --outfile=dist/webchat/widget.js");');
1529
- }
1162
+ }
1163
+ let rawBody;
1164
+ try {
1165
+ rawBody = await readBody(req);
1166
+ } catch {
1167
+ rawBody = "{}";
1168
+ }
1169
+ let body;
1170
+ try {
1171
+ body = JSON.parse(rawBody || "{}");
1172
+ } catch {
1173
+ jsonResponse(res, 400, {
1174
+ error: "Invalid JSON"
1175
+ }, corsHeaders);
1530
1176
  return;
1531
- }
1177
+ }
1178
+ const supabase = getServiceClient();
1179
+ const result = await processWorkflowSteps(supabase, body.batch_size || 10);
1180
+ jsonResponse(res, 200, {
1181
+ success: true,
1182
+ ...result
1183
+ }, corsHeaders);
1184
+ return;
1185
+ }
1186
+
1532
1187
  // ================================================================
1533
- // Billing & Usage routes
1188
+ // Start workflow run — service-role or user auth
1189
+ // POST /workflows/start
1534
1190
  // ================================================================
1535
- if (pathname.startsWith("/usage") || pathname.startsWith("/billing/")) {
1536
- const authHeader = req.headers.authorization;
1537
- const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1538
- if (!token) {
1539
- req.resume();
1540
- jsonResponse(res, 401, { error: "Missing authorization" }, corsHeaders);
1541
- return;
1542
- }
1543
- let rawBody;
1544
- try {
1545
- rawBody = await readBody(req);
1546
- }
1547
- catch {
1548
- jsonResponse(res, 413, { error: "Request body too large" }, corsHeaders);
1549
- return;
1550
- }
1551
- let billingBody = null;
1552
- try {
1553
- if (rawBody)
1554
- billingBody = JSON.parse(rawBody);
1555
- }
1556
- catch {
1557
- jsonResponse(res, 400, { error: "Invalid JSON" }, corsHeaders);
1558
- return;
1559
- }
1560
- const supabase = getServiceClient();
1561
- // Extract userId from JWT for checkout/portal endpoints
1562
- let billingUserId;
1563
- try {
1564
- const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString());
1565
- billingUserId = payload.sub;
1566
- }
1567
- catch { /* not a JWT — service role key */ }
1568
- const result = await handleBillingRoutes(pathname, req.method || "GET", billingBody, supabase, {
1569
- userId: billingUserId,
1570
- isServiceRole: safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT),
1571
- });
1572
- if (result) {
1573
- jsonResponse(res, result.status, result.body, corsHeaders);
1574
- }
1575
- else {
1576
- jsonResponse(res, 404, { error: `No route: ${req.method} ${pathname}` }, corsHeaders);
1577
- }
1191
+ if (pathname === "/workflows/start") {
1192
+ const authHeader = req.headers.authorization;
1193
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1194
+ const isInternal = safeCompare(token, FLY_INTERNAL_SECRET) || safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
1195
+ if (!isInternal && !token) {
1196
+ jsonResponse(res, 401, {
1197
+ error: "Missing authorization"
1198
+ }, corsHeaders);
1578
1199
  return;
1579
- }
1200
+ }
1201
+ const supabase = getServiceClient();
1202
+
1203
+ // Verify user auth if not internal
1204
+ if (!isInternal) {
1205
+ const {
1206
+ data: {
1207
+ user: authUser
1208
+ },
1209
+ error: authError
1210
+ } = await supabase.auth.getUser(token);
1211
+ if (authError || !authUser) {
1212
+ jsonResponse(res, 401, {
1213
+ error: "Invalid or expired token"
1214
+ }, corsHeaders);
1215
+ return;
1216
+ }
1217
+ }
1218
+ let rawBody;
1219
+ try {
1220
+ rawBody = await readBody(req);
1221
+ } catch {
1222
+ jsonResponse(res, 413, {
1223
+ error: "Request body too large"
1224
+ }, corsHeaders);
1225
+ return;
1226
+ }
1227
+ let body;
1228
+ try {
1229
+ body = JSON.parse(rawBody);
1230
+ } catch {
1231
+ jsonResponse(res, 400, {
1232
+ error: "Invalid JSON"
1233
+ }, corsHeaders);
1234
+ return;
1235
+ }
1236
+
1237
+ // P0 FIX: Verify the workflow belongs to the requesting store before starting a run
1238
+ if (body.workflow_id && body.store_id) {
1239
+ const {
1240
+ data: wfOwner,
1241
+ error: wfOwnerErr
1242
+ } = await supabase.from("workflows").select("id").eq("id", body.workflow_id).eq("store_id", body.store_id).single();
1243
+ if (wfOwnerErr || !wfOwner) {
1244
+ jsonResponse(res, 403, {
1245
+ error: "Workflow does not belong to this store"
1246
+ }, corsHeaders);
1247
+ return;
1248
+ }
1249
+ }
1250
+ const {
1251
+ data,
1252
+ error
1253
+ } = await supabase.rpc("start_workflow_run", {
1254
+ p_workflow_id: body.workflow_id,
1255
+ p_store_id: body.store_id,
1256
+ p_trigger_type: body.trigger_type || "api",
1257
+ p_trigger_payload: body.trigger_payload || {},
1258
+ p_idempotency_key: body.idempotency_key || null
1259
+ });
1260
+ if (error) {
1261
+ jsonResponse(res, 500, {
1262
+ success: false,
1263
+ error: error.message
1264
+ }, corsHeaders);
1265
+ } else {
1266
+ // Phase 4: Set version_id if workflow has a published version
1267
+ // Phase 1: Inline execution for API-triggered workflows
1268
+ if (data?.success && data.run_id && !data.deduplicated) {
1269
+ try {
1270
+ const {
1271
+ data: wf
1272
+ } = await supabase.from("workflows").select("published_version_id").eq("id", body.workflow_id).single();
1273
+ if (wf?.published_version_id) {
1274
+ await supabase.from("workflow_runs").update({
1275
+ version_id: wf.published_version_id
1276
+ }).eq("id", data.run_id);
1277
+ }
1278
+ await executeInlineChain(supabase, data.run_id);
1279
+ } catch (err) {
1280
+ log.error({
1281
+ err: err.message
1282
+ }, "start-inline chain error");
1283
+ }
1284
+ }
1285
+ jsonResponse(res, data?.success ? 200 : 422, data, corsHeaders);
1286
+ }
1287
+ return;
1288
+ }
1289
+
1580
1290
  // ================================================================
1581
- // Node & Channel management routes (supports GET, POST, PUT, DELETE)
1582
- // Must be before the method gate since GET /channels/:id/messages is valid
1291
+ // Standard auth gate for all other POST routes
1583
1292
  // ================================================================
1584
- if (pathname.startsWith("/nodes") || pathname.startsWith("/channels")) {
1585
- const authHeader = req.headers.authorization;
1586
- const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1587
- if (!token) {
1588
- req.resume();
1589
- jsonResponse(res, 401, { error: "Missing authorization" }, corsHeaders);
1590
- return;
1591
- }
1592
- let rawBody;
1593
- try {
1594
- rawBody = await readBody(req);
1595
- }
1596
- catch {
1597
- jsonResponse(res, 413, { error: "Request body too large" }, corsHeaders);
1598
- return;
1599
- }
1600
- let nodeBody = null;
1601
- try {
1602
- if (rawBody)
1603
- nodeBody = JSON.parse(rawBody);
1604
- }
1605
- catch {
1606
- jsonResponse(res, 400, { error: "Invalid JSON" }, corsHeaders);
1607
- return;
1608
- }
1609
- const supabase = getServiceClient();
1610
- let userId;
1611
- const isServiceRole = safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
1612
- if (!isServiceRole) {
1613
- const { data: { user: authUser } } = await supabase.auth.getUser(token);
1614
- if (authUser)
1615
- userId = authUser.id;
1616
- }
1617
- const result = await handleNodeRoutes(pathname, req.method || "GET", nodeBody, supabase, {
1618
- userId,
1619
- isServiceRole,
1620
- rawToken: token,
1621
- }, url.searchParams);
1622
- if (result) {
1623
- jsonResponse(res, result.status, result.body, corsHeaders);
1624
- }
1625
- else {
1626
- jsonResponse(res, 404, { error: `No route: ${req.method} ${pathname}` }, corsHeaders);
1627
- }
1293
+ log.info({
1294
+ method: req.method,
1295
+ path: pathname,
1296
+ contentLength: req.headers["content-length"] || "?"
1297
+ }, "request");
1298
+ const authHeader = req.headers.authorization;
1299
+ if (!authHeader?.startsWith("Bearer ")) {
1300
+ req.resume(); // drain body to avoid Fly proxy stall
1301
+ jsonResponse(res, 401, {
1302
+ error: "Missing authorization"
1303
+ }, corsHeaders);
1304
+ return;
1305
+ }
1306
+
1307
+ // Read body FIRST — before async auth calls (getUser, rate_limit).
1308
+ // Node.js request streams are paused until consumed. If we do async network
1309
+ // calls before reading, the TCP receive buffer fills up and Fly's proxy
1310
+ // stalls with "error writing a body to connection" on large requests.
1311
+ let rawBody;
1312
+ try {
1313
+ rawBody = await readBody(req);
1314
+ } catch {
1315
+ jsonResponse(res, 413, {
1316
+ error: "Request body too large (max 500MB)"
1317
+ }, corsHeaders);
1318
+ return;
1319
+ }
1320
+ const token = authHeader.substring(7);
1321
+ const supabase = getServiceClient();
1322
+
1323
+ // Check service-role key
1324
+ let user = null;
1325
+ const isServiceRole = safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
1326
+ if (!isServiceRole) {
1327
+ try {
1328
+ const {
1329
+ data: {
1330
+ user: authUser
1331
+ },
1332
+ error: authError
1333
+ } = await supabase.auth.getUser(token);
1334
+ if (authError || !authUser) {
1335
+ jsonResponse(res, 401, {
1336
+ error: "Invalid or expired token"
1337
+ }, corsHeaders);
1338
+ return;
1339
+ }
1340
+ user = authUser;
1341
+ } catch (authErr) {
1342
+ log.error({
1343
+ err: authErr.message
1344
+ }, "auth getUser failed");
1345
+ jsonResponse(res, 502, {
1346
+ error: "Auth service unavailable, please retry"
1347
+ }, corsHeaders);
1628
1348
  return;
1349
+ }
1629
1350
  }
1630
- if (req.method !== "POST" && req.method !== "DELETE" && req.method !== "PUT") {
1631
- jsonResponse(res, 405, { error: "Method not allowed" }, corsHeaders);
1351
+
1352
+ // In-memory token-bucket rate limiting (sole rate check — no DB round-trip)
1353
+ {
1354
+ const tier = isServiceRole ? "serviceRole" : user ? "authenticated" : "unauthenticated";
1355
+ const rateLimitKey = user ? `user:${user.id}` : `ip:${req.socket.remoteAddress || "unknown"}`;
1356
+ const memResult = rateLimiter.checkRequest(rateLimitKey, tier);
1357
+ if (!memResult.allowed) {
1358
+ res.writeHead(429, {
1359
+ "Retry-After": String(Math.ceil(memResult.retryAfterMs / 1000)),
1360
+ "X-RateLimit-Remaining": "0",
1361
+ "X-RateLimit-Bucket": memResult.bucket,
1362
+ "Content-Type": "application/json",
1363
+ ...corsHeaders
1364
+ });
1365
+ res.end(JSON.stringify({
1366
+ error: "Rate limit exceeded"
1367
+ }));
1632
1368
  return;
1369
+ }
1633
1370
  }
1371
+ let body;
1634
1372
  try {
1635
- // ================================================================
1636
- // Phase 4.2: Resume conversation from checkpoint
1637
- // POST /conversations/:id/resume
1638
- // ================================================================
1639
- const resumeMatch = pathname.match(/^\/conversations\/([a-f0-9-]+)\/resume$/);
1640
- if (resumeMatch && req.method === "POST") {
1641
- const convId = resumeMatch[1];
1642
- const authHeader = req.headers.authorization;
1643
- const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1644
- const isInternal = safeCompare(token, FLY_INTERNAL_SECRET) || safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
1645
- if (!isInternal && !token) {
1646
- jsonResponse(res, 401, { error: "Missing authorization" }, corsHeaders);
1647
- return;
1648
- }
1649
- const supabase = getServiceClient();
1650
- if (!isInternal) {
1651
- const { data: { user: authUser }, error: authError } = await supabase.auth.getUser(token);
1652
- if (authError || !authUser) {
1653
- jsonResponse(res, 401, { error: "Invalid or expired token" }, corsHeaders);
1654
- return;
1655
- }
1656
- }
1657
- // Drain the request body (even if we don't use it) to prevent Fly proxy stalls
1658
- try {
1659
- await readBody(req);
1660
- }
1661
- catch { /* body not needed */ }
1662
- const checkpoint = await loadCheckpoint(supabase, convId);
1663
- if (!checkpoint) {
1664
- jsonResponse(res, 404, { error: "No checkpoint found for this conversation" }, corsHeaders);
1665
- return;
1666
- }
1667
- // Parse messages back from serialized form
1668
- let parsedMessages;
1669
- try {
1670
- parsedMessages = JSON.parse(checkpoint.messages);
1671
- }
1672
- catch {
1673
- jsonResponse(res, 422, { error: "Checkpoint messages corrupted" }, corsHeaders);
1674
- return;
1675
- }
1676
- jsonResponse(res, 200, {
1677
- success: true,
1678
- conversation_id: checkpoint.conversation_id,
1679
- turn: checkpoint.turn,
1680
- messages: parsedMessages,
1681
- tokens_used: checkpoint.tokens_used,
1682
- cost_so_far: checkpoint.cost_so_far,
1683
- tools_used: checkpoint.tools_used,
1684
- checkpointed_at: checkpoint.created_at,
1685
- }, corsHeaders);
1686
- return;
1687
- }
1688
- // ================================================================
1689
- // Phase 2: Approval response endpoint
1690
- // POST /approvals/:id/respond
1691
- // ================================================================
1692
- const approvalMatch = pathname.match(/^\/approvals\/([a-f0-9-]+)\/respond$/);
1693
- if (approvalMatch) {
1694
- const approvalId = approvalMatch[1];
1695
- const authHeader = req.headers.authorization;
1696
- const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1697
- const isInternal = safeCompare(token, FLY_INTERNAL_SECRET) || safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
1698
- if (!isInternal && !token) {
1699
- jsonResponse(res, 401, { error: "Missing authorization" }, corsHeaders);
1700
- return;
1701
- }
1702
- const supabase = getServiceClient();
1703
- let userId = null;
1704
- if (!isInternal) {
1705
- const { data: { user: authUser }, error: authError } = await supabase.auth.getUser(token);
1706
- if (authError || !authUser) {
1707
- jsonResponse(res, 401, { error: "Invalid or expired token" }, corsHeaders);
1708
- return;
1709
- }
1710
- userId = authUser.id;
1711
- }
1712
- let rawBody;
1713
- try {
1714
- rawBody = await readBody(req);
1715
- }
1716
- catch {
1717
- jsonResponse(res, 413, { error: "Request body too large" }, corsHeaders);
1718
- return;
1719
- }
1720
- let body;
1721
- try {
1722
- body = JSON.parse(rawBody);
1723
- }
1724
- catch {
1725
- jsonResponse(res, 400, { error: "Invalid JSON" }, corsHeaders);
1726
- return;
1727
- }
1728
- if (!body.status) {
1729
- jsonResponse(res, 400, { error: "status required (approved/rejected)" }, corsHeaders);
1730
- return;
1731
- }
1732
- // Get store_id from approval
1733
- const { data: approval } = await supabase.from("workflow_approval_requests")
1734
- .select("store_id, run_id").eq("id", approvalId).single();
1735
- if (!approval) {
1736
- jsonResponse(res, 404, { error: "Approval not found" }, corsHeaders);
1737
- return;
1738
- }
1739
- // P1 FIX: Verify user has access to the approval's store
1740
- if (!isInternal && userId) {
1741
- const { data: approvalMembership } = await supabase.from("store_members")
1742
- .select("id").eq("store_id", approval.store_id).eq("user_id", userId).single();
1743
- if (!approvalMembership) {
1744
- jsonResponse(res, 403, { error: "Not authorized to respond to this approval" }, corsHeaders);
1745
- return;
1746
- }
1747
- }
1748
- const { data: result, error } = await supabase.rpc("respond_to_approval", {
1749
- p_approval_id: approvalId,
1750
- p_store_id: approval.store_id,
1751
- p_response: body.status,
1752
- p_response_data: body.response_data || {},
1753
- p_responded_by: userId || body.responded_by || null,
1754
- });
1755
- if (error) {
1756
- jsonResponse(res, 500, { success: false, error: error.message }, corsHeaders);
1757
- return;
1758
- }
1759
- // Inline resume — execute next step immediately
1760
- if (result?.success && approval.run_id) {
1761
- try {
1762
- await executeInlineChain(supabase, approval.run_id);
1763
- }
1764
- catch (err) {
1765
- log.error({ err: err.message }, "inline chain failed after approval");
1766
- }
1767
- }
1768
- jsonResponse(res, result?.success ? 200 : 422, result, corsHeaders);
1769
- return;
1770
- }
1771
- // ================================================================
1772
- // Waitpoint completion API endpoint
1773
- // POST /waitpoints/:token/complete
1774
- // ================================================================
1775
- const waitpointMatch = pathname.match(/^\/waitpoints\/([a-f0-9-]+)\/complete$/);
1776
- if (waitpointMatch) {
1777
- const token = waitpointMatch[1];
1778
- const authHeader = req.headers.authorization;
1779
- const authToken = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1780
- const isInternal = safeCompare(authToken, FLY_INTERNAL_SECRET) || safeCompare(authToken, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(authToken, SERVICE_ROLE_JWT);
1781
- if (!isInternal && !authToken) {
1782
- jsonResponse(res, 401, { error: "Missing authorization" }, corsHeaders);
1783
- return;
1784
- }
1785
- const supabase = getServiceClient();
1786
- let rawBody;
1787
- try {
1788
- rawBody = await readBody(req);
1789
- }
1790
- catch {
1791
- jsonResponse(res, 413, { error: "Request body too large" }, corsHeaders);
1792
- return;
1793
- }
1794
- let body;
1795
- try {
1796
- body = JSON.parse(rawBody || "{}");
1797
- }
1798
- catch {
1799
- jsonResponse(res, 400, { error: "Invalid JSON" }, corsHeaders);
1800
- return;
1801
- }
1802
- // Find waitpoint
1803
- const { data: wp } = await supabase.from("waitpoint_tokens")
1804
- .select("id, run_id, step_run_id, store_id, expires_at, status")
1805
- .eq("token", token).single();
1806
- if (!wp) {
1807
- jsonResponse(res, 404, { error: "Waitpoint token not found" }, corsHeaders);
1808
- return;
1809
- }
1810
- if (wp.status === "completed") {
1811
- jsonResponse(res, 409, { error: "Waitpoint already completed" }, corsHeaders);
1812
- return;
1813
- }
1814
- if (new Date(wp.expires_at) < new Date()) {
1815
- jsonResponse(res, 410, { error: "Waitpoint expired" }, corsHeaders);
1816
- return;
1817
- }
1818
- // Complete it
1819
- await supabase.from("waitpoint_tokens").update({
1820
- status: "completed", completion_data: body.data || {}, completed_at: new Date().toISOString(),
1821
- }).eq("id", wp.id);
1822
- await supabase.from("workflow_step_runs").update({
1823
- status: "pending", input: { waitpoint_completed: true, waitpoint_data: body.data || {} },
1824
- }).eq("id", wp.step_run_id).eq("status", "waiting");
1825
- // Inline resume
1826
- try {
1827
- await executeInlineChain(supabase, wp.run_id);
1828
- }
1829
- catch (err) {
1830
- log.error({ err: err.message, runId: wp.run_id }, "inline chain failed after waitpoint");
1831
- }
1832
- jsonResponse(res, 200, { success: true, run_id: wp.run_id }, corsHeaders);
1833
- return;
1834
- }
1835
- // ================================================================
1836
- // Webhook ingestion — no auth required (uses HMAC verification)
1837
- // POST /webhooks/:slug
1838
- // ================================================================
1839
- const webhookMatch = pathname.match(/^\/webhooks\/([a-zA-Z0-9_-]+)$/);
1840
- if (webhookMatch) {
1841
- const whClientIp = req.headers["x-forwarded-for"]?.toString().split(",")[0]?.trim() || req.socket.remoteAddress || "unknown";
1842
- if (sendIpRateLimit(res, whClientIp, corsHeaders))
1843
- return;
1844
- const slug = webhookMatch[1];
1845
- let rawBody;
1846
- try {
1847
- rawBody = await readBody(req);
1848
- }
1849
- catch {
1850
- jsonResponse(res, 413, { error: "Request body too large" }, corsHeaders);
1851
- return;
1852
- }
1853
- const supabase = getServiceClient();
1854
- const headers = {};
1855
- for (const [k, v] of Object.entries(req.headers)) {
1856
- if (typeof v === "string")
1857
- headers[k] = v;
1858
- }
1859
- const result = await handleWebhookIngestion(supabase, slug, rawBody, headers);
1860
- // Phase 1: Inline execution for webhook-triggered workflows
1861
- if (result.body.run_id && result.status === 200) {
1862
- try {
1863
- await executeInlineChain(supabase, result.body.run_id);
1864
- }
1865
- catch (err) {
1866
- log.error({ err: err.message }, "webhook inline chain error");
1867
- }
1868
- }
1869
- jsonResponse(res, result.status, result.body, corsHeaders);
1870
- return;
1871
- }
1872
- // ================================================================
1873
- // Fire event — service-role or internal auth
1874
- // POST /events
1875
- // ================================================================
1876
- if (pathname === "/events") {
1877
- const authHeader = req.headers.authorization;
1878
- const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1879
- const isInternal = safeCompare(token, FLY_INTERNAL_SECRET) || safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
1880
- if (!isInternal && !token) {
1881
- jsonResponse(res, 401, { error: "Missing authorization" }, corsHeaders);
1882
- return;
1883
- }
1884
- const supabase = getServiceClient();
1885
- // Verify user auth if not internal
1886
- if (!isInternal) {
1887
- const { data: { user: authUser }, error: authError } = await supabase.auth.getUser(token);
1888
- if (authError || !authUser) {
1889
- jsonResponse(res, 401, { error: "Invalid or expired token" }, corsHeaders);
1890
- return;
1891
- }
1892
- }
1893
- let rawBody;
1894
- try {
1895
- rawBody = await readBody(req);
1896
- }
1897
- catch {
1898
- jsonResponse(res, 413, { error: "Request body too large" }, corsHeaders);
1899
- return;
1900
- }
1901
- let body;
1902
- try {
1903
- body = JSON.parse(rawBody || "{}");
1904
- }
1905
- catch {
1906
- jsonResponse(res, 400, { error: "Invalid JSON" }, corsHeaders);
1907
- return;
1908
- }
1909
- if (!body.store_id || !body.event_type) {
1910
- jsonResponse(res, 400, { error: "store_id and event_type required" }, corsHeaders);
1911
- return;
1912
- }
1913
- // Idempotency: if client provides idempotency_key, check for duplicate
1914
- if (body.idempotency_key) {
1915
- const { data: existing } = await supabase.from("workflow_events")
1916
- .select("id")
1917
- .eq("idempotency_key", body.idempotency_key)
1918
- .limit(1);
1919
- if (existing && existing.length > 0) {
1920
- jsonResponse(res, 200, { success: true, event_id: existing[0].id, deduplicated: true }, corsHeaders);
1921
- return;
1922
- }
1923
- }
1924
- const { data: eventId, error: fireErr } = await supabase.rpc("fire_event", {
1925
- p_store_id: body.store_id,
1926
- p_event_type: body.event_type,
1927
- p_event_payload: body.payload || {},
1928
- p_source: body.source || "api",
1929
- });
1930
- if (fireErr) {
1931
- jsonResponse(res, 500, { success: false, error: fireErr.message }, corsHeaders);
1932
- }
1933
- else {
1934
- jsonResponse(res, 200, { success: true, event_id: eventId }, corsHeaders);
1935
- }
1936
- return;
1937
- }
1938
- // ================================================================
1939
- // Internal workflow processing — verified by internal secret
1940
- // POST /workflows/process
1941
- // ================================================================
1942
- if (pathname === "/workflows/process") {
1943
- const authHeader = req.headers.authorization;
1944
- const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1945
- if (!FLY_INTERNAL_SECRET || (!safeCompare(token, FLY_INTERNAL_SECRET) && !safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) && !safeCompare(token, SERVICE_ROLE_JWT))) {
1946
- jsonResponse(res, 401, { error: "Unauthorized" }, corsHeaders);
1947
- return;
1948
- }
1949
- let rawBody;
1950
- try {
1951
- rawBody = await readBody(req);
1952
- }
1953
- catch {
1954
- rawBody = "{}";
1955
- }
1956
- let body;
1957
- try {
1958
- body = JSON.parse(rawBody || "{}");
1959
- }
1960
- catch {
1961
- jsonResponse(res, 400, { error: "Invalid JSON" }, corsHeaders);
1962
- return;
1963
- }
1964
- const supabase = getServiceClient();
1965
- const result = await processWorkflowSteps(supabase, body.batch_size || 10);
1966
- jsonResponse(res, 200, { success: true, ...result }, corsHeaders);
1967
- return;
1968
- }
1969
- // ================================================================
1970
- // Start workflow run — service-role or user auth
1971
- // POST /workflows/start
1972
- // ================================================================
1973
- if (pathname === "/workflows/start") {
1974
- const authHeader = req.headers.authorization;
1975
- const token = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : "";
1976
- const isInternal = safeCompare(token, FLY_INTERNAL_SECRET) || safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
1977
- if (!isInternal && !token) {
1978
- jsonResponse(res, 401, { error: "Missing authorization" }, corsHeaders);
1979
- return;
1980
- }
1981
- const supabase = getServiceClient();
1982
- // Verify user auth if not internal
1983
- if (!isInternal) {
1984
- const { data: { user: authUser }, error: authError } = await supabase.auth.getUser(token);
1985
- if (authError || !authUser) {
1986
- jsonResponse(res, 401, { error: "Invalid or expired token" }, corsHeaders);
1987
- return;
1988
- }
1989
- }
1990
- let rawBody;
1991
- try {
1992
- rawBody = await readBody(req);
1993
- }
1994
- catch {
1995
- jsonResponse(res, 413, { error: "Request body too large" }, corsHeaders);
1996
- return;
1997
- }
1998
- let body;
1999
- try {
2000
- body = JSON.parse(rawBody);
2001
- }
2002
- catch {
2003
- jsonResponse(res, 400, { error: "Invalid JSON" }, corsHeaders);
2004
- return;
2005
- }
2006
- // P0 FIX: Verify the workflow belongs to the requesting store before starting a run
2007
- if (body.workflow_id && body.store_id) {
2008
- const { data: wfOwner, error: wfOwnerErr } = await supabase
2009
- .from("workflows")
2010
- .select("id")
2011
- .eq("id", body.workflow_id)
2012
- .eq("store_id", body.store_id)
2013
- .single();
2014
- if (wfOwnerErr || !wfOwner) {
2015
- jsonResponse(res, 403, { error: "Workflow does not belong to this store" }, corsHeaders);
2016
- return;
2017
- }
2018
- }
2019
- const { data, error } = await supabase.rpc("start_workflow_run", {
2020
- p_workflow_id: body.workflow_id,
2021
- p_store_id: body.store_id,
2022
- p_trigger_type: body.trigger_type || "api",
2023
- p_trigger_payload: body.trigger_payload || {},
2024
- p_idempotency_key: body.idempotency_key || null,
2025
- });
2026
- if (error) {
2027
- jsonResponse(res, 500, { success: false, error: error.message }, corsHeaders);
2028
- }
2029
- else {
2030
- // Phase 4: Set version_id if workflow has a published version
2031
- // Phase 1: Inline execution for API-triggered workflows
2032
- if (data?.success && data.run_id && !data.deduplicated) {
2033
- try {
2034
- const { data: wf } = await supabase.from("workflows")
2035
- .select("published_version_id").eq("id", body.workflow_id).single();
2036
- if (wf?.published_version_id) {
2037
- await supabase.from("workflow_runs").update({ version_id: wf.published_version_id }).eq("id", data.run_id);
2038
- }
2039
- await executeInlineChain(supabase, data.run_id);
2040
- }
2041
- catch (err) {
2042
- log.error({ err: err.message }, "start-inline chain error");
2043
- }
2044
- }
2045
- jsonResponse(res, data?.success ? 200 : 422, data, corsHeaders);
2046
- }
2047
- return;
2048
- }
2049
- // ================================================================
2050
- // Standard auth gate for all other POST routes
2051
- // ================================================================
2052
- log.info({ method: req.method, path: pathname, contentLength: req.headers["content-length"] || "?" }, "request");
2053
- const authHeader = req.headers.authorization;
2054
- if (!authHeader?.startsWith("Bearer ")) {
2055
- req.resume(); // drain body to avoid Fly proxy stall
2056
- jsonResponse(res, 401, { error: "Missing authorization" }, corsHeaders);
2057
- return;
2058
- }
2059
- // Read body FIRST — before async auth calls (getUser, rate_limit).
2060
- // Node.js request streams are paused until consumed. If we do async network
2061
- // calls before reading, the TCP receive buffer fills up and Fly's proxy
2062
- // stalls with "error writing a body to connection" on large requests.
2063
- let rawBody;
1373
+ body = JSON.parse(rawBody);
1374
+ } catch (parseErr) {
1375
+ const errMsg = parseErr instanceof Error ? parseErr.message : String(parseErr);
1376
+ const bodyLen = rawBody.length;
1377
+ const tail = bodyLen > 200 ? rawBody.slice(bodyLen - 200) : rawBody;
1378
+ log.error({
1379
+ bodyLen,
1380
+ errMsg,
1381
+ tail
1382
+ }, "JSON parse failed on request body");
1383
+ jsonResponse(res, 400, {
1384
+ error: "Invalid JSON in request body",
1385
+ detail: errMsg,
1386
+ body_length: bodyLen
1387
+ }, corsHeaders);
1388
+ return;
1389
+ }
1390
+
1391
+ // Anthropic API proxy mode
1392
+ if (body.mode === "proxy") {
1393
+ await handleProxy(res, body, corsHeaders);
1394
+ return;
1395
+ }
1396
+
1397
+ // Audio transcription mode (OpenAI Whisper)
1398
+ if (body.mode === "transcribe") {
1399
+ const {
1400
+ audio_base64,
1401
+ media_type,
1402
+ store_id
1403
+ } = body;
1404
+ if (!audio_base64 || !media_type) {
1405
+ jsonResponse(res, 400, {
1406
+ error: "audio_base64 and media_type required"
1407
+ }, corsHeaders);
1408
+ return;
1409
+ }
1410
+ const sid = store_id || body.storeId;
1411
+ if (!sid) {
1412
+ jsonResponse(res, 400, {
1413
+ error: "store_id required for transcription (needed to resolve OpenAI API key)"
1414
+ }, corsHeaders);
1415
+ return;
1416
+ }
1417
+ const result = await handleTranscribe(supabase, sid, audio_base64, media_type);
1418
+ jsonResponse(res, result.success ? 200 : 422, result, corsHeaders);
1419
+ return;
1420
+ }
1421
+
1422
+ // Conversation compaction mode (Haiku summarization for non-Anthropic providers)
1423
+ if (body.mode === "compact") {
1424
+ const {
1425
+ messages: compactMessages,
1426
+ system_prompt
1427
+ } = body;
1428
+ if (!compactMessages || !Array.isArray(compactMessages) || !system_prompt) {
1429
+ jsonResponse(res, 400, {
1430
+ error: "messages (array) and system_prompt required"
1431
+ }, corsHeaders);
1432
+ return;
1433
+ }
1434
+ const anthropic = new Anthropic({
1435
+ apiKey: ANTHROPIC_API_KEY
1436
+ });
1437
+ // Sanitize user-provided system prompt before passing to compaction
1438
+ const sanitizedCompactPrompt = sanitizeAndLog(system_prompt, "compactionEndpoint");
1439
+ const compactionResult = await generateCompaction({
1440
+ anthropic,
1441
+ messages: compactMessages,
1442
+ systemPrompt: sanitizedCompactPrompt
1443
+ });
1444
+ jsonResponse(res, compactionResult.success ? 200 : 422, {
1445
+ success: compactionResult.success,
1446
+ compaction_content: compactionResult.content,
1447
+ error: compactionResult.error
1448
+ }, corsHeaders);
1449
+ return;
1450
+ }
1451
+
1452
+ // Direct tool execution mode
1453
+ if (body.mode === "tool") {
1454
+ const {
1455
+ tool_name,
1456
+ args,
1457
+ store_id,
1458
+ conversation_id,
1459
+ trace_id
1460
+ } = body;
1461
+ if (!tool_name) {
1462
+ jsonResponse(res, 400, {
1463
+ error: "tool_name required"
1464
+ }, corsHeaders);
1465
+ return;
1466
+ }
1467
+
1468
+ // Resolve and validate store_id (server-derived, not blindly trusted)
1469
+ const resolvedToolStoreId = await resolveAndValidateStoreId(supabase, store_id || body.storeId, user, isServiceRole, token, body.userId);
1470
+ if (!resolvedToolStoreId && !isServiceRole) {
1471
+ jsonResponse(res, 400, {
1472
+ error: "store_id required for tool execution"
1473
+ }, corsHeaders);
1474
+ return;
1475
+ }
1476
+ const validToolStoreId = resolvedToolStoreId || store_id;
1477
+
1478
+ // Phase 7.2: Per-tool rate limiting
1479
+ const toolUserId = user?.id || body.userId || "anon";
1480
+ const toolLimit = rateLimiter.checkToolLimit(toolUserId, tool_name);
1481
+ if (!toolLimit.allowed) {
1482
+ res.writeHead(429, {
1483
+ "Retry-After": String(Math.ceil(toolLimit.retryAfterMs / 1000)),
1484
+ "X-RateLimit-Remaining": "0",
1485
+ "X-RateLimit-Bucket": toolLimit.bucket,
1486
+ "Content-Type": "application/json",
1487
+ ...corsHeaders
1488
+ });
1489
+ res.end(JSON.stringify({
1490
+ error: `Tool rate limit exceeded for ${tool_name}`
1491
+ }));
1492
+ return;
1493
+ }
1494
+ // Load user tools if this is a user_tool__ prefixed call
1495
+ let utRows;
1496
+ if (tool_name.startsWith("user_tool__") && validToolStoreId) {
1497
+ const {
1498
+ rows
1499
+ } = await loadUserTools(supabase, validToolStoreId);
1500
+ utRows = rows;
1501
+ }
1502
+ const result = await executeTool(supabase, tool_name, args || {}, validToolStoreId || undefined, trace_id || undefined, user?.id || body.userId || null, user?.email || body.userEmail || null, body.source || "whale-code", conversation_id || undefined, utRows);
1503
+ // Always 200 for tool results success/failure is in the JSON body.
1504
+ // HTTP 500 causes MCP clients to throw before reading the error message.
1505
+ jsonResponse(res, 200, result, corsHeaders);
1506
+ return;
1507
+ }
1508
+
1509
+ // Streaming tool execution mode (NDJSON) — for tools with live progress (kali, etc.)
1510
+ if (body.mode === "tool_stream") {
1511
+ const {
1512
+ tool_name,
1513
+ args,
1514
+ store_id
1515
+ } = body;
1516
+ if (!tool_name) {
1517
+ jsonResponse(res, 400, {
1518
+ error: "tool_name required"
1519
+ }, corsHeaders);
1520
+ return;
1521
+ }
1522
+
1523
+ // Resolve and validate store_id
1524
+ const resolvedStreamStoreId = await resolveAndValidateStoreId(supabase, store_id || body.storeId, user, isServiceRole, token, body.userId);
1525
+ if (!resolvedStreamStoreId && !isServiceRole) {
1526
+ jsonResponse(res, 400, {
1527
+ error: "store_id required for tool execution"
1528
+ }, corsHeaders);
1529
+ return;
1530
+ }
1531
+ const validStreamStoreId = resolvedStreamStoreId || store_id;
1532
+
1533
+ // Phase 7.2: Per-tool rate limiting (stream mode)
1534
+ const streamToolUserId = user?.id || body.userId || "anon";
1535
+ const streamToolLimit = rateLimiter.checkToolLimit(streamToolUserId, tool_name);
1536
+ if (!streamToolLimit.allowed) {
1537
+ res.writeHead(429, {
1538
+ "Retry-After": String(Math.ceil(streamToolLimit.retryAfterMs / 1000)),
1539
+ "X-RateLimit-Remaining": "0",
1540
+ "X-RateLimit-Bucket": streamToolLimit.bucket,
1541
+ "Content-Type": "application/json",
1542
+ ...corsHeaders
1543
+ });
1544
+ res.end(JSON.stringify({
1545
+ error: `Tool rate limit exceeded for ${tool_name}`
1546
+ }));
1547
+ return;
1548
+ }
1549
+ res.writeHead(200, {
1550
+ "Content-Type": "application/x-ndjson",
1551
+ "Transfer-Encoding": "chunked",
1552
+ ...corsHeaders
1553
+ });
1554
+ const onToolProgress = (_name, progress) => {
2064
1555
  try {
2065
- rawBody = await readBody(req);
2066
- }
2067
- catch {
2068
- jsonResponse(res, 413, { error: "Request body too large (max 500MB)" }, corsHeaders);
2069
- return;
2070
- }
2071
- const token = authHeader.substring(7);
2072
- const supabase = getServiceClient();
2073
- // Check service-role key
2074
- let user = null;
2075
- const isServiceRole = safeCompare(token, SUPABASE_SERVICE_ROLE_KEY) || safeCompare(token, SERVICE_ROLE_JWT);
2076
- if (!isServiceRole) {
2077
- try {
2078
- const { data: { user: authUser }, error: authError, } = await supabase.auth.getUser(token);
2079
- if (authError || !authUser) {
2080
- jsonResponse(res, 401, { error: "Invalid or expired token" }, corsHeaders);
2081
- return;
2082
- }
2083
- user = authUser;
2084
- }
2085
- catch (authErr) {
2086
- log.error({ err: authErr.message }, "auth getUser failed");
2087
- jsonResponse(res, 502, { error: "Auth service unavailable, please retry" }, corsHeaders);
2088
- return;
2089
- }
2090
- }
2091
- // Phase 7.2: In-memory token-bucket rate limiting (fast, before Supabase RPC)
2092
- {
2093
- const tier = isServiceRole ? "serviceRole" : user ? "authenticated" : "unauthenticated";
2094
- const rateLimitKey = user ? `user:${user.id}` : `ip:${req.socket.remoteAddress || "unknown"}`;
2095
- const memResult = rateLimiter.checkRequest(rateLimitKey, tier);
2096
- if (!memResult.allowed) {
2097
- res.writeHead(429, {
2098
- "Retry-After": String(Math.ceil(memResult.retryAfterMs / 1000)),
2099
- "X-RateLimit-Remaining": "0",
2100
- "X-RateLimit-Bucket": memResult.bucket,
2101
- "Content-Type": "application/json",
2102
- ...corsHeaders,
2103
- });
2104
- res.end(JSON.stringify({ error: "Rate limit exceeded" }));
2105
- return;
2106
- }
2107
- }
2108
- // Persistent rate limiting via Supabase RPC (secondary check, skip for service-role) — 100 req/60s
2109
- if (user) {
2110
- const { data: rl } = await supabase.rpc("check_rate_limit", {
2111
- p_user_id: user.id,
2112
- p_window_seconds: 60,
2113
- p_max_requests: 100,
2114
- });
2115
- if (rl?.[0] && !rl[0].allowed) {
2116
- res.writeHead(429, {
2117
- "Retry-After": String(rl[0].retry_after_seconds),
2118
- "X-RateLimit-Remaining": "0",
2119
- "Content-Type": "application/json",
2120
- ...corsHeaders,
2121
- });
2122
- res.end(JSON.stringify({ error: "Rate limit exceeded" }));
2123
- return;
2124
- }
2125
- }
2126
- let body;
1556
+ // Structured status events from kali — relay as type "status" for CLI ToolProgress handling
1557
+ const p = progress;
1558
+ if (p && p.type === "status" && p.progress) {
1559
+ res.write(JSON.stringify({
1560
+ type: "status",
1561
+ progress: p.progress
1562
+ }) + "\n");
1563
+ } else {
1564
+ res.write(JSON.stringify({
1565
+ type: "progress",
1566
+ progress
1567
+ }) + "\n");
1568
+ }
1569
+ } catch {/* client disconnected */}
1570
+ };
1571
+ try {
1572
+ const result = await executeTool(supabase, tool_name, args || {}, validStreamStoreId || undefined, body.trace_id || undefined, user?.id || body.userId || null, user?.email || body.userEmail || null, body.source || "whale-code-stream", body.conversation_id || undefined, undefined, undefined, onToolProgress);
1573
+ res.write(JSON.stringify({
1574
+ type: "result",
1575
+ ...result
1576
+ }) + "\n");
1577
+ } catch (err) {
1578
+ res.write(JSON.stringify({
1579
+ type: "result",
1580
+ success: false,
1581
+ error: sanitizeError(err)
1582
+ }) + "\n");
1583
+ }
1584
+ res.end();
1585
+ return;
1586
+ }
1587
+
1588
+ // CLI telemetry ingest — batch of spans from Whale Code CLI
1589
+ if (body.mode === "telemetry_ingest") {
1590
+ const spans = body.spans;
1591
+ if (!Array.isArray(spans) || spans.length === 0) {
1592
+ jsonResponse(res, 400, {
1593
+ error: "spans array required"
1594
+ }, corsHeaders);
1595
+ return;
1596
+ }
1597
+ // Cap at 500 spans per request to prevent abuse
1598
+ const batch = spans.slice(0, 500);
1599
+ let queued = 0;
1600
+ for (const raw of batch) {
2127
1601
  try {
2128
- body = JSON.parse(rawBody);
2129
- }
2130
- catch (parseErr) {
2131
- const errMsg = parseErr instanceof Error ? parseErr.message : String(parseErr);
2132
- const bodyLen = rawBody.length;
2133
- const tail = bodyLen > 200 ? rawBody.slice(bodyLen - 200) : rawBody;
2134
- log.error({ bodyLen, errMsg, tail }, "JSON parse failed on request body");
2135
- jsonResponse(res, 400, {
2136
- error: "Invalid JSON in request body",
2137
- detail: errMsg,
2138
- body_length: bodyLen,
2139
- }, corsHeaders);
2140
- return;
2141
- }
2142
- // Anthropic API proxy mode
2143
- if (body.mode === "proxy") {
2144
- await handleProxy(res, body, corsHeaders);
2145
- return;
2146
- }
2147
- // Audio transcription mode (OpenAI Whisper)
2148
- if (body.mode === "transcribe") {
2149
- const { audio_base64, media_type, store_id } = body;
2150
- if (!audio_base64 || !media_type) {
2151
- jsonResponse(res, 400, { error: "audio_base64 and media_type required" }, corsHeaders);
2152
- return;
2153
- }
2154
- const sid = store_id || body.storeId;
2155
- if (!sid) {
2156
- jsonResponse(res, 400, { error: "store_id required for transcription (needed to resolve OpenAI API key)" }, corsHeaders);
2157
- return;
2158
- }
2159
- const result = await handleTranscribe(supabase, sid, audio_base64, media_type);
2160
- jsonResponse(res, result.success ? 200 : 422, result, corsHeaders);
2161
- return;
2162
- }
2163
- // Conversation compaction mode (Haiku summarization for non-Anthropic providers)
2164
- if (body.mode === "compact") {
2165
- const { messages: compactMessages, system_prompt } = body;
2166
- if (!compactMessages || !Array.isArray(compactMessages) || !system_prompt) {
2167
- jsonResponse(res, 400, { error: "messages (array) and system_prompt required" }, corsHeaders);
2168
- return;
2169
- }
2170
- const anthropic = new Anthropic({ apiKey: ANTHROPIC_API_KEY });
2171
- // Sanitize user-provided system prompt before passing to compaction
2172
- const sanitizedCompactPrompt = sanitizeAndLog(system_prompt, "compactionEndpoint");
2173
- const compactionResult = await generateCompaction({
2174
- anthropic,
2175
- messages: compactMessages,
2176
- systemPrompt: sanitizedCompactPrompt,
2177
- });
2178
- jsonResponse(res, compactionResult.success ? 200 : 422, {
2179
- success: compactionResult.success,
2180
- compaction_content: compactionResult.content,
2181
- error: compactionResult.error,
2182
- }, corsHeaders);
2183
- return;
2184
- }
2185
- // Direct tool execution mode
2186
- if (body.mode === "tool") {
2187
- const { tool_name, args, store_id, conversation_id, trace_id } = body;
2188
- if (!tool_name) {
2189
- jsonResponse(res, 400, { error: "tool_name required" }, corsHeaders);
2190
- return;
2191
- }
2192
- // Resolve and validate store_id (server-derived, not blindly trusted)
2193
- const resolvedToolStoreId = await resolveAndValidateStoreId(supabase, store_id || body.storeId, user, isServiceRole, token, body.userId);
2194
- if (!resolvedToolStoreId && !isServiceRole) {
2195
- jsonResponse(res, 400, { error: "store_id required for tool execution" }, corsHeaders);
2196
- return;
2197
- }
2198
- const validToolStoreId = resolvedToolStoreId || store_id;
2199
- // Phase 7.2: Per-tool rate limiting
2200
- const toolUserId = user?.id || body.userId || "anon";
2201
- const toolLimit = rateLimiter.checkToolLimit(toolUserId, tool_name);
2202
- if (!toolLimit.allowed) {
2203
- res.writeHead(429, {
2204
- "Retry-After": String(Math.ceil(toolLimit.retryAfterMs / 1000)),
2205
- "X-RateLimit-Remaining": "0",
2206
- "X-RateLimit-Bucket": toolLimit.bucket,
2207
- "Content-Type": "application/json",
2208
- ...corsHeaders,
2209
- });
2210
- res.end(JSON.stringify({ error: `Tool rate limit exceeded for ${tool_name}` }));
2211
- return;
2212
- }
2213
- // Load user tools if this is a user_tool__ prefixed call
2214
- let utRows;
2215
- if (tool_name.startsWith("user_tool__") && validToolStoreId) {
2216
- const { rows } = await loadUserTools(supabase, validToolStoreId);
2217
- utRows = rows;
2218
- }
2219
- const result = await executeTool(supabase, tool_name, (args || {}), validToolStoreId || undefined, trace_id || undefined, user?.id || body.userId || null, user?.email || body.userEmail || null, body.source || "whale-code", conversation_id || undefined, utRows);
2220
- // Always 200 for tool results — success/failure is in the JSON body.
2221
- // HTTP 500 causes MCP clients to throw before reading the error message.
2222
- jsonResponse(res, 200, result, corsHeaders);
2223
- return;
2224
- }
2225
- // Streaming tool execution mode (NDJSON) — for tools with live progress (kali, etc.)
2226
- if (body.mode === "tool_stream") {
2227
- const { tool_name, args, store_id } = body;
2228
- if (!tool_name) {
2229
- jsonResponse(res, 400, { error: "tool_name required" }, corsHeaders);
2230
- return;
2231
- }
2232
- // Resolve and validate store_id
2233
- const resolvedStreamStoreId = await resolveAndValidateStoreId(supabase, store_id || body.storeId, user, isServiceRole, token, body.userId);
2234
- if (!resolvedStreamStoreId && !isServiceRole) {
2235
- jsonResponse(res, 400, { error: "store_id required for tool execution" }, corsHeaders);
2236
- return;
2237
- }
2238
- const validStreamStoreId = resolvedStreamStoreId || store_id;
2239
- // Phase 7.2: Per-tool rate limiting (stream mode)
2240
- const streamToolUserId = user?.id || body.userId || "anon";
2241
- const streamToolLimit = rateLimiter.checkToolLimit(streamToolUserId, tool_name);
2242
- if (!streamToolLimit.allowed) {
2243
- res.writeHead(429, {
2244
- "Retry-After": String(Math.ceil(streamToolLimit.retryAfterMs / 1000)),
2245
- "X-RateLimit-Remaining": "0",
2246
- "X-RateLimit-Bucket": streamToolLimit.bucket,
2247
- "Content-Type": "application/json",
2248
- ...corsHeaders,
2249
- });
2250
- res.end(JSON.stringify({ error: `Tool rate limit exceeded for ${tool_name}` }));
2251
- return;
2252
- }
2253
- res.writeHead(200, {
2254
- "Content-Type": "application/x-ndjson",
2255
- "Transfer-Encoding": "chunked",
2256
- ...corsHeaders,
1602
+ // Enforce store_id from auth context, not client-provided
1603
+ const resolvedStoreId = await resolveAndValidateStoreId(supabase, raw.store_id || body.store_id, user, isServiceRole, token, body.userId);
1604
+ raw.store_id = resolvedStoreId || raw.store_id || null;
1605
+ raw.user_id = raw.user_id || user?.id || body.userId || null;
1606
+ raw.user_email = raw.user_email || user?.email || body.userEmail || null;
1607
+ queueSpan(auditRowToSpan(raw));
1608
+ queued++;
1609
+ } catch {
1610
+ // Skip malformed spans
1611
+ }
1612
+ }
1613
+
1614
+ // Also upsert ai_conversations row if conversation_id provided
1615
+ if (body.conversation_id && body.store_id) {
1616
+ try {
1617
+ const convStoreId = await resolveAndValidateStoreId(supabase, body.store_id, user, isServiceRole, token, body.userId);
1618
+ if (convStoreId) {
1619
+ await supabase.from("ai_conversations").upsert({
1620
+ id: body.conversation_id,
1621
+ store_id: convStoreId,
1622
+ user_id: user?.id || body.userId || null,
1623
+ title: body.conversation_title || "CLI Session",
1624
+ metadata: {
1625
+ source: body.source || "whale_cli",
1626
+ hostname: body.hostname,
1627
+ version: body.version
1628
+ }
1629
+ }, {
1630
+ onConflict: "id"
2257
1631
  });
2258
- const onToolProgress = (_name, progress) => {
2259
- try {
2260
- // Structured status events from kali relay as type "status" for CLI ToolProgress handling
2261
- const p = progress;
2262
- if (p && p.type === "status" && p.progress) {
2263
- res.write(JSON.stringify({ type: "status", progress: p.progress }) + "\n");
2264
- }
2265
- else {
2266
- res.write(JSON.stringify({ type: "progress", progress }) + "\n");
2267
- }
2268
- }
2269
- catch { /* client disconnected */ }
2270
- };
2271
- try {
2272
- const result = await executeTool(supabase, tool_name, (args || {}), validStreamStoreId || undefined, body.trace_id || undefined, user?.id || body.userId || null, user?.email || body.userEmail || null, body.source || "whale-code-stream", body.conversation_id || undefined, undefined, undefined, onToolProgress);
2273
- res.write(JSON.stringify({ type: "result", ...result }) + "\n");
2274
- }
2275
- catch (err) {
2276
- res.write(JSON.stringify({ type: "result", success: false, error: sanitizeError(err) }) + "\n");
2277
- }
2278
- res.end();
2279
- return;
2280
- }
2281
- // CLI telemetry ingest — batch of spans from Whale Code CLI
2282
- if (body.mode === "telemetry_ingest") {
2283
- const spans = body.spans;
2284
- if (!Array.isArray(spans) || spans.length === 0) {
2285
- jsonResponse(res, 400, { error: "spans array required" }, corsHeaders);
2286
- return;
2287
- }
2288
- // Cap at 500 spans per request to prevent abuse
2289
- const batch = spans.slice(0, 500);
2290
- let queued = 0;
2291
- for (const raw of batch) {
2292
- try {
2293
- // Enforce store_id from auth context, not client-provided
2294
- const resolvedStoreId = await resolveAndValidateStoreId(supabase, raw.store_id || body.store_id, user, isServiceRole, token, body.userId);
2295
- raw.store_id = resolvedStoreId || raw.store_id || null;
2296
- raw.user_id = raw.user_id || user?.id || body.userId || null;
2297
- raw.user_email = raw.user_email || user?.email || body.userEmail || null;
2298
- queueSpan(auditRowToSpan(raw));
2299
- queued++;
2300
- }
2301
- catch {
2302
- // Skip malformed spans
2303
- }
2304
- }
2305
- // Also upsert ai_conversations row if conversation_id provided
2306
- if (body.conversation_id && body.store_id) {
2307
- try {
2308
- const convStoreId = await resolveAndValidateStoreId(supabase, body.store_id, user, isServiceRole, token, body.userId);
2309
- if (convStoreId) {
2310
- await supabase.from("ai_conversations").upsert({
2311
- id: body.conversation_id,
2312
- store_id: convStoreId,
2313
- user_id: user?.id || body.userId || null,
2314
- title: body.conversation_title || "CLI Session",
2315
- metadata: {
2316
- source: body.source || "whale_cli",
2317
- hostname: body.hostname,
2318
- version: body.version,
2319
- },
2320
- }, { onConflict: "id" });
2321
- }
2322
- }
2323
- catch {
2324
- // Non-critical — spans still ingested
2325
- }
2326
- }
2327
- jsonResponse(res, 200, { success: true, queued }, corsHeaders);
2328
- return;
2329
- }
2330
- // Agent chat mode (SSE)
2331
- await handleAgentChat(req, res, supabase, body, user, isServiceRole, token, corsHeaders);
2332
- }
2333
- catch (err) {
2334
- if (!res.headersSent) {
2335
- jsonResponse(res, 500, { error: sanitizeError(err) }, corsHeaders);
2336
- }
2337
- }
2338
- });
2339
- // Inject tool executor into workflow engine (avoids circular dependency)
2340
- setToolExecutor((supabase, toolName, args, storeId, traceId) => {
2341
- // Store boundary validation: prevent workflows from accessing other stores
2342
- if (args.store_id && args.store_id !== storeId) {
2343
- return Promise.resolve({ success: false, error: "Store boundary violation: workflow cannot access other stores" });
2344
- }
2345
- args.store_id = storeId; // Force the workflow's store
2346
- return executeTool(supabase, toolName, args, storeId, traceId, null, null, "workflow_engine");
1632
+ }
1633
+ } catch {
1634
+ // Non-criticalspans still ingested
1635
+ }
1636
+ }
1637
+ jsonResponse(res, 200, {
1638
+ success: true,
1639
+ queued
1640
+ }, corsHeaders);
1641
+ return;
1642
+ }
1643
+
1644
+ // Agent chat mode (SSE)
1645
+ await handleAgentChat(req, res, supabase, body, user, isServiceRole, token, corsHeaders);
1646
+ } catch (err) {
1647
+ if (!res.headersSent) {
1648
+ jsonResponse(res, 500, {
1649
+ error: sanitizeError(err)
1650
+ }, corsHeaders);
1651
+ }
1652
+ }
2347
1653
  });
2348
- // Inject agent executor for "agent" step type in workflows
2349
- setAgentExecutor(async (supabase, agentId, prompt, storeId, maxTurns = 5, onToken, traceId) => {
2350
- const agent = await loadAgentConfig(supabase, agentId, storeId);
2351
- if (!agent)
2352
- return { success: false, error: `Agent ${agentId} not found` };
2353
- // Workflow agents get all tools (no lazy loading — they run headless)
2354
- const { all: allTools } = await loadTools(supabase);
2355
- const { rows: userToolRows, defs: userToolDefs } = await loadUserTools(supabase, storeId);
2356
- const tools = getToolsForAgent(agent, allTools, userToolDefs);
2357
- const agentModel = agent.model || MODELS.SONNET;
2358
- // Resolve all behavioral config from DB — workflow maxTurns caps agent config
2359
- const resolved = resolveAgentLoopConfig(agent, "workflow", maxTurns);
2360
- if (resolved.defaultsUsed.length > 0) {
2361
- log.info({ agentId, callPath: "workflow", defaults: resolved.defaultsUsed }, "workflow agent config defaults applied");
2362
- }
2363
- // Build full system prompt — shared helper ensures workflow gets same context as SSE/channel
2364
- // (previously hand-built, missing location/customer context)
2365
- const { systemPrompt } = await buildAgentSystemPrompt(supabase, agent, storeId, prompt, tools);
2366
- try {
2367
- const result = await runServerAgentLoop({
2368
- anthropic: getAnthropicClient(agent),
2369
- supabase,
2370
- model: agentModel,
2371
- systemPrompt,
2372
- messages: [{ role: "user", content: prompt }],
2373
- tools,
2374
- // All behavioral knobs from resolved config — DB is single source of truth
2375
- maxTurns: resolved.maxTurns,
2376
- temperature: resolved.temperature,
2377
- maxTokens: resolved.maxTokens,
2378
- maxConcurrentTools: resolved.maxConcurrentTools,
2379
- enableDelegation: resolved.enableDelegation,
2380
- enableModelRouting: resolved.enableModelRouting,
2381
- contextOverrides: resolved.contextOverrides,
2382
- subagentMaxTokens: resolved.subagentMaxTokens,
2383
- subagentMaxTurns: resolved.subagentMaxTurns,
2384
- subagentTemperature: resolved.subagentTemperature,
2385
- storeId,
2386
- source: "workflow_agent",
2387
- agentId,
2388
- traceId,
2389
- executeTool: async (toolName, args) => {
2390
- const toolArgs = { ...args };
2391
- if (!toolArgs.store_id)
2392
- toolArgs.store_id = storeId;
2393
- return executeTool(supabase, toolName, toolArgs, storeId, traceId, null, null, "workflow_agent", undefined, userToolRows, agentId, undefined, true);
2394
- },
2395
- enableStreaming: !!onToken,
2396
- onText: onToken || undefined,
2397
- maxDurationMs: resolved.maxDurationMs,
2398
- });
2399
- return { success: true, response: result.finalText || "(no response)" };
2400
- }
2401
- catch (err) {
2402
- return { success: false, error: sanitizeError(err) };
2403
- }
2404
- });
2405
- // Inject token broadcaster for real-time agent step streaming
2406
- setTokenBroadcaster((runId, stepKey, token) => {
2407
- broadcastToRun(runId, { type: "agent_token", run_id: runId, step_key: stepKey, token });
2408
- });
2409
- // Inject step error broadcaster for real-time workflow error surfacing
2410
- setStepErrorBroadcaster((runId, data) => {
2411
- broadcastToRun(runId, data);
2412
- });
2413
- // Shared agent invoker — used by channel messages, webchat, and any source
2414
- // Uses shared helpers so it's IDENTICAL to CLI chat — same prompt, tools, memory, persistence, audit
2415
- async function invokeAgentForChannel(supabase, agentId, message, storeId, conversationId, senderContext) {
2416
- const agent = await loadAgentConfig(supabase, agentId, storeId);
2417
- if (!agent)
2418
- return { success: false, error: `Agent ${agentId} not found` };
2419
- const { core: coreTools, extended: extendedTools } = await loadTools(supabase);
2420
- setExtendedToolsCache(extendedTools);
2421
- const { rows: userToolRows, defs: userToolDefs } = await loadUserTools(supabase, storeId);
2422
- const tools = getToolsForAgent(agent, coreTools, userToolDefs);
2423
- const agentModel = agent.model || MODELS.SONNET;
2424
- // Resolve all behavioral config from DB — channel has 15-turn safety cap built in
2425
- const resolved = resolveAgentLoopConfig(agent, "channel");
2426
- if (resolved.defaultsUsed.length > 0) {
2427
- log.info({ agentId, callPath: "channel", defaults: resolved.defaultsUsed }, "channel agent config defaults applied");
2428
- }
2429
- // Build system prompt — shared helper, identical to SSE chat
2430
- const { systemPrompt, dynamicContext } = await buildAgentSystemPrompt(supabase, agent, storeId, message, tools, { senderContext, extendedTools: getExtendedToolsIndex() });
2431
- // Update ai_conversations with agent_id (fire-and-forget)
2432
- if (conversationId) {
2433
- Promise.resolve(supabase.from("ai_conversations").update({ agent_id: agentId }).eq("id", conversationId).is("agent_id", null)).catch(() => { });
2434
- }
2435
- // Load conversation history with size-based compaction (same as SSE chat)
2436
- const MAX_HISTORY_CHARS = resolved.maxHistoryChars;
2437
- let loadedHistory = [];
2438
- if (conversationId) {
2439
- try {
2440
- const { data: history } = await supabase
2441
- .from("ai_messages")
2442
- .select("role, content")
2443
- .eq("conversation_id", conversationId)
2444
- .order("created_at", { ascending: true })
2445
- .limit(50);
2446
- if (history?.length) {
2447
- const raw = [];
2448
- for (const m of history) {
2449
- if (m.role === "user" || m.role === "assistant") {
2450
- raw.push({ role: m.role, content: m.content });
2451
- }
2452
- }
2453
- loadedHistory = compactHistory(raw, MAX_HISTORY_CHARS);
2454
- }
2455
- }
2456
- catch { /* history not critical */ }
2457
- }
2458
- // Prepend dynamic context to user message to keep system prompt static (cache-friendly)
2459
- const contextPrefix = dynamicContext ? `[Context]\n${dynamicContext}\n\n[User Message]\n` : "";
2460
- const finalMessage = contextPrefix + message;
2461
- const messages = [...loadedHistory, { role: "user", content: finalMessage }];
2462
- const traceId = randomUUID();
2463
- const chatStartTime = Date.now();
2464
- try {
2465
- const result = await runServerAgentLoop({
2466
- anthropic: getAnthropicClient(agent),
2467
- supabase,
2468
- model: agentModel,
2469
- systemPrompt,
2470
- messages,
2471
- tools,
2472
- extendedTools,
2473
- // All behavioral knobs from resolved config — DB is single source of truth
2474
- maxTurns: resolved.maxTurns,
2475
- temperature: resolved.temperature,
2476
- maxTokens: resolved.maxTokens,
2477
- maxConcurrentTools: resolved.maxConcurrentTools,
2478
- enableDelegation: resolved.enableDelegation,
2479
- enableModelRouting: resolved.enableModelRouting,
2480
- contextOverrides: resolved.contextOverrides,
2481
- subagentMaxTokens: resolved.subagentMaxTokens,
2482
- subagentMaxTurns: resolved.subagentMaxTurns,
2483
- subagentTemperature: resolved.subagentTemperature,
2484
- storeId,
2485
- source: "channel_agent",
2486
- agentId,
2487
- traceId,
2488
- conversationId,
2489
- executeTool: async (toolName, args) => {
2490
- const toolArgs = { ...args };
2491
- if (!toolArgs.store_id)
2492
- toolArgs.store_id = storeId;
2493
- // Pass sender context as user identity so node-triggered tool calls aren't anonymous
2494
- const senderUserId = senderContext?.customerId || null;
2495
- const senderUserLabel = senderContext?.customerName || senderContext?.senderName
2496
- || (senderContext?.senderId ? `${senderContext.channelType}:${senderContext.senderId}` : null);
2497
- return executeTool(supabase, toolName, toolArgs, storeId, traceId, senderUserId, senderUserLabel, "channel_agent", conversationId, userToolRows, agentId, undefined, true);
2498
- },
2499
- enableStreaming: false,
2500
- maxDurationMs: resolved.maxDurationMs,
2501
- });
2502
- // Persist everything — shared helper, identical to SSE chat
2503
- await persistAgentTurn(supabase, agent, {
2504
- conversationId, storeId, agentId, agentModel, traceId, message, result,
2505
- source: "channel_agent",
2506
- chatStartTime, chatEndTime: Date.now(),
2507
- userId: senderContext?.customerId || undefined,
2508
- userEmail: senderContext?.customerName || senderContext?.senderName
2509
- || (senderContext?.senderId ? `${senderContext.channelType}:${senderContext.senderId}` : undefined),
2510
- senderContext,
2511
- });
2512
- return { success: true, response: result.finalText || "(no response)" };
2513
- }
2514
- catch (err) {
2515
- return { success: false, error: err.message };
2516
- }
2517
- }
1654
+
1655
+ // Wire executors into workflow engine (extracted modules no inline duplicates)
1656
+ wireToolExecutor();
1657
+ wireAgentExecutor();
1658
+ wireBroadcasters();
1659
+
2518
1660
  // Wire both channel and webchat to the same invoker
2519
1661
  setNodeAgentInvoker(invokeAgentForChannel);
2520
1662
  webchatAgentInvoker = invokeAgentForChannel;
2521
- // ============================================================================
2522
- // NODE OFFLINE DETECTION
2523
- // ============================================================================
2524
- async function enforceNodeOfflineStatus(supabase) {
2525
- const threshold = new Date(Date.now() - 3 * 60_000).toISOString(); // 3 missed heartbeats (60s interval)
2526
- const { data } = await supabase
2527
- .from("nodes")
2528
- .update({ status: "offline" })
2529
- .eq("status", "online")
2530
- .lt("last_heartbeat", threshold)
2531
- .select("id");
2532
- return data?.length || 0;
2533
- }
2534
- // ============================================================================
2535
- // PERSISTENT WORKFLOW WORKER LOOP (5-second interval)
2536
- // ============================================================================
2537
- // Phase 3.1: Increased from 5s to 15s — NOTIFY-driven execution handles the fast path
2538
- // Worker loop is now a safety net for missed notifications
2539
- const BASE_WORKER_INTERVAL_MS = 15_000;
2540
- const MAX_WORKER_INTERVAL_MS = 60_000;
2541
- let workerRunning = false;
2542
- let consecutiveErrors = 0;
2543
- let currentWorkerInterval = BASE_WORKER_INTERVAL_MS;
2544
- async function workflowWorkerLoop() {
2545
- if (workerRunning)
2546
- return; // Prevent concurrent runs
2547
- workerRunning = true;
2548
- try {
2549
- const supabase = getServiceClient();
2550
- const [stepResult, waitingResolved] = await Promise.all([
2551
- processWorkflowSteps(supabase, 10),
2552
- processWaitingSteps(supabase),
2553
- Promise.resolve(supabase.rpc("expire_pending_waitpoints")).then(() => { }).catch(e => log.warn({ err: e.message }, "expire_pending_waitpoints failed")), // Non-fatal
2554
- ]);
2555
- // Schedule triggers + timeout enforcement + event triggers + orphan cleanup + DLQ retries + node offline detection
2556
- const [scheduled, timedOut, eventsProcessed, orphansCleaned, dlqRetried, nodesOfflined] = await Promise.all([
2557
- processScheduleTriggers(supabase).catch(e => { log.warn({ err: e.message }, "processScheduleTriggers failed"); return 0; }),
2558
- enforceWorkflowTimeouts(supabase).catch(e => { log.warn({ err: e.message }, "enforceWorkflowTimeouts failed"); return 0; }),
2559
- processEventTriggers(supabase).catch(e => { log.warn({ err: e.message }, "processEventTriggers failed"); return 0; }),
2560
- cleanupOrphanedSteps(supabase).catch(e => { log.warn({ err: e.message }, "cleanupOrphanedSteps failed"); return 0; }),
2561
- processDlqRetries(supabase).catch(e => { log.warn({ err: e.message }, "processDlqRetries failed"); return 0; }),
2562
- enforceNodeOfflineStatus(supabase).catch(e => { log.warn({ err: e.message }, "enforceNodeOfflineStatus failed"); return 0; }),
2563
- ]);
2564
- if (stepResult.processed > 0 || waitingResolved > 0 || scheduled > 0 || timedOut > 0 || eventsProcessed > 0 || stepResult.reclaimed > 0 || orphansCleaned > 0 || dlqRetried > 0 || nodesOfflined > 0) {
2565
- log.info({ processed: stepResult.processed, errors: stepResult.errors, reclaimed: stepResult.reclaimed || 0, waiting: waitingResolved, scheduled, timedOut, events: eventsProcessed, orphans: orphansCleaned, dlqRetries: dlqRetried, nodesOfflined }, "worker tick");
2566
- }
2567
- // Reset backoff on success
2568
- if (consecutiveErrors > 0) {
2569
- consecutiveErrors = 0;
2570
- if (currentWorkerInterval !== BASE_WORKER_INTERVAL_MS) {
2571
- currentWorkerInterval = BASE_WORKER_INTERVAL_MS;
2572
- clearInterval(workerInterval);
2573
- workerInterval = setInterval(workflowWorkerLoop, currentWorkerInterval);
2574
- log.info({ intervalMs: currentWorkerInterval }, "worker interval reset after recovery");
2575
- }
2576
- }
2577
- }
2578
- catch (err) {
2579
- consecutiveErrors++;
2580
- log.error({ err: sanitizeError(err), consecutiveErrors }, "worker error");
2581
- // Exponential backoff: 5s → 10s → 20s → 40s → 60s (cap)
2582
- if (consecutiveErrors >= 3) {
2583
- const newInterval = Math.min(BASE_WORKER_INTERVAL_MS * Math.pow(2, consecutiveErrors - 2), MAX_WORKER_INTERVAL_MS);
2584
- if (newInterval !== currentWorkerInterval) {
2585
- currentWorkerInterval = newInterval;
2586
- clearInterval(workerInterval);
2587
- workerInterval = setInterval(workflowWorkerLoop, currentWorkerInterval);
2588
- log.warn({ intervalMs: currentWorkerInterval, consecutiveErrors }, "worker interval increased due to repeated errors");
2589
- }
2590
- }
2591
- }
2592
- finally {
2593
- workerRunning = false;
2594
- }
2595
- }
2596
- let workerInterval = setInterval(workflowWorkerLoop, BASE_WORKER_INTERVAL_MS);
1663
+
1664
+ // Start persistent workflow worker loop (extracted module)
1665
+ startWorkerLoop();
1666
+
2597
1667
  // Initialize shared Supabase client with retry logic before server starts
2598
1668
  initSupabase(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY);
2599
1669
  server.listen(PORT, () => {
2600
- log.info({ port: PORT, supabaseUrl: SUPABASE_URL, runtime: process.version, workerIntervalMs: BASE_WORKER_INTERVAL_MS }, "server listening");
2601
- // Initialize code worker pool for fast code step execution
2602
- try {
2603
- initWorkerPool();
2604
- const stats = getPoolStats();
2605
- log.info({ workers: stats.total }, "code worker pool initialized");
2606
- workerPoolReady = true;
2607
- }
2608
- catch (err) {
2609
- log.error({ err: err.message }, "worker pool init failed");
2610
- }
2611
- // Initialize local agent WebSocket gateway
2612
- try {
2613
- initLocalAgentGateway(server);
2614
- }
2615
- catch (err) {
2616
- log.error({ err: err.message }, "local-agent gateway init failed");
2617
- }
2618
- // Phase 3: Start pg LISTEN for real-time SSE streaming
2619
- setupPgListen().catch((err) => {
2620
- log.error({ err: err.message }, "pg-listen setup failed");
2621
- });
2622
- // Phase 4.2: Mark orphaned conversations as resumable
2623
- const serverBootTime = new Date();
2624
- markOrphaned(getServiceClient(), serverBootTime)
2625
- .then((count) => {
2626
- if (count > 0)
2627
- log.info({ count }, "orphaned conversations marked resumable");
2628
- })
2629
- .catch((err) => {
2630
- log.warn({ err: err.message }, "markOrphaned failed (table may not exist yet)");
2631
- });
1670
+ log.info({
1671
+ port: PORT,
1672
+ supabaseUrl: SUPABASE_URL,
1673
+ runtime: process.version
1674
+ }, "server listening");
1675
+
1676
+ // Initialize code worker pool for fast code step execution
1677
+ try {
1678
+ initWorkerPool();
1679
+ const stats = getPoolStats();
1680
+ log.info({
1681
+ workers: stats.total
1682
+ }, "code worker pool initialized");
1683
+ workerPoolReady = true;
1684
+ } catch (err) {
1685
+ log.error({
1686
+ err: err.message
1687
+ }, "worker pool init failed");
1688
+ }
1689
+
1690
+ // Initialize local agent WebSocket gateway
1691
+ try {
1692
+ initLocalAgentGateway(server);
1693
+ } catch (err) {
1694
+ log.error({
1695
+ err: err.message
1696
+ }, "local-agent gateway init failed");
1697
+ }
1698
+
1699
+ // Phase 3: Start pg LISTEN for real-time SSE streaming
1700
+ setupPgListen().catch(err => {
1701
+ log.error({
1702
+ err: err.message
1703
+ }, "pg-listen setup failed");
1704
+ });
1705
+
1706
+ // Phase 4.2: Mark orphaned conversations as resumable
1707
+ const serverBootTime = new Date();
1708
+ markOrphaned(getServiceClient(), serverBootTime).then(count => {
1709
+ if (count > 0) log.info({
1710
+ count
1711
+ }, "orphaned conversations marked resumable");
1712
+ }).catch(err => {
1713
+ log.warn({
1714
+ err: err.message
1715
+ }, "markOrphaned failed (table may not exist yet)");
1716
+ });
2632
1717
  });
1718
+
2633
1719
  // ============================================================================
2634
1720
  // GRACEFUL SHUTDOWN
2635
1721
  // ============================================================================
1722
+
2636
1723
  async function gracefulShutdown(signal) {
2637
- log.info({ signal, activeRequests }, "received shutdown signal, shutting down gracefully");
2638
- // 1. Stop accepting new connections
2639
- server.close(() => {
2640
- log.info("HTTP server closed");
2641
- });
2642
- // 2. Clear workflow worker intervals
2643
- clearInterval(workerInterval);
2644
- clearInterval(sseCleanupInterval);
2645
- // 3. Close all SSE client connections
2646
- for (const [, clients] of sseClients) {
2647
- for (const res of clients) {
2648
- try {
2649
- res.end();
2650
- }
2651
- catch { /* client already disconnected — benign */ }
2652
- }
2653
- clients.clear();
2654
- }
2655
- sseClients.clear();
2656
- // 3b. Flush ClickHouse span buffer before shutdown (prevents data loss on crash)
2657
- try {
2658
- await flushSpans();
2659
- log.info("span buffer flushed");
2660
- }
2661
- catch (err) {
2662
- log.error({ err: err.message }, "span buffer flush error");
2663
- }
2664
- // 4. Shut down code worker pool
2665
- try {
2666
- shutdownPool();
2667
- log.info("worker pool shut down");
2668
- }
2669
- catch (err) {
2670
- log.error({ err: err.message }, "worker pool shutdown error");
2671
- }
2672
- // 4b. Shut down local agent gateway
2673
- try {
2674
- shutdownAgentGateway();
2675
- }
2676
- catch (err) {
2677
- log.error({ err: err.message }, "agent gateway shutdown error");
2678
- }
2679
- // 4c. Shut down rate limiter
2680
- rateLimiter.shutdown();
2681
- // 5. Close pg LISTEN connection
2682
- if (pgClient) {
2683
- pgClient.end().catch(() => { });
2684
- pgClient = null;
2685
- log.info("pg LISTEN connection closed");
2686
- }
2687
- // 6. Wait for active requests to drain (up to 25 seconds)
2688
- const drainStart = Date.now();
2689
- const drainInterval = setInterval(() => {
2690
- if (activeRequests <= 0 || Date.now() - drainStart > 25_000) {
2691
- clearInterval(drainInterval);
2692
- log.info({ activeRequests }, "drain complete, exiting");
2693
- process.exit(activeRequests > 0 ? 1 : 0);
2694
- }
2695
- log.info({ activeRequests }, "waiting for active requests to drain...");
2696
- }, 1000);
2697
- // 7. Force exit after 30 seconds if graceful shutdown hangs (increased from 10s)
2698
- setTimeout(() => {
2699
- log.fatal({ activeRequests }, "graceful shutdown timed out after 30s, forcing exit");
2700
- process.exit(1);
2701
- }, 30_000).unref();
1724
+ log.info({
1725
+ signal,
1726
+ activeRequests
1727
+ }, "received shutdown signal, shutting down gracefully");
1728
+
1729
+ // 1. Stop accepting new connections
1730
+ server.close(() => {
1731
+ log.info("HTTP server closed");
1732
+ });
1733
+
1734
+ // 2. Clear workflow worker + SSE cleanup intervals
1735
+ stopWorkerLoop();
1736
+ clearInterval(sseCleanupInterval);
1737
+
1738
+ // 3. Close all SSE client connections
1739
+ const sseClients = getSseClients();
1740
+ for (const [, clients] of sseClients) {
1741
+ for (const res of clients) {
1742
+ try {
1743
+ res.end();
1744
+ } catch {/* client already disconnected — benign */}
1745
+ }
1746
+ clients.clear();
1747
+ }
1748
+ sseClients.clear();
1749
+
1750
+ // 3b. Flush ClickHouse span buffer before shutdown (prevents data loss on crash)
1751
+ try {
1752
+ await flushSpans();
1753
+ log.info("span buffer flushed");
1754
+ } catch (err) {
1755
+ log.error({
1756
+ err: err.message
1757
+ }, "span buffer flush error");
1758
+ }
1759
+
1760
+ // 4. Shut down code worker pool
1761
+ try {
1762
+ shutdownPool();
1763
+ log.info("worker pool shut down");
1764
+ } catch (err) {
1765
+ log.error({
1766
+ err: err.message
1767
+ }, "worker pool shutdown error");
1768
+ }
1769
+
1770
+ // 4b. Shut down local agent gateway
1771
+ try {
1772
+ shutdownAgentGateway();
1773
+ } catch (err) {
1774
+ log.error({
1775
+ err: err.message
1776
+ }, "agent gateway shutdown error");
1777
+ }
1778
+
1779
+ // 4c. Shut down rate limiter
1780
+ rateLimiter.shutdown();
1781
+
1782
+ // 5. Close pg LISTEN connection
1783
+ closePgClient();
1784
+
1785
+ // 6. Wait for active requests to drain (up to 25 seconds)
1786
+ const drainStart = Date.now();
1787
+ const drainInterval = setInterval(() => {
1788
+ if (activeRequests <= 0 || Date.now() - drainStart > 25_000) {
1789
+ clearInterval(drainInterval);
1790
+ log.info({
1791
+ activeRequests
1792
+ }, "drain complete, exiting");
1793
+ process.exit(activeRequests > 0 ? 1 : 0);
1794
+ }
1795
+ log.info({
1796
+ activeRequests
1797
+ }, "waiting for active requests to drain...");
1798
+ }, 1000);
1799
+
1800
+ // 7. Force exit after 30 seconds if graceful shutdown hangs (increased from 10s)
1801
+ setTimeout(() => {
1802
+ log.fatal({
1803
+ activeRequests
1804
+ }, "graceful shutdown timed out after 30s, forcing exit");
1805
+ process.exit(1);
1806
+ }, 30_000).unref();
2702
1807
  }
2703
1808
  process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
2704
1809
  process.on("SIGINT", () => gracefulShutdown("SIGINT"));
1810
+ //# sourceMappingURL=index.js.map