whale-code 6.5.4 → 6.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (853) hide show
  1. package/README.md +39 -31
  2. package/bin/{swagmanager-mcp.js → whale-code.js} +17 -2
  3. package/dist/cli/app.js +148 -72
  4. package/dist/cli/app.js.map +1 -0
  5. package/dist/cli/chat/AgentSelector.js +105 -10
  6. package/dist/cli/chat/AgentSelector.js.map +1 -0
  7. package/dist/cli/chat/ChatApp.d.ts +31 -0
  8. package/dist/cli/chat/ChatApp.js +539 -286
  9. package/dist/cli/chat/ChatApp.js.map +1 -0
  10. package/dist/cli/chat/ChatInput.js +1088 -770
  11. package/dist/cli/chat/ChatInput.js.map +1 -0
  12. package/dist/cli/chat/MarkdownText.js +39 -14
  13. package/dist/cli/chat/MarkdownText.js.map +1 -0
  14. package/dist/cli/chat/MemoryManager.js +181 -46
  15. package/dist/cli/chat/MemoryManager.js.map +1 -0
  16. package/dist/cli/chat/MessageList.d.ts +2 -3
  17. package/dist/cli/chat/MessageList.js +186 -45
  18. package/dist/cli/chat/MessageList.js.map +1 -0
  19. package/dist/cli/chat/ModelSelector.js +282 -63
  20. package/dist/cli/chat/ModelSelector.js.map +1 -0
  21. package/dist/cli/chat/NodeManager.js +165 -75
  22. package/dist/cli/chat/NodeManager.js.map +1 -0
  23. package/dist/cli/chat/NodeSelector.js +171 -30
  24. package/dist/cli/chat/NodeSelector.js.map +1 -0
  25. package/dist/cli/chat/PlanApproval.js +281 -57
  26. package/dist/cli/chat/PlanApproval.js.map +1 -0
  27. package/dist/cli/chat/RewindViewer.js +559 -144
  28. package/dist/cli/chat/RewindViewer.js.map +1 -0
  29. package/dist/cli/chat/SessionManager.js +137 -30
  30. package/dist/cli/chat/SessionManager.js.map +1 -0
  31. package/dist/cli/chat/SlashMenu.js +293 -164
  32. package/dist/cli/chat/SlashMenu.js.map +1 -0
  33. package/dist/cli/chat/StatusBar.js +172 -9
  34. package/dist/cli/chat/StatusBar.js.map +1 -0
  35. package/dist/cli/chat/StoreSelector.js +147 -18
  36. package/dist/cli/chat/StoreSelector.js.map +1 -0
  37. package/dist/cli/chat/StreamingText.d.ts +1 -5
  38. package/dist/cli/chat/StreamingText.js +22 -7
  39. package/dist/cli/chat/StreamingText.js.map +1 -0
  40. package/dist/cli/chat/SubagentPanel.d.ts +1 -2
  41. package/dist/cli/chat/SubagentPanel.js +612 -72
  42. package/dist/cli/chat/SubagentPanel.js.map +1 -0
  43. package/dist/cli/chat/TeamPanel.d.ts +1 -0
  44. package/dist/cli/chat/TeamPanel.js +230 -30
  45. package/dist/cli/chat/TeamPanel.js.map +1 -0
  46. package/dist/cli/chat/ThemeSelector.js +84 -24
  47. package/dist/cli/chat/ThemeSelector.js.map +1 -0
  48. package/dist/cli/chat/ToolIndicator.js +1476 -371
  49. package/dist/cli/chat/ToolIndicator.js.map +1 -0
  50. package/dist/cli/chat/hooks/useAgentLoop.d.ts +1 -0
  51. package/dist/cli/chat/hooks/useAgentLoop.js +481 -367
  52. package/dist/cli/chat/hooks/useAgentLoop.js.map +1 -0
  53. package/dist/cli/chat/hooks/useSlashCommands.d.ts +3 -14
  54. package/dist/cli/chat/hooks/useSlashCommands.js +744 -572
  55. package/dist/cli/chat/hooks/useSlashCommands.js.map +1 -0
  56. package/dist/cli/commands/config-cmd.js +56 -57
  57. package/dist/cli/commands/config-cmd.js.map +1 -0
  58. package/dist/cli/commands/db.js +184 -169
  59. package/dist/cli/commands/db.js.map +1 -0
  60. package/dist/cli/commands/doctor.js +212 -122
  61. package/dist/cli/commands/doctor.js.map +1 -0
  62. package/dist/cli/commands/init.js +211 -244
  63. package/dist/cli/commands/init.js.map +1 -0
  64. package/dist/cli/commands/mcp.js +127 -122
  65. package/dist/cli/commands/mcp.js.map +1 -0
  66. package/dist/cli/login/LoginApp.js +355 -141
  67. package/dist/cli/login/LoginApp.js.map +1 -0
  68. package/dist/cli/print-mode.js +196 -177
  69. package/dist/cli/print-mode.js.map +1 -0
  70. package/dist/cli/serve-mode.js +615 -530
  71. package/dist/cli/serve-mode.js.map +1 -0
  72. package/dist/cli/services/agent-config.d.ts +29 -0
  73. package/dist/cli/services/agent-config.js +91 -0
  74. package/dist/cli/services/agent-config.js.map +1 -0
  75. package/dist/cli/services/agent-definitions.d.ts +4 -1
  76. package/dist/cli/services/agent-definitions.js +97 -56
  77. package/dist/cli/services/agent-definitions.js.map +1 -0
  78. package/dist/cli/services/agent-events.js +225 -162
  79. package/dist/cli/services/agent-events.js.map +1 -0
  80. package/dist/cli/services/agent-loop.js +978 -669
  81. package/dist/cli/services/agent-loop.js.map +1 -0
  82. package/dist/cli/services/agent-worker-base.d.ts +35 -5
  83. package/dist/cli/services/agent-worker-base.js +337 -153
  84. package/dist/cli/services/agent-worker-base.js.map +1 -0
  85. package/dist/cli/services/api-retry.js +69 -64
  86. package/dist/cli/services/api-retry.js.map +1 -0
  87. package/dist/cli/services/auth-service.d.ts +3 -3
  88. package/dist/cli/services/auth-service.js +209 -132
  89. package/dist/cli/services/auth-service.js.map +1 -0
  90. package/dist/cli/services/background-processes.js +343 -267
  91. package/dist/cli/services/background-processes.js.map +1 -0
  92. package/dist/cli/services/browser-auth.d.ts +2 -2
  93. package/dist/cli/services/browser-auth.js +159 -118
  94. package/dist/cli/services/browser-auth.js.map +1 -0
  95. package/dist/cli/services/claude-md-loader.js +40 -36
  96. package/dist/cli/services/claude-md-loader.js.map +1 -0
  97. package/dist/cli/services/config-store.d.ts +9 -4
  98. package/dist/cli/services/config-store.js +164 -117
  99. package/dist/cli/services/config-store.js.map +1 -0
  100. package/dist/cli/services/debug-log.d.ts +1 -1
  101. package/dist/cli/services/debug-log.js +34 -35
  102. package/dist/cli/services/debug-log.js.map +1 -0
  103. package/dist/cli/services/env-detect.d.ts +7 -0
  104. package/dist/cli/services/env-detect.js +9 -0
  105. package/dist/cli/services/env-detect.js.map +1 -0
  106. package/dist/cli/services/error-logger.d.ts +2 -3
  107. package/dist/cli/services/error-logger.js +189 -180
  108. package/dist/cli/services/error-logger.js.map +1 -0
  109. package/dist/cli/services/file-history.d.ts +1 -1
  110. package/dist/cli/services/file-history.js +50 -54
  111. package/dist/cli/services/file-history.js.map +1 -0
  112. package/dist/cli/services/format-server-response.js +332 -372
  113. package/dist/cli/services/format-server-response.js.map +1 -0
  114. package/dist/cli/services/git-context.js +61 -45
  115. package/dist/cli/services/git-context.js.map +1 -0
  116. package/dist/cli/services/hooks.d.ts +2 -2
  117. package/dist/cli/services/hooks.js +195 -180
  118. package/dist/cli/services/hooks.js.map +1 -0
  119. package/dist/cli/services/ink-incremental.d.ts +19 -0
  120. package/dist/cli/services/ink-incremental.js +59 -0
  121. package/dist/cli/services/ink-incremental.js.map +1 -0
  122. package/dist/cli/services/ink-resize-fix.js +54 -44
  123. package/dist/cli/services/ink-resize-fix.js.map +1 -0
  124. package/dist/cli/services/ink-sync-output.d.ts +12 -0
  125. package/dist/cli/services/ink-sync-output.js +16 -0
  126. package/dist/cli/services/ink-sync-output.js.map +1 -0
  127. package/dist/cli/services/interactive-tools.js +268 -212
  128. package/dist/cli/services/interactive-tools.js.map +1 -0
  129. package/dist/cli/services/keybinding-manager.d.ts +11 -1
  130. package/dist/cli/services/keybinding-manager.js +126 -63
  131. package/dist/cli/services/keybinding-manager.js.map +1 -0
  132. package/dist/cli/services/local-tools.d.ts +1 -1
  133. package/dist/cli/services/local-tools.js +939 -656
  134. package/dist/cli/services/local-tools.js.map +1 -0
  135. package/dist/cli/services/lsp-manager.js +757 -594
  136. package/dist/cli/services/lsp-manager.js.map +1 -0
  137. package/dist/cli/services/mcp-client.d.ts +1 -1
  138. package/dist/cli/services/mcp-client.js +173 -134
  139. package/dist/cli/services/mcp-client.js.map +1 -0
  140. package/dist/cli/services/memory-manager.js +53 -40
  141. package/dist/cli/services/memory-manager.js.map +1 -0
  142. package/dist/cli/services/model-manager.js +55 -40
  143. package/dist/cli/services/model-manager.js.map +1 -0
  144. package/dist/cli/services/model-router.js +115 -85
  145. package/dist/cli/services/model-router.js.map +1 -0
  146. package/dist/cli/services/paths.d.ts +30 -0
  147. package/dist/cli/services/paths.js +81 -0
  148. package/dist/cli/services/paths.js.map +1 -0
  149. package/dist/cli/services/permission-modes.js +32 -25
  150. package/dist/cli/services/permission-modes.js.map +1 -0
  151. package/dist/cli/services/rewind.js +182 -168
  152. package/dist/cli/services/rewind.js.map +1 -0
  153. package/dist/cli/services/ripgrep.js +115 -115
  154. package/dist/cli/services/ripgrep.js.map +1 -0
  155. package/dist/cli/services/sandbox.d.ts +1 -1
  156. package/dist/cli/services/sandbox.js +58 -37
  157. package/dist/cli/services/sandbox.js.map +1 -0
  158. package/dist/cli/services/server-tools.js +738 -565
  159. package/dist/cli/services/server-tools.js.map +1 -0
  160. package/dist/cli/services/session-persistence.js +69 -74
  161. package/dist/cli/services/session-persistence.js.map +1 -0
  162. package/dist/cli/services/subagent-worker.js +42 -27
  163. package/dist/cli/services/subagent-worker.js.map +1 -0
  164. package/dist/cli/services/subagent.d.ts +2 -0
  165. package/dist/cli/services/subagent.js +606 -430
  166. package/dist/cli/services/subagent.js.map +1 -0
  167. package/dist/cli/services/system-prompt.js +86 -78
  168. package/dist/cli/services/system-prompt.js.map +1 -0
  169. package/dist/cli/services/task-decomposer.d.ts +1 -1
  170. package/dist/cli/services/task-decomposer.js +172 -139
  171. package/dist/cli/services/task-decomposer.js.map +1 -0
  172. package/dist/cli/services/team-lead.d.ts +2 -2
  173. package/dist/cli/services/team-lead.js +727 -529
  174. package/dist/cli/services/team-lead.js.map +1 -0
  175. package/dist/cli/services/team-state.js +319 -319
  176. package/dist/cli/services/team-state.js.map +1 -0
  177. package/dist/cli/services/teammate.d.ts +8 -2
  178. package/dist/cli/services/teammate.js +862 -560
  179. package/dist/cli/services/teammate.js.map +1 -0
  180. package/dist/cli/services/telemetry.d.ts +6 -1
  181. package/dist/cli/services/telemetry.js +180 -157
  182. package/dist/cli/services/telemetry.js.map +1 -0
  183. package/dist/cli/services/tools/agent-tools.d.ts +3 -3
  184. package/dist/cli/services/tools/agent-tools.js +480 -322
  185. package/dist/cli/services/tools/agent-tools.js.map +1 -0
  186. package/dist/cli/services/tools/file-ops.js +563 -450
  187. package/dist/cli/services/tools/file-ops.js.map +1 -0
  188. package/dist/cli/services/tools/search-tools.js +231 -162
  189. package/dist/cli/services/tools/search-tools.js.map +1 -0
  190. package/dist/cli/services/tools/shell-exec.js +197 -151
  191. package/dist/cli/services/tools/shell-exec.js.map +1 -0
  192. package/dist/cli/services/tools/task-manager.js +206 -173
  193. package/dist/cli/services/tools/task-manager.js.map +1 -0
  194. package/dist/cli/services/tools/web-tools.js +388 -341
  195. package/dist/cli/services/tools/web-tools.js.map +1 -0
  196. package/dist/cli/setup/SetupApp.d.ts +2 -2
  197. package/dist/cli/setup/SetupApp.js +608 -160
  198. package/dist/cli/setup/SetupApp.js.map +1 -0
  199. package/dist/cli/shared/ErrorBoundary.d.ts +22 -0
  200. package/dist/cli/shared/ErrorBoundary.js +73 -0
  201. package/dist/cli/shared/ErrorBoundary.js.map +1 -0
  202. package/dist/cli/shared/MatrixIntro.js +66 -69
  203. package/dist/cli/shared/MatrixIntro.js.map +1 -0
  204. package/dist/cli/shared/SpinnerSlot.d.ts +14 -0
  205. package/dist/cli/shared/SpinnerSlot.js +63 -0
  206. package/dist/cli/shared/SpinnerSlot.js.map +1 -0
  207. package/dist/cli/shared/Theme.d.ts +1 -1
  208. package/dist/cli/shared/Theme.js +136 -92
  209. package/dist/cli/shared/Theme.js.map +1 -0
  210. package/dist/cli/shared/WhaleBanner.js +99 -11
  211. package/dist/cli/shared/WhaleBanner.js.map +1 -0
  212. package/dist/cli/shared/markdown.d.ts +3 -1
  213. package/dist/cli/shared/markdown.js +736 -674
  214. package/dist/cli/shared/markdown.js.map +1 -0
  215. package/dist/cli/shared/marked-terminal.d.js +2 -0
  216. package/dist/cli/shared/marked-terminal.d.js.map +1 -0
  217. package/dist/cli/shared/theme-manager.js +99 -90
  218. package/dist/cli/shared/theme-manager.js.map +1 -0
  219. package/dist/cli/shared/theme-presets.js +256 -254
  220. package/dist/cli/shared/theme-presets.js.map +1 -0
  221. package/dist/cli/status/StatusApp.js +235 -86
  222. package/dist/cli/status/StatusApp.js.map +1 -0
  223. package/dist/cli/stores/StoreApp.js +275 -65
  224. package/dist/cli/stores/StoreApp.js.map +1 -0
  225. package/dist/index.d.ts +2 -2
  226. package/dist/index.js +509 -396
  227. package/dist/index.js.map +1 -0
  228. package/dist/local-agent/connection.d.ts +2 -2
  229. package/dist/local-agent/connection.js +352 -293
  230. package/dist/local-agent/connection.js.map +1 -0
  231. package/dist/local-agent/discovery.js +259 -122
  232. package/dist/local-agent/discovery.js.map +1 -0
  233. package/dist/local-agent/executor.js +216 -193
  234. package/dist/local-agent/executor.js.map +1 -0
  235. package/dist/local-agent/index.d.ts +2 -2
  236. package/dist/local-agent/index.js +156 -156
  237. package/dist/local-agent/index.js.map +1 -0
  238. package/dist/node/adapters/base.js +18 -8
  239. package/dist/node/adapters/base.js.map +1 -0
  240. package/dist/node/adapters/discord.js +286 -275
  241. package/dist/node/adapters/discord.js.map +1 -0
  242. package/dist/node/adapters/email.js +189 -202
  243. package/dist/node/adapters/email.js.map +1 -0
  244. package/dist/node/adapters/imessage.js +145 -142
  245. package/dist/node/adapters/imessage.js.map +1 -0
  246. package/dist/node/adapters/slack.js +237 -236
  247. package/dist/node/adapters/slack.js.map +1 -0
  248. package/dist/node/adapters/sms.js +149 -151
  249. package/dist/node/adapters/sms.js.map +1 -0
  250. package/dist/node/adapters/telegram.js +88 -92
  251. package/dist/node/adapters/telegram.js.map +1 -0
  252. package/dist/node/adapters/webchat.js +160 -136
  253. package/dist/node/adapters/webchat.js.map +1 -0
  254. package/dist/node/adapters/whatsapp.js +212 -215
  255. package/dist/node/adapters/whatsapp.js.map +1 -0
  256. package/dist/node/cli.js +884 -653
  257. package/dist/node/cli.js.map +1 -0
  258. package/dist/node/config.js +20 -18
  259. package/dist/node/config.js.map +1 -0
  260. package/dist/node/gateway-client.js +191 -181
  261. package/dist/node/gateway-client.js.map +1 -0
  262. package/dist/node/portal/clipboard.js +161 -130
  263. package/dist/node/portal/clipboard.js.map +1 -0
  264. package/dist/node/portal/discovery.js +51 -45
  265. package/dist/node/portal/discovery.js.map +1 -0
  266. package/dist/node/portal/forward.js +64 -58
  267. package/dist/node/portal/forward.js.map +1 -0
  268. package/dist/node/portal/index.js +246 -221
  269. package/dist/node/portal/index.js.map +1 -0
  270. package/dist/node/portal/multiplexer.js +192 -182
  271. package/dist/node/portal/multiplexer.js.map +1 -0
  272. package/dist/node/portal/permissions.js +102 -70
  273. package/dist/node/portal/permissions.js.map +1 -0
  274. package/dist/node/portal/protocol.js +153 -116
  275. package/dist/node/portal/protocol.js.map +1 -0
  276. package/dist/node/portal/screen.js +80 -69
  277. package/dist/node/portal/screen.js.map +1 -0
  278. package/dist/node/portal/session.js +124 -117
  279. package/dist/node/portal/session.js.map +1 -0
  280. package/dist/node/portal/shell.js +140 -113
  281. package/dist/node/portal/shell.js.map +1 -0
  282. package/dist/node/portal/stream.js +77 -75
  283. package/dist/node/portal/stream.js.map +1 -0
  284. package/dist/node/portal/transfer.js +190 -167
  285. package/dist/node/portal/transfer.js.map +1 -0
  286. package/dist/node/portal/ui.js +124 -99
  287. package/dist/node/portal/ui.js.map +1 -0
  288. package/dist/node/remote-desktop/compile-helper.js +50 -45
  289. package/dist/node/remote-desktop/compile-helper.js.map +1 -0
  290. package/dist/node/remote-desktop/index.js +215 -187
  291. package/dist/node/remote-desktop/index.js.map +1 -0
  292. package/dist/node/remote-desktop/protocol.js +45 -29
  293. package/dist/node/remote-desktop/protocol.js.map +1 -0
  294. package/dist/node/runtime.js +493 -410
  295. package/dist/node/runtime.js.map +1 -0
  296. package/dist/server/handlers/__test-utils__/test-db.js +39 -89
  297. package/dist/server/handlers/__test-utils__/test-db.js.map +1 -0
  298. package/dist/server/handlers/analytics.js +467 -261
  299. package/dist/server/handlers/analytics.js.map +1 -0
  300. package/dist/server/handlers/api-docs.d.ts +6 -0
  301. package/dist/server/handlers/api-docs.js +1613 -0
  302. package/dist/server/handlers/api-docs.js.map +1 -0
  303. package/dist/server/handlers/api-keys.js +295 -232
  304. package/dist/server/handlers/api-keys.js.map +1 -0
  305. package/dist/server/handlers/billing.js +330 -239
  306. package/dist/server/handlers/billing.js.map +1 -0
  307. package/dist/server/handlers/browser.js +468 -395
  308. package/dist/server/handlers/browser.js.map +1 -0
  309. package/dist/server/handlers/catalog.js +1377 -978
  310. package/dist/server/handlers/catalog.js.map +1 -0
  311. package/dist/server/handlers/clickhouse.js +157 -109
  312. package/dist/server/handlers/clickhouse.js.map +1 -0
  313. package/dist/server/handlers/comms.d.ts +0 -53
  314. package/dist/server/handlers/comms.js +1443 -970
  315. package/dist/server/handlers/comms.js.map +1 -0
  316. package/dist/server/handlers/creations.js +461 -394
  317. package/dist/server/handlers/creations.js.map +1 -0
  318. package/dist/server/handlers/crm.js +1082 -791
  319. package/dist/server/handlers/crm.js.map +1 -0
  320. package/dist/server/handlers/discovery.js +251 -232
  321. package/dist/server/handlers/discovery.js.map +1 -0
  322. package/dist/server/handlers/embeddings.js +241 -164
  323. package/dist/server/handlers/embeddings.js.map +1 -0
  324. package/dist/server/handlers/enrichment.js +887 -718
  325. package/dist/server/handlers/enrichment.js.map +1 -0
  326. package/dist/server/handlers/image-gen.js +467 -376
  327. package/dist/server/handlers/image-gen.js.map +1 -0
  328. package/dist/server/handlers/inventory.js +797 -424
  329. package/dist/server/handlers/inventory.js.map +1 -0
  330. package/dist/server/handlers/kali.js +272 -230
  331. package/dist/server/handlers/kali.js.map +1 -0
  332. package/dist/server/handlers/llm-providers.js +803 -580
  333. package/dist/server/handlers/llm-providers.js.map +1 -0
  334. package/dist/server/handlers/local-agent.js +133 -105
  335. package/dist/server/handlers/local-agent.js.map +1 -0
  336. package/dist/server/handlers/media.js +1179 -857
  337. package/dist/server/handlers/media.js.map +1 -0
  338. package/dist/server/handlers/meta-ads.js +2669 -2093
  339. package/dist/server/handlers/meta-ads.js.map +1 -0
  340. package/dist/server/handlers/nodes.js +1321 -913
  341. package/dist/server/handlers/nodes.js.map +1 -0
  342. package/dist/server/handlers/operations.js +183 -157
  343. package/dist/server/handlers/operations.js.map +1 -0
  344. package/dist/server/handlers/platform.js +346 -210
  345. package/dist/server/handlers/platform.js.map +1 -0
  346. package/dist/server/handlers/remove-bg.js +118 -86
  347. package/dist/server/handlers/remove-bg.js.map +1 -0
  348. package/dist/server/handlers/storefront.js +586 -446
  349. package/dist/server/handlers/storefront.js.map +1 -0
  350. package/dist/server/handlers/supply-chain.js +546 -326
  351. package/dist/server/handlers/supply-chain.js.map +1 -0
  352. package/dist/server/handlers/transcription.js +106 -97
  353. package/dist/server/handlers/transcription.js.map +1 -0
  354. package/dist/server/handlers/video-gen.js +593 -424
  355. package/dist/server/handlers/video-gen.js.map +1 -0
  356. package/dist/server/handlers/voice.js +1458 -1017
  357. package/dist/server/handlers/voice.js.map +1 -0
  358. package/dist/server/handlers/workflow-steps.js +2837 -2116
  359. package/dist/server/handlers/workflow-steps.js.map +1 -0
  360. package/dist/server/handlers/workflows.js +1630 -933
  361. package/dist/server/handlers/workflows.js.map +1 -0
  362. package/dist/server/index.js +3166 -2390
  363. package/dist/server/index.js.map +1 -0
  364. package/dist/server/lib/batch-client.js +471 -409
  365. package/dist/server/lib/batch-client.js.map +1 -0
  366. package/dist/server/lib/clickhouse-buffer.js +118 -104
  367. package/dist/server/lib/clickhouse-buffer.js.map +1 -0
  368. package/dist/server/lib/clickhouse-client.js +107 -107
  369. package/dist/server/lib/clickhouse-client.js.map +1 -0
  370. package/dist/server/lib/coa-renderer.js +1786 -356
  371. package/dist/server/lib/coa-renderer.js.map +1 -0
  372. package/dist/server/lib/code-worker-pool.js +227 -177
  373. package/dist/server/lib/code-worker-pool.js.map +1 -0
  374. package/dist/server/lib/code-worker.js +174 -164
  375. package/dist/server/lib/code-worker.js.map +1 -0
  376. package/dist/server/lib/compaction-service.d.ts +2 -12
  377. package/dist/server/lib/compaction-service.js +74 -184
  378. package/dist/server/lib/compaction-service.js.map +1 -0
  379. package/dist/server/lib/logger.js +36 -24
  380. package/dist/server/lib/logger.js.map +1 -0
  381. package/dist/server/lib/otel.js +101 -80
  382. package/dist/server/lib/otel.js.map +1 -0
  383. package/dist/server/lib/pdf-renderer.d.ts +1 -1
  384. package/dist/server/lib/pdf-renderer.js +954 -776
  385. package/dist/server/lib/pdf-renderer.js.map +1 -0
  386. package/dist/server/lib/prompt-sanitizer.js +188 -108
  387. package/dist/server/lib/prompt-sanitizer.js.map +1 -0
  388. package/dist/server/lib/provider-capabilities.js +136 -138
  389. package/dist/server/lib/provider-capabilities.js.map +1 -0
  390. package/dist/server/lib/provider-failover.js +190 -168
  391. package/dist/server/lib/provider-failover.js.map +1 -0
  392. package/dist/server/lib/rate-limiter.js +186 -117
  393. package/dist/server/lib/rate-limiter.js.map +1 -0
  394. package/dist/server/lib/react-pdf-layout.js +551 -382
  395. package/dist/server/lib/react-pdf-layout.js.map +1 -0
  396. package/dist/server/lib/server-agent-loop.d.ts +9 -0
  397. package/dist/server/lib/server-agent-loop.js +906 -624
  398. package/dist/server/lib/server-agent-loop.js.map +1 -0
  399. package/dist/server/lib/server-subagent.d.ts +2 -0
  400. package/dist/server/lib/server-subagent.js +260 -162
  401. package/dist/server/lib/server-subagent.js.map +1 -0
  402. package/dist/server/lib/session-checkpoint.js +105 -96
  403. package/dist/server/lib/session-checkpoint.js.map +1 -0
  404. package/dist/server/lib/ssrf-guard.js +193 -184
  405. package/dist/server/lib/ssrf-guard.js.map +1 -0
  406. package/dist/server/lib/supabase-client.js +94 -82
  407. package/dist/server/lib/supabase-client.js.map +1 -0
  408. package/dist/server/lib/template-resolver.js +154 -176
  409. package/dist/server/lib/template-resolver.js.map +1 -0
  410. package/dist/server/lib/utils.js +242 -133
  411. package/dist/server/lib/utils.js.map +1 -0
  412. package/dist/server/local-agent-gateway.d.ts +2 -2
  413. package/dist/server/local-agent-gateway.js +785 -627
  414. package/dist/server/local-agent-gateway.js.map +1 -0
  415. package/dist/server/providers/anthropic.js +254 -176
  416. package/dist/server/providers/anthropic.js.map +1 -0
  417. package/dist/server/providers/bedrock.js +221 -162
  418. package/dist/server/providers/bedrock.js.map +1 -0
  419. package/dist/server/providers/gemini.js +548 -418
  420. package/dist/server/providers/gemini.js.map +1 -0
  421. package/dist/server/providers/openai.js +571 -437
  422. package/dist/server/providers/openai.js.map +1 -0
  423. package/dist/server/providers/registry.js +23 -18
  424. package/dist/server/providers/registry.js.map +1 -0
  425. package/dist/server/providers/shared.js +123 -95
  426. package/dist/server/providers/shared.js.map +1 -0
  427. package/dist/server/providers/types.js +1 -11
  428. package/dist/server/providers/types.js.map +1 -0
  429. package/dist/server/proxy-handlers.js +209 -165
  430. package/dist/server/proxy-handlers.js.map +1 -0
  431. package/dist/server/tool-router.d.ts +13 -0
  432. package/dist/server/tool-router.js +960 -598
  433. package/dist/server/tool-router.js.map +1 -0
  434. package/dist/server/validation.js +248 -188
  435. package/dist/server/validation.js.map +1 -0
  436. package/dist/server/worker.js +202 -133
  437. package/dist/server/worker.js.map +1 -0
  438. package/dist/setup.d.ts +2 -2
  439. package/dist/setup.js +151 -147
  440. package/dist/setup.js.map +1 -0
  441. package/dist/shared/agent-core.d.ts +191 -24
  442. package/dist/shared/agent-core.js +971 -462
  443. package/dist/shared/agent-core.js.map +1 -0
  444. package/dist/shared/anthropic-types.js +1 -6
  445. package/dist/shared/anthropic-types.js.map +1 -0
  446. package/dist/shared/api-client.d.ts +17 -9
  447. package/dist/shared/api-client.js +419 -327
  448. package/dist/shared/api-client.js.map +1 -0
  449. package/dist/shared/compaction.d.ts +36 -0
  450. package/dist/shared/compaction.js +138 -0
  451. package/dist/shared/compaction.js.map +1 -0
  452. package/dist/shared/constants.js +67 -64
  453. package/dist/shared/constants.js.map +1 -0
  454. package/dist/shared/sse-parser.js +221 -219
  455. package/dist/shared/sse-parser.js.map +1 -0
  456. package/dist/shared/tool-dispatch.d.ts +4 -2
  457. package/dist/shared/tool-dispatch.js +226 -165
  458. package/dist/shared/tool-dispatch.js.map +1 -0
  459. package/dist/shared/types.js +1 -6
  460. package/dist/shared/types.js.map +1 -0
  461. package/dist/types/cli-highlight.d.js +2 -0
  462. package/dist/types/cli-highlight.d.js.map +1 -0
  463. package/dist/types/diff.d.js +2 -0
  464. package/dist/types/diff.d.js.map +1 -0
  465. package/dist/types/pdf-parse.d.js +2 -0
  466. package/dist/types/pdf-parse.d.js.map +1 -0
  467. package/dist/updater.d.ts +1 -1
  468. package/dist/updater.js +118 -92
  469. package/dist/updater.js.map +1 -0
  470. package/dist/webchat/widget.js +227 -380
  471. package/dist/webchat/widget.js.map +1 -0
  472. package/package.json +22 -10
  473. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  474. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  475. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  476. package/vendor/ink/build/apply-styles.js +175 -0
  477. package/vendor/ink/build/build-layout.js +77 -0
  478. package/vendor/ink/build/calculate-wrapped-text.js +53 -0
  479. package/vendor/ink/build/colorize.d.ts +3 -0
  480. package/vendor/ink/build/colorize.js +48 -0
  481. package/vendor/ink/build/colorize.js.map +1 -0
  482. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  483. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  484. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  485. package/vendor/ink/build/components/App.d.ts +18 -0
  486. package/vendor/ink/build/components/App.js +351 -0
  487. package/vendor/ink/build/components/App.js.map +1 -0
  488. package/vendor/ink/build/components/AppContext.d.ts +15 -0
  489. package/vendor/ink/build/components/AppContext.js +11 -0
  490. package/vendor/ink/build/components/AppContext.js.map +1 -0
  491. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  492. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  493. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  494. package/vendor/ink/build/components/Box.d.ts +117 -0
  495. package/vendor/ink/build/components/Box.js +34 -0
  496. package/vendor/ink/build/components/Box.js.map +1 -0
  497. package/vendor/ink/build/components/Color.js +62 -0
  498. package/vendor/ink/build/components/Cursor.d.ts +83 -0
  499. package/vendor/ink/build/components/Cursor.js +53 -0
  500. package/vendor/ink/build/components/Cursor.js.map +1 -0
  501. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  502. package/vendor/ink/build/components/CursorContext.js +8 -0
  503. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  504. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  505. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  506. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  507. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  508. package/vendor/ink/build/components/ErrorOverview.js +84 -0
  509. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  510. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  511. package/vendor/ink/build/components/FocusContext.js +17 -0
  512. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  513. package/vendor/ink/build/components/Newline.d.ts +13 -0
  514. package/vendor/ink/build/components/Newline.js +8 -0
  515. package/vendor/ink/build/components/Newline.js.map +1 -0
  516. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  517. package/vendor/ink/build/components/Spacer.js +11 -0
  518. package/vendor/ink/build/components/Spacer.js.map +1 -0
  519. package/vendor/ink/build/components/Static.d.ts +24 -0
  520. package/vendor/ink/build/components/Static.js +28 -0
  521. package/vendor/ink/build/components/Static.js.map +1 -0
  522. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  523. package/vendor/ink/build/components/StderrContext.js +13 -0
  524. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  525. package/vendor/ink/build/components/StdinContext.d.ts +22 -0
  526. package/vendor/ink/build/components/StdinContext.js +19 -0
  527. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  528. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  529. package/vendor/ink/build/components/StdoutContext.js +13 -0
  530. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  531. package/vendor/ink/build/components/Text.d.ts +55 -0
  532. package/vendor/ink/build/components/Text.js +50 -0
  533. package/vendor/ink/build/components/Text.js.map +1 -0
  534. package/vendor/ink/build/components/Transform.d.ts +16 -0
  535. package/vendor/ink/build/components/Transform.js +15 -0
  536. package/vendor/ink/build/components/Transform.js.map +1 -0
  537. package/vendor/ink/build/cursor-helpers.d.ts +38 -0
  538. package/vendor/ink/build/cursor-helpers.js +56 -0
  539. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  540. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  541. package/vendor/ink/build/devtools-window-polyfill.js +65 -0
  542. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  543. package/vendor/ink/build/devtools.d.ts +1 -0
  544. package/vendor/ink/build/devtools.js +11 -0
  545. package/vendor/ink/build/devtools.js.map +1 -0
  546. package/vendor/ink/build/dom.d.ts +56 -0
  547. package/vendor/ink/build/dom.js +124 -0
  548. package/vendor/ink/build/dom.js.map +1 -0
  549. package/vendor/ink/build/experimental/apply-style.js +140 -0
  550. package/vendor/ink/build/experimental/dom.js +123 -0
  551. package/vendor/ink/build/experimental/output.js +91 -0
  552. package/vendor/ink/build/experimental/reconciler.js +141 -0
  553. package/vendor/ink/build/experimental/renderer.js +81 -0
  554. package/vendor/ink/build/get-max-width.d.ts +3 -0
  555. package/vendor/ink/build/get-max-width.js +10 -0
  556. package/vendor/ink/build/get-max-width.js.map +1 -0
  557. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  558. package/vendor/ink/build/hooks/use-app.js +8 -0
  559. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  560. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  561. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  562. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  563. package/vendor/ink/build/hooks/use-focus-manager.d.ts +28 -0
  564. package/vendor/ink/build/hooks/use-focus-manager.js +17 -0
  565. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  566. package/vendor/ink/build/hooks/use-focus.d.ts +29 -0
  567. package/vendor/ink/build/hooks/use-focus.js +42 -0
  568. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  569. package/vendor/ink/build/hooks/use-input.d.ts +131 -0
  570. package/vendor/ink/build/hooks/use-input.js +124 -0
  571. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  572. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +5 -0
  573. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +11 -0
  574. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  575. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  576. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  577. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  578. package/vendor/ink/build/hooks/use-stdin.d.ts +5 -0
  579. package/vendor/ink/build/hooks/use-stdin.js +8 -0
  580. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  581. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  582. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  583. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  584. package/vendor/ink/build/hooks/useInput.js +38 -0
  585. package/vendor/ink/build/index.d.ts +34 -0
  586. package/vendor/ink/build/index.js +20 -0
  587. package/vendor/ink/build/index.js.map +1 -0
  588. package/vendor/ink/build/ink.d.ts +90 -0
  589. package/vendor/ink/build/ink.js +654 -0
  590. package/vendor/ink/build/ink.js.map +1 -0
  591. package/vendor/ink/build/input-parser.d.ts +7 -0
  592. package/vendor/ink/build/input-parser.js +154 -0
  593. package/vendor/ink/build/input-parser.js.map +1 -0
  594. package/vendor/ink/build/instance.js +205 -0
  595. package/vendor/ink/build/instances.d.ts +3 -0
  596. package/vendor/ink/build/instances.js +8 -0
  597. package/vendor/ink/build/instances.js.map +1 -0
  598. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  599. package/vendor/ink/build/kitty-keyboard.js +32 -0
  600. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  601. package/vendor/ink/build/layout.d.ts +7 -0
  602. package/vendor/ink/build/layout.js +33 -0
  603. package/vendor/ink/build/layout.js.map +1 -0
  604. package/vendor/ink/build/log-update.d.ts +19 -0
  605. package/vendor/ink/build/log-update.js +243 -0
  606. package/vendor/ink/build/log-update.js.map +1 -0
  607. package/vendor/ink/build/measure-element.d.ts +16 -0
  608. package/vendor/ink/build/measure-element.js +9 -0
  609. package/vendor/ink/build/measure-element.js.map +1 -0
  610. package/vendor/ink/build/measure-text.d.ts +6 -0
  611. package/vendor/ink/build/measure-text.js +21 -0
  612. package/vendor/ink/build/measure-text.js.map +1 -0
  613. package/vendor/ink/build/options.d.ts +52 -0
  614. package/vendor/ink/build/options.js +2 -0
  615. package/vendor/ink/build/options.js.map +1 -0
  616. package/vendor/ink/build/output.d.ts +35 -0
  617. package/vendor/ink/build/output.js +183 -0
  618. package/vendor/ink/build/output.js.map +1 -0
  619. package/vendor/ink/build/parse-keypress.d.ts +22 -0
  620. package/vendor/ink/build/parse-keypress.js +493 -0
  621. package/vendor/ink/build/parse-keypress.js.map +1 -0
  622. package/vendor/ink/build/reconciler.d.ts +4 -0
  623. package/vendor/ink/build/reconciler.js +274 -0
  624. package/vendor/ink/build/reconciler.js.map +1 -0
  625. package/vendor/ink/build/render-background.d.ts +4 -0
  626. package/vendor/ink/build/render-background.js +25 -0
  627. package/vendor/ink/build/render-background.js.map +1 -0
  628. package/vendor/ink/build/render-border.d.ts +4 -0
  629. package/vendor/ink/build/render-border.js +73 -0
  630. package/vendor/ink/build/render-border.js.map +1 -0
  631. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  632. package/vendor/ink/build/render-node-to-output.js +147 -0
  633. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  634. package/vendor/ink/build/render-to-string.d.ts +38 -0
  635. package/vendor/ink/build/render-to-string.js +115 -0
  636. package/vendor/ink/build/render-to-string.js.map +1 -0
  637. package/vendor/ink/build/render.d.ts +121 -0
  638. package/vendor/ink/build/render.js +55 -0
  639. package/vendor/ink/build/render.js.map +1 -0
  640. package/vendor/ink/build/renderer.d.ts +8 -0
  641. package/vendor/ink/build/renderer.js +55 -0
  642. package/vendor/ink/build/renderer.js.map +1 -0
  643. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  644. package/vendor/ink/build/sanitize-ansi.js +27 -0
  645. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  646. package/vendor/ink/build/screen-reader-update.d.ts +13 -0
  647. package/vendor/ink/build/screen-reader-update.js +38 -0
  648. package/vendor/ink/build/screen-reader-update.js.map +1 -0
  649. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  650. package/vendor/ink/build/squash-text-nodes.js +36 -0
  651. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  652. package/vendor/ink/build/styles.d.ts +240 -0
  653. package/vendor/ink/build/styles.js +232 -0
  654. package/vendor/ink/build/styles.js.map +1 -0
  655. package/vendor/ink/build/utils.d.ts +2 -0
  656. package/vendor/ink/build/utils.js +4 -0
  657. package/vendor/ink/build/utils.js.map +1 -0
  658. package/vendor/ink/build/wrap-text.d.ts +3 -0
  659. package/vendor/ink/build/wrap-text.js +31 -0
  660. package/vendor/ink/build/wrap-text.js.map +1 -0
  661. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  662. package/vendor/ink/build/write-synchronized.js +7 -0
  663. package/vendor/ink/build/write-synchronized.js.map +1 -0
  664. package/vendor/ink/license +10 -0
  665. package/vendor/ink/node_modules/@types/node/LICENSE +21 -0
  666. package/vendor/ink/node_modules/@types/node/README.md +15 -0
  667. package/vendor/ink/node_modules/@types/node/assert/strict.d.ts +105 -0
  668. package/vendor/ink/node_modules/@types/node/assert.d.ts +955 -0
  669. package/vendor/ink/node_modules/@types/node/async_hooks.d.ts +623 -0
  670. package/vendor/ink/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  671. package/vendor/ink/node_modules/@types/node/buffer.d.ts +1810 -0
  672. package/vendor/ink/node_modules/@types/node/child_process.d.ts +1428 -0
  673. package/vendor/ink/node_modules/@types/node/cluster.d.ts +486 -0
  674. package/vendor/ink/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  675. package/vendor/ink/node_modules/@types/node/console.d.ts +151 -0
  676. package/vendor/ink/node_modules/@types/node/constants.d.ts +20 -0
  677. package/vendor/ink/node_modules/@types/node/crypto.d.ts +4065 -0
  678. package/vendor/ink/node_modules/@types/node/dgram.d.ts +564 -0
  679. package/vendor/ink/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  680. package/vendor/ink/node_modules/@types/node/dns/promises.d.ts +503 -0
  681. package/vendor/ink/node_modules/@types/node/dns.d.ts +922 -0
  682. package/vendor/ink/node_modules/@types/node/domain.d.ts +166 -0
  683. package/vendor/ink/node_modules/@types/node/events.d.ts +1054 -0
  684. package/vendor/ink/node_modules/@types/node/fs/promises.d.ts +1329 -0
  685. package/vendor/ink/node_modules/@types/node/fs.d.ts +4676 -0
  686. package/vendor/ink/node_modules/@types/node/globals.d.ts +150 -0
  687. package/vendor/ink/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  688. package/vendor/ink/node_modules/@types/node/http.d.ts +2167 -0
  689. package/vendor/ink/node_modules/@types/node/http2.d.ts +2480 -0
  690. package/vendor/ink/node_modules/@types/node/https.d.ts +405 -0
  691. package/vendor/ink/node_modules/@types/node/index.d.ts +115 -0
  692. package/vendor/ink/node_modules/@types/node/inspector/promises.d.ts +41 -0
  693. package/vendor/ink/node_modules/@types/node/inspector.d.ts +224 -0
  694. package/vendor/ink/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  695. package/vendor/ink/node_modules/@types/node/module.d.ts +819 -0
  696. package/vendor/ink/node_modules/@types/node/net.d.ts +933 -0
  697. package/vendor/ink/node_modules/@types/node/os.d.ts +507 -0
  698. package/vendor/ink/node_modules/@types/node/package.json +155 -0
  699. package/vendor/ink/node_modules/@types/node/path/posix.d.ts +8 -0
  700. package/vendor/ink/node_modules/@types/node/path/win32.d.ts +8 -0
  701. package/vendor/ink/node_modules/@types/node/path.d.ts +187 -0
  702. package/vendor/ink/node_modules/@types/node/perf_hooks.d.ts +643 -0
  703. package/vendor/ink/node_modules/@types/node/process.d.ts +2156 -0
  704. package/vendor/ink/node_modules/@types/node/punycode.d.ts +117 -0
  705. package/vendor/ink/node_modules/@types/node/querystring.d.ts +152 -0
  706. package/vendor/ink/node_modules/@types/node/quic.d.ts +910 -0
  707. package/vendor/ink/node_modules/@types/node/readline/promises.d.ts +161 -0
  708. package/vendor/ink/node_modules/@types/node/readline.d.ts +541 -0
  709. package/vendor/ink/node_modules/@types/node/repl.d.ts +415 -0
  710. package/vendor/ink/node_modules/@types/node/sea.d.ts +162 -0
  711. package/vendor/ink/node_modules/@types/node/sqlite.d.ts +955 -0
  712. package/vendor/ink/node_modules/@types/node/stream/consumers.d.ts +38 -0
  713. package/vendor/ink/node_modules/@types/node/stream/promises.d.ts +211 -0
  714. package/vendor/ink/node_modules/@types/node/stream/web.d.ts +296 -0
  715. package/vendor/ink/node_modules/@types/node/stream.d.ts +1760 -0
  716. package/vendor/ink/node_modules/@types/node/string_decoder.d.ts +67 -0
  717. package/vendor/ink/node_modules/@types/node/test/reporters.d.ts +96 -0
  718. package/vendor/ink/node_modules/@types/node/test.d.ts +2240 -0
  719. package/vendor/ink/node_modules/@types/node/timers/promises.d.ts +108 -0
  720. package/vendor/ink/node_modules/@types/node/timers.d.ts +159 -0
  721. package/vendor/ink/node_modules/@types/node/tls.d.ts +1198 -0
  722. package/vendor/ink/node_modules/@types/node/trace_events.d.ts +197 -0
  723. package/vendor/ink/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  724. package/vendor/ink/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  725. package/vendor/ink/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  726. package/vendor/ink/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  727. package/vendor/ink/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  728. package/vendor/ink/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  729. package/vendor/ink/node_modules/@types/node/tty.d.ts +250 -0
  730. package/vendor/ink/node_modules/@types/node/url.d.ts +519 -0
  731. package/vendor/ink/node_modules/@types/node/util/types.d.ts +558 -0
  732. package/vendor/ink/node_modules/@types/node/util.d.ts +1662 -0
  733. package/vendor/ink/node_modules/@types/node/v8.d.ts +983 -0
  734. package/vendor/ink/node_modules/@types/node/vm.d.ts +1208 -0
  735. package/vendor/ink/node_modules/@types/node/wasi.d.ts +202 -0
  736. package/vendor/ink/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  737. package/vendor/ink/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  738. package/vendor/ink/node_modules/@types/node/web-globals/console.d.ts +9 -0
  739. package/vendor/ink/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  740. package/vendor/ink/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  741. package/vendor/ink/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  742. package/vendor/ink/node_modules/@types/node/web-globals/events.d.ts +106 -0
  743. package/vendor/ink/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
  744. package/vendor/ink/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  745. package/vendor/ink/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  746. package/vendor/ink/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  747. package/vendor/ink/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  748. package/vendor/ink/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  749. package/vendor/ink/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  750. package/vendor/ink/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  751. package/vendor/ink/node_modules/@types/node/web-globals/url.d.ts +24 -0
  752. package/vendor/ink/node_modules/@types/node/worker_threads.d.ts +717 -0
  753. package/vendor/ink/node_modules/@types/node/zlib.d.ts +618 -0
  754. package/vendor/ink/node_modules/node-pty/LICENSE +69 -0
  755. package/vendor/ink/node_modules/node-pty/README.md +164 -0
  756. package/vendor/ink/node_modules/node-pty/binding.gyp +150 -0
  757. package/vendor/ink/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  758. package/vendor/ink/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  759. package/vendor/ink/node_modules/node-pty/lib/index.js +52 -0
  760. package/vendor/ink/node_modules/node-pty/lib/interfaces.js +7 -0
  761. package/vendor/ink/node_modules/node-pty/lib/shared/conout.js +11 -0
  762. package/vendor/ink/node_modules/node-pty/lib/terminal.js +190 -0
  763. package/vendor/ink/node_modules/node-pty/lib/types.js +7 -0
  764. package/vendor/ink/node_modules/node-pty/lib/unixTerminal.js +349 -0
  765. package/vendor/ink/node_modules/node-pty/lib/utils.js +39 -0
  766. package/vendor/ink/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  767. package/vendor/ink/node_modules/node-pty/lib/windowsPtyAgent.js +287 -0
  768. package/vendor/ink/node_modules/node-pty/lib/windowsTerminal.js +201 -0
  769. package/vendor/ink/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  770. package/vendor/ink/node_modules/node-pty/package.json +65 -0
  771. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  772. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  773. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  774. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  775. package/vendor/ink/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  776. package/vendor/ink/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  777. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  778. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  779. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  780. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  781. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  782. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  783. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  784. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  785. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  786. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  787. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  788. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  789. package/vendor/ink/node_modules/node-pty/scripts/post-install.js +76 -0
  790. package/vendor/ink/node_modules/node-pty/scripts/prebuild.js +34 -0
  791. package/vendor/ink/node_modules/node-pty/src/unix/pty.cc +875 -0
  792. package/vendor/ink/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  793. package/vendor/ink/node_modules/node-pty/src/win/conpty.cc +582 -0
  794. package/vendor/ink/node_modules/node-pty/src/win/conpty.h +41 -0
  795. package/vendor/ink/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  796. package/vendor/ink/node_modules/node-pty/src/win/path_util.cc +95 -0
  797. package/vendor/ink/node_modules/node-pty/src/win/path_util.h +26 -0
  798. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  799. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  800. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  801. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  802. package/vendor/ink/node_modules/node-pty/typings/node-pty.d.ts +215 -0
  803. package/vendor/ink/node_modules/undici-types/LICENSE +21 -0
  804. package/vendor/ink/node_modules/undici-types/README.md +6 -0
  805. package/vendor/ink/node_modules/undici-types/agent.d.ts +32 -0
  806. package/vendor/ink/node_modules/undici-types/api.d.ts +43 -0
  807. package/vendor/ink/node_modules/undici-types/balanced-pool.d.ts +30 -0
  808. package/vendor/ink/node_modules/undici-types/cache-interceptor.d.ts +173 -0
  809. package/vendor/ink/node_modules/undici-types/cache.d.ts +36 -0
  810. package/vendor/ink/node_modules/undici-types/client-stats.d.ts +15 -0
  811. package/vendor/ink/node_modules/undici-types/client.d.ts +108 -0
  812. package/vendor/ink/node_modules/undici-types/connector.d.ts +34 -0
  813. package/vendor/ink/node_modules/undici-types/content-type.d.ts +21 -0
  814. package/vendor/ink/node_modules/undici-types/cookies.d.ts +30 -0
  815. package/vendor/ink/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  816. package/vendor/ink/node_modules/undici-types/dispatcher.d.ts +276 -0
  817. package/vendor/ink/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  818. package/vendor/ink/node_modules/undici-types/errors.d.ts +161 -0
  819. package/vendor/ink/node_modules/undici-types/eventsource.d.ts +66 -0
  820. package/vendor/ink/node_modules/undici-types/fetch.d.ts +211 -0
  821. package/vendor/ink/node_modules/undici-types/formdata.d.ts +108 -0
  822. package/vendor/ink/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  823. package/vendor/ink/node_modules/undici-types/global-origin.d.ts +7 -0
  824. package/vendor/ink/node_modules/undici-types/h2c-client.d.ts +73 -0
  825. package/vendor/ink/node_modules/undici-types/handlers.d.ts +15 -0
  826. package/vendor/ink/node_modules/undici-types/header.d.ts +160 -0
  827. package/vendor/ink/node_modules/undici-types/index.d.ts +88 -0
  828. package/vendor/ink/node_modules/undici-types/interceptors.d.ts +73 -0
  829. package/vendor/ink/node_modules/undici-types/mock-agent.d.ts +68 -0
  830. package/vendor/ink/node_modules/undici-types/mock-call-history.d.ts +111 -0
  831. package/vendor/ink/node_modules/undici-types/mock-client.d.ts +27 -0
  832. package/vendor/ink/node_modules/undici-types/mock-errors.d.ts +12 -0
  833. package/vendor/ink/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  834. package/vendor/ink/node_modules/undici-types/mock-pool.d.ts +27 -0
  835. package/vendor/ink/node_modules/undici-types/package.json +55 -0
  836. package/vendor/ink/node_modules/undici-types/patch.d.ts +29 -0
  837. package/vendor/ink/node_modules/undici-types/pool-stats.d.ts +19 -0
  838. package/vendor/ink/node_modules/undici-types/pool.d.ts +41 -0
  839. package/vendor/ink/node_modules/undici-types/proxy-agent.d.ts +29 -0
  840. package/vendor/ink/node_modules/undici-types/readable.d.ts +68 -0
  841. package/vendor/ink/node_modules/undici-types/retry-agent.d.ts +8 -0
  842. package/vendor/ink/node_modules/undici-types/retry-handler.d.ts +125 -0
  843. package/vendor/ink/node_modules/undici-types/round-robin-pool.d.ts +41 -0
  844. package/vendor/ink/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  845. package/vendor/ink/node_modules/undici-types/util.d.ts +18 -0
  846. package/vendor/ink/node_modules/undici-types/utility.d.ts +7 -0
  847. package/vendor/ink/node_modules/undici-types/webidl.d.ts +341 -0
  848. package/vendor/ink/node_modules/undici-types/websocket.d.ts +186 -0
  849. package/vendor/ink/package.json +201 -0
  850. package/vendor/ink/readme.md +2636 -0
  851. package/bin/swag-agent.js +0 -9
  852. package/dist/server/lib/pg-rate-limiter.d.ts +0 -21
  853. package/dist/server/lib/pg-rate-limiter.js +0 -86
@@ -7,986 +7,1683 @@
7
7
  // Step execution, inline chains, cron/schedule processing, event triggers,
8
8
  // webhook ingestion, circuit breakers, and all step-type executors live in
9
9
  // ./workflow-steps.ts.
10
+
10
11
  import { randomUUID } from "node:crypto";
11
12
  // Re-export everything from workflow-steps so existing imports from workflows.ts still work
12
- export {
13
+ export {
13
14
  // Injected executor setters
14
- setToolExecutor, setAgentExecutor, setTokenBroadcaster, setStepErrorBroadcaster,
15
+ setToolExecutor, setAgentExecutor, setTokenBroadcaster, setStepErrorBroadcaster,
15
16
  // Core engine
16
- processWorkflowSteps, processWaitingSteps, executeAndAdvance, executeInlineChain,
17
+ processWorkflowSteps, processWaitingSteps, executeAndAdvance, executeInlineChain,
17
18
  // Guest approval
18
- generateGuestApprovalUrl, verifyGuestApprovalSignature,
19
+ generateGuestApprovalUrl, verifyGuestApprovalSignature,
19
20
  // Schedule / timeout / events
20
- processScheduleTriggers, enforceWorkflowTimeouts, processEventTriggers,
21
+ processScheduleTriggers, enforceWorkflowTimeouts, processEventTriggers,
21
22
  // Resilience
22
- cleanupOrphanedSteps, processDlqRetries,
23
+ cleanupOrphanedSteps, processDlqRetries,
23
24
  // Webhook ingestion
24
- handleWebhookIngestion,
25
+ handleWebhookIngestion,
25
26
  // Worker pool management
26
- initWorkerPool, getPoolStats, shutdownPool,
27
+ initWorkerPool, getPoolStats, shutdownPool,
27
28
  // Cron parser (used by CRUD)
28
- getNextCronTime,
29
+ getNextCronTime,
29
30
  // Event journal (used by CRUD replay)
30
- logWorkflowEvent,
31
+ logWorkflowEvent
32
+ // Types
33
+ ,
31
34
  // Run completion (used by CRUD start/cancel)
32
- completeWorkflowRun, } from "./workflow-steps.js";
33
- import { executeInlineChain, getNextCronTime, completeWorkflowRun, logWorkflowEvent, } from "./workflow-steps.js";
35
+ completeWorkflowRun } from "./workflow-steps.js";
36
+ import { executeInlineChain, getNextCronTime, completeWorkflowRun, logWorkflowEvent } from "./workflow-steps.js";
37
+
34
38
  // ============================================================================
35
39
  // CRUD HANDLER — MCP tool interface
36
40
  // ============================================================================
41
+
37
42
  export async function handleWorkflows(supabase, args, storeId) {
38
- const action = args.action;
39
- const sid = storeId;
40
- switch (action) {
41
- case "list": {
42
- let q = supabase.from("workflows")
43
- .select("id, name, description, icon, status, is_active, trigger_type, max_concurrent_runs, version, last_run_at, created_at, circuit_breaker_state")
44
- .eq("store_id", sid).order("created_at", { ascending: false });
45
- if (args.status)
46
- q = q.eq("status", args.status);
47
- if (args.trigger_type)
48
- q = q.eq("trigger_type", args.trigger_type);
49
- const { data, error } = await q.limit(args.limit || 50);
50
- return error ? { success: false, error: error.message } : { success: true, data };
43
+ const action = args.action;
44
+ const sid = storeId;
45
+ switch (action) {
46
+ case "list":
47
+ {
48
+ let q = supabase.from("workflows").select("id, name, description, icon, status, is_active, trigger_type, max_concurrent_runs, version, last_run_at, created_at, circuit_breaker_state").eq("store_id", sid).order("created_at", {
49
+ ascending: false
50
+ });
51
+ if (args.status) q = q.eq("status", args.status);
52
+ if (args.trigger_type) q = q.eq("trigger_type", args.trigger_type);
53
+ const {
54
+ data,
55
+ error
56
+ } = await q.limit(args.limit || 50);
57
+ return error ? {
58
+ success: false,
59
+ error: error.message
60
+ } : {
61
+ success: true,
62
+ data
63
+ };
64
+ }
65
+ case "get":
66
+ {
67
+ const {
68
+ data: wf,
69
+ error
70
+ } = await supabase.from("workflows").select("*, workflow_steps(*)").eq("id", args.workflow_id).eq("store_id", sid).single();
71
+ if (error) return {
72
+ success: false,
73
+ error: error.message
74
+ };
75
+ const {
76
+ data: runs
77
+ } = await supabase.from("workflow_runs").select("id, status, trigger_type, started_at, completed_at, duration_ms, error_message, error_step_key").eq("workflow_id", args.workflow_id).order("created_at", {
78
+ ascending: false
79
+ }).limit(10);
80
+ return {
81
+ success: true,
82
+ data: {
83
+ ...wf,
84
+ recent_runs: runs || []
85
+ }
86
+ };
87
+ }
88
+ case "create":
89
+ {
90
+ // Compute next_run_at from cron expression — check both top-level and trigger_config
91
+ const tc = args.trigger_config;
92
+ const cronExpr = args.cron_expression || tc?.cron || tc?.cron_expression || null;
93
+ let nextRunAt = null;
94
+ if (cronExpr) {
95
+ const next = getNextCronTime(cronExpr);
96
+ if (!next) return {
97
+ success: false,
98
+ error: `Invalid cron expression: ${cronExpr}`
99
+ };
100
+ nextRunAt = next.toISOString();
51
101
  }
52
- case "get": {
53
- const { data: wf, error } = await supabase.from("workflows")
54
- .select("*, workflow_steps(*)").eq("id", args.workflow_id).eq("store_id", sid).single();
55
- if (error)
56
- return { success: false, error: error.message };
57
- const { data: runs } = await supabase.from("workflow_runs")
58
- .select("id, status, trigger_type, started_at, completed_at, duration_ms, error_message, error_step_key")
59
- .eq("workflow_id", args.workflow_id).order("created_at", { ascending: false }).limit(10);
60
- return { success: true, data: { ...wf, recent_runs: runs || [] } };
102
+ // Auto-extract timezone from trigger_config if not top-level
103
+ const tz = args.timezone || tc?.timezone || "UTC";
104
+ const {
105
+ data,
106
+ error
107
+ } = await supabase.from("workflows").insert({
108
+ store_id: sid,
109
+ name: args.name,
110
+ description: args.description || null,
111
+ icon: args.icon || null,
112
+ status: args.status || "draft",
113
+ is_active: args.status === "active",
114
+ trigger_type: args.trigger_type || (cronExpr ? "schedule" : "manual"),
115
+ trigger_config: args.trigger_config || {},
116
+ max_concurrent_runs: args.max_concurrent_runs || 1,
117
+ max_run_duration_seconds: args.max_run_duration_seconds || 3600,
118
+ max_steps_per_run: args.max_steps_per_run || 50,
119
+ max_retries_per_step: args.max_retries_per_step || 3,
120
+ on_error_webhook_url: args.on_error_webhook_url || null,
121
+ on_error_email: args.on_error_email || null,
122
+ cron_expression: cronExpr,
123
+ next_run_at: nextRunAt,
124
+ timezone: tz,
125
+ multitask_strategy: args.multitask_strategy || "allow"
126
+ }).select().single();
127
+ if (error) return {
128
+ success: false,
129
+ error: error.message
130
+ };
131
+ if (Array.isArray(args.steps)) {
132
+ const steps = args.steps.map((s, i) => ({
133
+ workflow_id: data.id,
134
+ step_key: s.step_key,
135
+ step_type: s.step_type,
136
+ is_entry_point: s.is_entry_point ?? i === 0,
137
+ on_success: s.on_success || null,
138
+ on_failure: s.on_failure || null,
139
+ step_config: s.step_config || {},
140
+ max_retries: s.max_retries || 3,
141
+ retry_delay_seconds: s.retry_delay_seconds || 10,
142
+ timeout_seconds: s.timeout_seconds || 60,
143
+ input_schema: s.input_schema || null,
144
+ position_x: s.position_x || 0,
145
+ position_y: s.position_y || i * 100
146
+ }));
147
+ const {
148
+ error: stepsErr
149
+ } = await supabase.from("workflow_steps").insert(steps);
150
+ if (stepsErr) return {
151
+ success: false,
152
+ error: `Workflow created but steps failed: ${stepsErr.message}`
153
+ };
61
154
  }
62
- case "create": {
63
- // Compute next_run_at from cron expression — check both top-level and trigger_config
64
- const tc = args.trigger_config;
65
- const cronExpr = args.cron_expression
66
- || tc?.cron || tc?.cron_expression || null;
67
- let nextRunAt = null;
68
- if (cronExpr) {
69
- const next = getNextCronTime(cronExpr);
70
- if (!next)
71
- return { success: false, error: `Invalid cron expression: ${cronExpr}` };
72
- nextRunAt = next.toISOString();
73
- }
74
- // Auto-extract timezone from trigger_config if not top-level
75
- const tz = args.timezone || tc?.timezone || "UTC";
76
- const { data, error } = await supabase.from("workflows").insert({
77
- store_id: sid,
78
- name: args.name,
79
- description: args.description || null,
80
- icon: args.icon || null,
81
- status: args.status || "draft",
82
- is_active: args.status === "active",
83
- trigger_type: args.trigger_type || (cronExpr ? "schedule" : "manual"),
84
- trigger_config: args.trigger_config || {},
85
- max_concurrent_runs: args.max_concurrent_runs || 1,
86
- max_run_duration_seconds: args.max_run_duration_seconds || 3600,
87
- max_steps_per_run: args.max_steps_per_run || 50,
88
- max_retries_per_step: args.max_retries_per_step || 3,
89
- on_error_webhook_url: args.on_error_webhook_url || null,
90
- on_error_email: args.on_error_email || null,
91
- cron_expression: cronExpr,
92
- next_run_at: nextRunAt,
93
- timezone: tz,
94
- multitask_strategy: args.multitask_strategy || "allow",
95
- }).select().single();
96
- if (error)
97
- return { success: false, error: error.message };
98
- if (Array.isArray(args.steps)) {
99
- const steps = args.steps.map((s, i) => ({
100
- workflow_id: data.id,
101
- step_key: s.step_key,
102
- step_type: s.step_type,
103
- is_entry_point: s.is_entry_point ?? (i === 0),
104
- on_success: s.on_success || null,
105
- on_failure: s.on_failure || null,
106
- step_config: s.step_config || {},
107
- max_retries: s.max_retries || 3,
108
- retry_delay_seconds: s.retry_delay_seconds || 10,
109
- timeout_seconds: s.timeout_seconds || 60,
110
- input_schema: s.input_schema || null,
111
- position_x: s.position_x || 0,
112
- position_y: s.position_y || i * 100,
113
- }));
114
- const { error: stepsErr } = await supabase.from("workflow_steps").insert(steps);
115
- if (stepsErr)
116
- return { success: false, error: `Workflow created but steps failed: ${stepsErr.message}` };
117
- }
118
- return { success: true, data };
155
+ return {
156
+ success: true,
157
+ data
158
+ };
159
+ }
160
+ case "update":
161
+ {
162
+ const updates = {};
163
+ const allowed = ["name", "description", "icon", "status", "trigger_type", "trigger_config", "max_concurrent_runs", "max_run_duration_seconds", "max_steps_per_run", "max_retries_per_step", "on_error_webhook_url", "on_error_email", "multitask_strategy", "timezone"];
164
+ for (const k of allowed) {
165
+ if (args[k] !== undefined) updates[k] = args[k];
119
166
  }
120
- case "update": {
121
- const updates = {};
122
- const allowed = ["name", "description", "icon", "status", "trigger_type", "trigger_config",
123
- "max_concurrent_runs", "max_run_duration_seconds", "max_steps_per_run", "max_retries_per_step",
124
- "on_error_webhook_url", "on_error_email", "multitask_strategy", "timezone"];
125
- for (const k of allowed) {
126
- if (args[k] !== undefined)
127
- updates[k] = args[k];
128
- }
129
- if (args.status !== undefined)
130
- updates.is_active = args.status === "active";
131
- // Handle cron_expression update — check both top-level and trigger_config
132
- const utc = args.trigger_config;
133
- const cronFromConfig = utc?.cron || utc?.cron_expression || null;
134
- const cronExplicit = args.cron_expression !== undefined ? args.cron_expression : null;
135
- const cronExpr = cronExplicit || cronFromConfig;
136
- if (cronExpr !== null && (args.cron_expression !== undefined || cronFromConfig)) {
137
- updates.cron_expression = cronExpr;
138
- const next = getNextCronTime(cronExpr);
139
- if (!next)
140
- return { success: false, error: `Invalid cron expression: ${cronExpr}` };
141
- updates.next_run_at = next.toISOString();
142
- // Auto-set trigger_type to schedule if cron is provided
143
- if (!updates.trigger_type)
144
- updates.trigger_type = "schedule";
145
- // Extract timezone from trigger_config if not top-level
146
- if (!updates.timezone && utc?.timezone)
147
- updates.timezone = utc.timezone;
148
- }
149
- else if (args.cron_expression === null) {
150
- // Explicitly clearing cron
151
- updates.cron_expression = null;
152
- updates.next_run_at = null;
153
- }
154
- const { data, error } = await supabase.from("workflows")
155
- .update(updates).eq("id", args.workflow_id).eq("store_id", sid).select().maybeSingle();
156
- if (error)
157
- return { success: false, error: error.message };
158
- if (!data)
159
- return { success: false, error: "Workflow not found or store mismatch" };
160
- return { success: true, data };
167
+ if (args.status !== undefined) updates.is_active = args.status === "active";
168
+
169
+ // Handle cron_expression update check both top-level and trigger_config
170
+ const utc = args.trigger_config;
171
+ const cronFromConfig = utc?.cron || utc?.cron_expression || null;
172
+ const cronExplicit = args.cron_expression !== undefined ? args.cron_expression : null;
173
+ const cronExpr = cronExplicit || cronFromConfig;
174
+ if (cronExpr !== null && (args.cron_expression !== undefined || cronFromConfig)) {
175
+ updates.cron_expression = cronExpr;
176
+ const next = getNextCronTime(cronExpr);
177
+ if (!next) return {
178
+ success: false,
179
+ error: `Invalid cron expression: ${cronExpr}`
180
+ };
181
+ updates.next_run_at = next.toISOString();
182
+ // Auto-set trigger_type to schedule if cron is provided
183
+ if (!updates.trigger_type) updates.trigger_type = "schedule";
184
+ // Extract timezone from trigger_config if not top-level
185
+ if (!updates.timezone && utc?.timezone) updates.timezone = utc.timezone;
186
+ } else if (args.cron_expression === null) {
187
+ // Explicitly clearing cron
188
+ updates.cron_expression = null;
189
+ updates.next_run_at = null;
161
190
  }
162
- case "delete": {
163
- const { error } = await supabase.from("workflows").delete()
164
- .eq("id", args.workflow_id).eq("store_id", sid);
165
- return error ? { success: false, error: error.message } : { success: true, data: { deleted: true } };
191
+ const {
192
+ data,
193
+ error
194
+ } = await supabase.from("workflows").update(updates).eq("id", args.workflow_id).eq("store_id", sid).select().maybeSingle();
195
+ if (error) return {
196
+ success: false,
197
+ error: error.message
198
+ };
199
+ if (!data) return {
200
+ success: false,
201
+ error: "Workflow not found or store mismatch"
202
+ };
203
+ return {
204
+ success: true,
205
+ data
206
+ };
207
+ }
208
+ case "delete":
209
+ {
210
+ const {
211
+ error
212
+ } = await supabase.from("workflows").delete().eq("id", args.workflow_id).eq("store_id", sid);
213
+ return error ? {
214
+ success: false,
215
+ error: error.message
216
+ } : {
217
+ success: true,
218
+ data: {
219
+ deleted: true
220
+ }
221
+ };
222
+ }
223
+ case "add_step":
224
+ {
225
+ // H9 FIX: Verify workflow belongs to this store before adding step
226
+ const {
227
+ data: wfCheck
228
+ } = await supabase.from("workflows").select("id").eq("id", args.workflow_id).eq("store_id", sid).single();
229
+ if (!wfCheck) return {
230
+ success: false,
231
+ error: "Workflow not found in this store"
232
+ };
233
+ const {
234
+ data,
235
+ error
236
+ } = await supabase.from("workflow_steps").insert({
237
+ workflow_id: args.workflow_id,
238
+ step_key: args.step_key,
239
+ step_type: args.step_type,
240
+ is_entry_point: args.is_entry_point ?? false,
241
+ on_success: args.on_success || null,
242
+ on_failure: args.on_failure || null,
243
+ step_config: args.step_config || {},
244
+ max_retries: args.max_retries || 3,
245
+ timeout_seconds: args.timeout_seconds || 60,
246
+ input_schema: args.input_schema || null
247
+ }).select().single();
248
+ return error ? {
249
+ success: false,
250
+ error: error.message
251
+ } : {
252
+ success: true,
253
+ data
254
+ };
255
+ }
256
+ case "update_step":
257
+ {
258
+ // H9 FIX: Verify step belongs to a workflow owned by this store
259
+ const {
260
+ data: stepCheck
261
+ } = await supabase.from("workflow_steps").select("id, workflow_id, workflows!inner(store_id)").eq("id", args.step_id).single();
262
+ if (!stepCheck || stepCheck.workflows?.store_id !== sid) {
263
+ return {
264
+ success: false,
265
+ error: "Step not found in this store's workflows"
266
+ };
166
267
  }
167
- case "add_step": {
168
- // H9 FIX: Verify workflow belongs to this store before adding step
169
- const { data: wfCheck } = await supabase.from("workflows")
170
- .select("id").eq("id", args.workflow_id).eq("store_id", sid).single();
171
- if (!wfCheck)
172
- return { success: false, error: "Workflow not found in this store" };
173
- const { data, error } = await supabase.from("workflow_steps").insert({
174
- workflow_id: args.workflow_id,
175
- step_key: args.step_key, step_type: args.step_type,
176
- is_entry_point: args.is_entry_point ?? false,
177
- on_success: args.on_success || null, on_failure: args.on_failure || null,
178
- step_config: args.step_config || {},
179
- max_retries: args.max_retries || 3,
180
- timeout_seconds: args.timeout_seconds || 60,
181
- input_schema: args.input_schema || null,
182
- }).select().single();
183
- return error ? { success: false, error: error.message } : { success: true, data };
268
+ const su = {};
269
+ for (const k of ["step_key", "step_type", "is_entry_point", "on_success", "on_failure", "step_config", "max_retries", "retry_delay_seconds", "timeout_seconds", "input_schema", "position_x", "position_y"]) {
270
+ if (args[k] !== undefined) {
271
+ // Treat empty string as null for nullable fields (on_success, on_failure)
272
+ su[k] = args[k] === "" && (k === "on_success" || k === "on_failure") ? null : args[k];
273
+ }
184
274
  }
185
- case "update_step": {
186
- // H9 FIX: Verify step belongs to a workflow owned by this store
187
- const { data: stepCheck } = await supabase.from("workflow_steps")
188
- .select("id, workflow_id, workflows!inner(store_id)")
189
- .eq("id", args.step_id).single();
190
- if (!stepCheck || stepCheck.workflows?.store_id !== sid) {
191
- return { success: false, error: "Step not found in this store's workflows" };
192
- }
193
- const su = {};
194
- for (const k of ["step_key", "step_type", "is_entry_point", "on_success", "on_failure",
195
- "step_config", "max_retries", "retry_delay_seconds", "timeout_seconds", "input_schema",
196
- "position_x", "position_y"]) {
197
- if (args[k] !== undefined) {
198
- // Treat empty string as null for nullable fields (on_success, on_failure)
199
- su[k] = (args[k] === "" && (k === "on_success" || k === "on_failure")) ? null : args[k];
200
- }
201
- }
202
- const { data, error } = await supabase.from("workflow_steps")
203
- .update(su).eq("id", args.step_id).select().single();
204
- return error ? { success: false, error: error.message } : { success: true, data };
275
+ const {
276
+ data,
277
+ error
278
+ } = await supabase.from("workflow_steps").update(su).eq("id", args.step_id).select().single();
279
+ return error ? {
280
+ success: false,
281
+ error: error.message
282
+ } : {
283
+ success: true,
284
+ data
285
+ };
286
+ }
287
+ case "delete_step":
288
+ {
289
+ // H9 FIX: Verify step belongs to a workflow owned by this store
290
+ const {
291
+ data: stepCheck
292
+ } = await supabase.from("workflow_steps").select("id, workflow_id, workflows!inner(store_id)").eq("id", args.step_id).single();
293
+ if (!stepCheck || stepCheck.workflows?.store_id !== sid) {
294
+ return {
295
+ success: false,
296
+ error: "Step not found in this store's workflows"
297
+ };
205
298
  }
206
- case "delete_step": {
207
- // H9 FIX: Verify step belongs to a workflow owned by this store
208
- const { data: stepCheck } = await supabase.from("workflow_steps")
209
- .select("id, workflow_id, workflows!inner(store_id)")
210
- .eq("id", args.step_id).single();
211
- if (!stepCheck || stepCheck.workflows?.store_id !== sid) {
212
- return { success: false, error: "Step not found in this store's workflows" };
213
- }
214
- const { error } = await supabase.from("workflow_steps").delete().eq("id", args.step_id);
215
- return error ? { success: false, error: error.message } : { success: true, data: { deleted: true } };
299
+ const {
300
+ error
301
+ } = await supabase.from("workflow_steps").delete().eq("id", args.step_id);
302
+ return error ? {
303
+ success: false,
304
+ error: error.message
305
+ } : {
306
+ success: true,
307
+ data: {
308
+ deleted: true
309
+ }
310
+ };
311
+ }
312
+ case "start":
313
+ {
314
+ const wfId = args.workflow_id;
315
+
316
+ // FIX 5: Validate idempotency key format if provided
317
+ if (args.idempotency_key) {
318
+ const key = String(args.idempotency_key);
319
+ if (key.length > 255 || !/^[a-zA-Z0-9._:\/-]+$/.test(key)) {
320
+ return {
321
+ success: false,
322
+ error: "Invalid idempotency_key: must be 1-255 alphanumeric characters (with . _ : / -)"
323
+ };
324
+ }
216
325
  }
217
- case "start": {
218
- const wfId = args.workflow_id;
219
- // FIX 5: Validate idempotency key format if provided
220
- if (args.idempotency_key) {
221
- const key = String(args.idempotency_key);
222
- if (key.length > 255 || !/^[a-zA-Z0-9._:\/-]+$/.test(key)) {
223
- return { success: false, error: "Invalid idempotency_key: must be 1-255 alphanumeric characters (with . _ : / -)" };
224
- }
225
- }
226
- // FIX 6: Reject oversized trigger payloads before storing
227
- if (args.trigger_payload) {
228
- const payloadStr = JSON.stringify(args.trigger_payload);
229
- if (payloadStr.length > 10_000_000) {
230
- return { success: false, error: "Trigger payload too large (max 10MB)" };
231
- }
232
- }
233
- // Auto-activate workflow if needed (start implies intent to run)
234
- await supabase.from("workflows").update({ is_active: true, status: "active" })
235
- .eq("id", wfId).eq("store_id", sid).in("status", ["draft", "paused"]);
236
- // Load workflow config for strategy + concurrency + versioning
237
- const { data: wfConfig } = await supabase.from("workflows")
238
- .select("multitask_strategy, published_version_id, max_concurrent_runs").eq("id", wfId).eq("store_id", sid).single();
239
- if (!wfConfig)
240
- return { success: false, error: "Workflow not found or access denied" };
241
- const strategy = wfConfig.multitask_strategy || "allow";
242
- // Concurrency is enforced atomically inside start_workflow_run RPC (FOR UPDATE lock)
243
- // No app-side check needed — the RPC handles the race-free count + insert
244
- if (strategy !== "allow") {
245
- // Check for in-flight runs
246
- const { data: activeRuns } = await supabase.from("workflow_runs")
247
- .select("id, status, created_at")
248
- .eq("workflow_id", wfId).eq("store_id", sid)
249
- .in("status", ["running", "pending"])
250
- .order("created_at", { ascending: false })
251
- .limit(10);
252
- if (activeRuns?.length) {
253
- switch (strategy) {
254
- case "reject":
255
- return { success: false, error: `Workflow already has ${activeRuns.length} active run(s). Strategy: reject concurrent runs.`, data: { strategy: "reject", active_runs: activeRuns.length } };
256
- case "enqueue":
257
- // Let it through — the run will be created and the worker picks it up in order
258
- // Set priority lower so existing runs finish first
259
- break;
260
- case "interrupt":
261
- // Cancel all active runs before starting new one
262
- for (const run of activeRuns) {
263
- await completeWorkflowRun(supabase, run.id, wfId, sid, "cancelled", "Interrupted by new run (multitask_strategy: interrupt)");
264
- }
265
- break;
266
- case "replace":
267
- // Cancel all active runs AND delete their step runs
268
- for (const run of activeRuns) {
269
- await completeWorkflowRun(supabase, run.id, wfId, sid, "cancelled", "Replaced by new run (multitask_strategy: replace)");
270
- }
271
- break;
272
- }
273
- }
274
- }
275
- const { data, error } = await supabase.rpc("start_workflow_run", {
276
- p_workflow_id: wfId, p_store_id: sid,
277
- p_trigger_type: args.trigger_type || "manual",
278
- p_trigger_payload: args.trigger_payload || {},
279
- p_idempotency_key: args.idempotency_key || null,
280
- });
281
- if (error)
282
- return { success: false, error: error.message };
283
- if (!data?.success)
284
- return { success: false, error: data?.error || "Failed" };
285
- // Generate trace_id for distributed tracing
286
- const traceId = data.run_id ? randomUUID() : undefined;
287
- // Set version_id, trace_id, priority
288
- if (data.run_id && !data.deduplicated) {
289
- const runUpdates = { trace_id: traceId };
290
- if (wfConfig?.published_version_id)
291
- runUpdates.version_id = wfConfig.published_version_id;
292
- if (strategy === "enqueue")
293
- runUpdates.priority = 3;
294
- await supabase.from("workflow_runs").update(runUpdates).eq("id", data.run_id);
295
- // Phase 1: Inline execution — execute first step immediately
296
- try {
297
- await executeInlineChain(supabase, data.run_id);
326
+
327
+ // FIX 6: Reject oversized trigger payloads before storing
328
+ if (args.trigger_payload) {
329
+ const payloadStr = JSON.stringify(args.trigger_payload);
330
+ if (payloadStr.length > 10_000_000) {
331
+ return {
332
+ success: false,
333
+ error: "Trigger payload too large (max 10MB)"
334
+ };
335
+ }
336
+ }
337
+
338
+ // Auto-activate workflow if needed (start implies intent to run)
339
+ await supabase.from("workflows").update({
340
+ is_active: true,
341
+ status: "active"
342
+ }).eq("id", wfId).eq("store_id", sid).in("status", ["draft", "paused"]);
343
+
344
+ // Load workflow config for strategy + concurrency + versioning
345
+ const {
346
+ data: wfConfig
347
+ } = await supabase.from("workflows").select("multitask_strategy, published_version_id, max_concurrent_runs").eq("id", wfId).eq("store_id", sid).single();
348
+ if (!wfConfig) return {
349
+ success: false,
350
+ error: "Workflow not found or access denied"
351
+ };
352
+ const strategy = wfConfig.multitask_strategy || "allow";
353
+
354
+ // Concurrency is enforced atomically inside start_workflow_run RPC (FOR UPDATE lock)
355
+ // No app-side check needed the RPC handles the race-free count + insert
356
+
357
+ if (strategy !== "allow") {
358
+ // Check for in-flight runs
359
+ const {
360
+ data: activeRuns
361
+ } = await supabase.from("workflow_runs").select("id, status, created_at").eq("workflow_id", wfId).eq("store_id", sid).in("status", ["running", "pending"]).order("created_at", {
362
+ ascending: false
363
+ }).limit(10);
364
+ if (activeRuns?.length) {
365
+ switch (strategy) {
366
+ case "reject":
367
+ return {
368
+ success: false,
369
+ error: `Workflow already has ${activeRuns.length} active run(s). Strategy: reject concurrent runs.`,
370
+ data: {
371
+ strategy: "reject",
372
+ active_runs: activeRuns.length
373
+ }
374
+ };
375
+ case "enqueue":
376
+ // Let it through the run will be created and the worker picks it up in order
377
+ // Set priority lower so existing runs finish first
378
+ break;
379
+ case "interrupt":
380
+ // Cancel all active runs before starting new one
381
+ for (const run of activeRuns) {
382
+ await completeWorkflowRun(supabase, run.id, wfId, sid, "cancelled", "Interrupted by new run (multitask_strategy: interrupt)");
298
383
  }
299
- catch (err) {
300
- console.error("[workflow-inline] Error in inline chain:", err.message);
301
- // Non-fatal worker will pick up remaining steps
384
+ break;
385
+ case "replace":
386
+ // Cancel all active runs AND delete their step runs
387
+ for (const run of activeRuns) {
388
+ await completeWorkflowRun(supabase, run.id, wfId, sid, "cancelled", "Replaced by new run (multitask_strategy: replace)");
302
389
  }
390
+ break;
303
391
  }
304
- return { success: true, data: { ...data, strategy, trace_id: traceId } };
305
- }
306
- case "pause": {
307
- const { error } = await supabase.from("workflow_runs").update({ status: "paused" })
308
- .eq("id", args.run_id).eq("store_id", sid).eq("status", "running");
309
- return error ? { success: false, error: error.message } : { success: true, data: { paused: true } };
310
- }
311
- case "resume": {
312
- const { error } = await supabase.from("workflow_runs").update({ status: "running" })
313
- .eq("id", args.run_id).eq("store_id", sid).eq("status", "paused");
314
- return error ? { success: false, error: error.message } : { success: true, data: { resumed: true } };
392
+ }
315
393
  }
316
- case "cancel": {
317
- const { data: run } = await supabase.from("workflow_runs")
318
- .select("workflow_id, store_id").eq("id", args.run_id).eq("store_id", sid).single();
319
- if (!run)
320
- return { success: false, error: "Run not found or access denied" };
321
- await completeWorkflowRun(supabase, args.run_id, run.workflow_id, run.store_id, "cancelled", "Cancelled by user");
322
- return { success: true, data: { cancelled: true } };
394
+ const {
395
+ data,
396
+ error
397
+ } = await supabase.rpc("start_workflow_run", {
398
+ p_workflow_id: wfId,
399
+ p_store_id: sid,
400
+ p_trigger_type: args.trigger_type || "manual",
401
+ p_trigger_payload: args.trigger_payload || {},
402
+ p_idempotency_key: args.idempotency_key || null
403
+ });
404
+ if (error) return {
405
+ success: false,
406
+ error: error.message
407
+ };
408
+ if (!data?.success) return {
409
+ success: false,
410
+ error: data?.error || "Failed"
411
+ };
412
+
413
+ // Generate trace_id for distributed tracing
414
+ const traceId = data.run_id ? randomUUID() : undefined;
415
+
416
+ // Set version_id, trace_id, priority
417
+ if (data.run_id && !data.deduplicated) {
418
+ const runUpdates = {
419
+ trace_id: traceId
420
+ };
421
+ if (wfConfig?.published_version_id) runUpdates.version_id = wfConfig.published_version_id;
422
+ if (strategy === "enqueue") runUpdates.priority = 3;
423
+ await supabase.from("workflow_runs").update(runUpdates).eq("id", data.run_id);
424
+
425
+ // Phase 1: Inline execution — execute first step immediately
426
+ try {
427
+ await executeInlineChain(supabase, data.run_id);
428
+ } catch (err) {
429
+ console.error("[workflow-inline] Error in inline chain:", err.message);
430
+ // Non-fatal — worker will pick up remaining steps
431
+ }
323
432
  }
324
- case "reset_circuit_breaker": {
325
- const { data, error } = await supabase.from("workflows")
326
- .update({ circuit_breaker_state: "closed", circuit_breaker_failures: 0 })
327
- .eq("id", args.workflow_id).eq("store_id", sid).select().single();
328
- if (error)
329
- return { success: false, error: error.message };
330
- if (!data)
331
- return { success: false, error: "Workflow not found" };
332
- return { success: true, data: { reset: true, workflow_id: data.id } };
433
+ return {
434
+ success: true,
435
+ data: {
436
+ ...data,
437
+ strategy,
438
+ trace_id: traceId
439
+ }
440
+ };
441
+ }
442
+ case "pause":
443
+ {
444
+ const {
445
+ error
446
+ } = await supabase.from("workflow_runs").update({
447
+ status: "paused"
448
+ }).eq("id", args.run_id).eq("store_id", sid).eq("status", "running");
449
+ return error ? {
450
+ success: false,
451
+ error: error.message
452
+ } : {
453
+ success: true,
454
+ data: {
455
+ paused: true
456
+ }
457
+ };
458
+ }
459
+ case "resume":
460
+ {
461
+ const {
462
+ error
463
+ } = await supabase.from("workflow_runs").update({
464
+ status: "running"
465
+ }).eq("id", args.run_id).eq("store_id", sid).eq("status", "paused");
466
+ return error ? {
467
+ success: false,
468
+ error: error.message
469
+ } : {
470
+ success: true,
471
+ data: {
472
+ resumed: true
473
+ }
474
+ };
475
+ }
476
+ case "cancel":
477
+ {
478
+ const {
479
+ data: run
480
+ } = await supabase.from("workflow_runs").select("workflow_id, store_id").eq("id", args.run_id).eq("store_id", sid).single();
481
+ if (!run) return {
482
+ success: false,
483
+ error: "Run not found or access denied"
484
+ };
485
+ await completeWorkflowRun(supabase, args.run_id, run.workflow_id, run.store_id, "cancelled", "Cancelled by user");
486
+ return {
487
+ success: true,
488
+ data: {
489
+ cancelled: true
490
+ }
491
+ };
492
+ }
493
+ case "reset_circuit_breaker":
494
+ {
495
+ const {
496
+ data,
497
+ error
498
+ } = await supabase.from("workflows").update({
499
+ circuit_breaker_state: "closed",
500
+ circuit_breaker_failures: 0
501
+ }).eq("id", args.workflow_id).eq("store_id", sid).select().single();
502
+ if (error) return {
503
+ success: false,
504
+ error: error.message
505
+ };
506
+ if (!data) return {
507
+ success: false,
508
+ error: "Workflow not found"
509
+ };
510
+ return {
511
+ success: true,
512
+ data: {
513
+ reset: true,
514
+ workflow_id: data.id
515
+ }
516
+ };
517
+ }
518
+ case "runs":
519
+ {
520
+ let q = supabase.from("workflow_runs").select("id, workflow_id, status, trigger_type, trigger_payload, current_step_key, error_message, error_step_key, started_at, completed_at, duration_ms, created_at").eq("store_id", sid).order("created_at", {
521
+ ascending: false
522
+ });
523
+ if (args.workflow_id) q = q.eq("workflow_id", args.workflow_id);
524
+ if (args.status) q = q.eq("status", args.status);
525
+ const {
526
+ data,
527
+ error
528
+ } = await q.limit(args.limit || 25);
529
+ return error ? {
530
+ success: false,
531
+ error: error.message
532
+ } : {
533
+ success: true,
534
+ data
535
+ };
536
+ }
537
+ case "step_runs":
538
+ {
539
+ // IDOR FIX: Verify the run belongs to this store before querying step_runs
540
+ const {
541
+ data: runCheck
542
+ } = await supabase.from("workflow_runs").select("id").eq("id", args.run_id).eq("store_id", sid).single();
543
+ if (!runCheck) return {
544
+ success: false,
545
+ error: "Run not found or access denied"
546
+ };
547
+ const {
548
+ data,
549
+ error
550
+ } = await supabase.from("workflow_step_runs").select("id, step_key, step_type, status, input, output, error_message, attempt_count, started_at, completed_at, duration_ms, parent_step_run_id, child_run_id").eq("run_id", args.run_id).order("created_at", {
551
+ ascending: true
552
+ });
553
+ return error ? {
554
+ success: false,
555
+ error: error.message
556
+ } : {
557
+ success: true,
558
+ data
559
+ };
560
+ }
561
+ case "analytics":
562
+ {
563
+ const {
564
+ data,
565
+ error
566
+ } = await supabase.rpc("get_workflow_analytics", {
567
+ p_store_id: sid,
568
+ p_days: args.days || 30
569
+ });
570
+ return error ? {
571
+ success: false,
572
+ error: error.message
573
+ } : {
574
+ success: true,
575
+ data
576
+ };
577
+ }
578
+ case "create_webhook":
579
+ {
580
+ // P1 FIX: Ensure slug is globally unique to prevent cross-store webhook interception.
581
+ // handleWebhookIngestion queries by slug without store_id, so duplicate slugs across
582
+ // stores would cause one store to intercept another's webhooks.
583
+ const {
584
+ data: existingSlug
585
+ } = await supabase.from("webhook_endpoints").select("id").eq("slug", args.slug).eq("is_active", true).limit(1);
586
+ if (existingSlug?.length) {
587
+ return {
588
+ success: false,
589
+ error: "Webhook slug already in use. Choose a different slug."
590
+ };
333
591
  }
334
- case "runs": {
335
- let q = supabase.from("workflow_runs")
336
- .select("id, workflow_id, status, trigger_type, trigger_payload, current_step_key, error_message, error_step_key, started_at, completed_at, duration_ms, created_at")
337
- .eq("store_id", sid).order("created_at", { ascending: false });
338
- if (args.workflow_id)
339
- q = q.eq("workflow_id", args.workflow_id);
340
- if (args.status)
341
- q = q.eq("status", args.status);
342
- const { data, error } = await q.limit(args.limit || 25);
343
- return error ? { success: false, error: error.message } : { success: true, data };
592
+ const {
593
+ data,
594
+ error
595
+ } = await supabase.from("webhook_endpoints").insert({
596
+ store_id: sid,
597
+ name: args.name,
598
+ description: args.description || null,
599
+ slug: args.slug,
600
+ workflow_id: args.workflow_id,
601
+ verify_signature: args.verify_signature ?? true,
602
+ max_requests_per_minute: args.max_requests_per_minute || 60,
603
+ payload_transform: args.payload_transform || null,
604
+ sync_response: args.sync_response ?? false,
605
+ sync_timeout_seconds: args.sync_timeout_seconds || 30
606
+ }).select().single();
607
+ if (error) return {
608
+ success: false,
609
+ error: error.message
610
+ };
611
+ return {
612
+ success: true,
613
+ data: {
614
+ ...data,
615
+ webhook_url: `https://whale-agent.fly.dev/webhooks/${data.slug}`
616
+ }
617
+ };
618
+ }
619
+ case "list_webhooks":
620
+ {
621
+ const {
622
+ data,
623
+ error
624
+ } = await supabase.from("webhook_endpoints").select("id, name, description, slug, workflow_id, is_active, verify_signature, sync_response, last_received_at, total_received, created_at").eq("store_id", sid).order("created_at", {
625
+ ascending: false
626
+ });
627
+ return error ? {
628
+ success: false,
629
+ error: error.message
630
+ } : {
631
+ success: true,
632
+ data
633
+ };
634
+ }
635
+ case "delete_webhook":
636
+ {
637
+ const {
638
+ error
639
+ } = await supabase.from("webhook_endpoints").delete().eq("id", args.webhook_id).eq("store_id", sid);
640
+ return error ? {
641
+ success: false,
642
+ error: error.message
643
+ } : {
644
+ success: true,
645
+ data: {
646
+ deleted: true
647
+ }
648
+ };
649
+ }
650
+
651
+ // ================================================================
652
+ // PHASE 2: Approval actions
653
+ // ================================================================
654
+
655
+ case "list_approvals":
656
+ {
657
+ let q = supabase.from("workflow_approval_requests").select("id, run_id, step_run_id, workflow_id, title, description, prompt, options, form_schema, assigned_to, assigned_role, status, response_data, responded_by, responded_at, expires_at, timeout_action, created_at").eq("store_id", sid).order("created_at", {
658
+ ascending: false
659
+ });
660
+ if (args.status) q = q.eq("status", args.status);
661
+ if (args.workflow_id) q = q.eq("workflow_id", args.workflow_id);
662
+ if (args.run_id) q = q.eq("run_id", args.run_id);
663
+ const {
664
+ data,
665
+ error
666
+ } = await q.limit(args.limit || 25);
667
+ return error ? {
668
+ success: false,
669
+ error: error.message
670
+ } : {
671
+ success: true,
672
+ data
673
+ };
674
+ }
675
+ case "respond_approval":
676
+ {
677
+ if (!args.approval_id) return {
678
+ success: false,
679
+ error: "approval_id required"
680
+ };
681
+ if (!args.response_status) return {
682
+ success: false,
683
+ error: "response_status required (approved/rejected)"
684
+ };
685
+
686
+ // FIX 3: Check expiration before calling the RPC (defense in depth)
687
+ const {
688
+ data: approval,
689
+ error: approvalErr
690
+ } = await supabase.from("workflow_approval_requests").select("id, expires_at, status").eq("id", args.approval_id).eq("store_id", sid).single();
691
+ if (approvalErr || !approval) return {
692
+ success: false,
693
+ error: "Approval not found"
694
+ };
695
+ if (approval.status !== "pending") return {
696
+ success: false,
697
+ error: `Approval already ${approval.status}`
698
+ };
699
+ if (approval.expires_at && new Date(approval.expires_at) < new Date()) {
700
+ return {
701
+ success: false,
702
+ error: "Approval has expired"
703
+ };
344
704
  }
345
- case "step_runs": {
346
- // IDOR FIX: Verify the run belongs to this store before querying step_runs
347
- const { data: runCheck } = await supabase.from("workflow_runs")
348
- .select("id").eq("id", args.run_id).eq("store_id", sid).single();
349
- if (!runCheck)
350
- return { success: false, error: "Run not found or access denied" };
351
- const { data, error } = await supabase.from("workflow_step_runs")
352
- .select("id, step_key, step_type, status, input, output, error_message, attempt_count, started_at, completed_at, duration_ms, parent_step_run_id, child_run_id")
353
- .eq("run_id", args.run_id).order("created_at", { ascending: true });
354
- return error ? { success: false, error: error.message } : { success: true, data };
705
+ const {
706
+ data,
707
+ error
708
+ } = await supabase.rpc("respond_to_approval", {
709
+ p_approval_id: args.approval_id,
710
+ p_store_id: sid,
711
+ p_response: args.response_status,
712
+ p_response_data: args.response_data || {},
713
+ p_responded_by: args.responded_by || null
714
+ });
715
+ if (error) return {
716
+ success: false,
717
+ error: error.message
718
+ };
719
+ return data?.success ? {
720
+ success: true,
721
+ data
722
+ } : {
723
+ success: false,
724
+ error: data?.error || "Failed"
725
+ };
726
+ }
727
+
728
+ // ================================================================
729
+ // PHASE 4: Versioning actions
730
+ // ================================================================
731
+
732
+ case "publish":
733
+ {
734
+ if (!args.workflow_id) return {
735
+ success: false,
736
+ error: "workflow_id required"
737
+ };
738
+ const {
739
+ data,
740
+ error
741
+ } = await supabase.rpc("publish_workflow_version", {
742
+ p_workflow_id: args.workflow_id,
743
+ p_store_id: sid,
744
+ p_changelog: args.changelog || null,
745
+ p_published_by: args.published_by || null
746
+ });
747
+ if (error) return {
748
+ success: false,
749
+ error: error.message
750
+ };
751
+ if (!data?.success) return {
752
+ success: false,
753
+ error: data?.error || "Failed"
754
+ };
755
+ // Auto-activate on publish — publishing implies ready to run
756
+ await supabase.from("workflows").update({
757
+ is_active: true,
758
+ status: "active"
759
+ }).eq("id", args.workflow_id).eq("store_id", sid);
760
+ return {
761
+ success: true,
762
+ data
763
+ };
764
+ }
765
+ case "versions":
766
+ {
767
+ if (!args.workflow_id) return {
768
+ success: false,
769
+ error: "workflow_id required"
770
+ };
771
+ // IDOR FIX: Verify the workflow belongs to this store
772
+ const {
773
+ data: versionsWfCheck
774
+ } = await supabase.from("workflows").select("id").eq("id", args.workflow_id).eq("store_id", sid).single();
775
+ if (!versionsWfCheck) return {
776
+ success: false,
777
+ error: "Workflow not found or access denied"
778
+ };
779
+ const {
780
+ data,
781
+ error
782
+ } = await supabase.from("workflow_versions").select("id, version, name, description, trigger_type, published_by, published_at, changelog").eq("workflow_id", args.workflow_id).order("version", {
783
+ ascending: false
784
+ }).limit(args.limit || 25);
785
+ return error ? {
786
+ success: false,
787
+ error: error.message
788
+ } : {
789
+ success: true,
790
+ data
791
+ };
792
+ }
793
+ case "version_detail":
794
+ {
795
+ if (!args.version_id) return {
796
+ success: false,
797
+ error: "version_id required"
798
+ };
799
+ // Load full version row
800
+ const {
801
+ data: ver,
802
+ error: vErr
803
+ } = await supabase.from("workflow_versions").select("*").eq("id", args.version_id).single();
804
+ if (vErr || !ver) return {
805
+ success: false,
806
+ error: vErr?.message || "Version not found"
807
+ };
808
+ // IDOR FIX: Verify the version's workflow belongs to this store
809
+ const {
810
+ data: versionWfCheck
811
+ } = await supabase.from("workflows").select("id").eq("id", ver.workflow_id).eq("store_id", sid).single();
812
+ if (!versionWfCheck) return {
813
+ success: false,
814
+ error: "Version not found or access denied"
815
+ };
816
+ // Also load the workflow steps that existed at publish time
817
+ // (stored as a snapshot in the version row, or we load current steps for the workflow)
818
+ let steps = [];
819
+ if (ver.steps_snapshot && Array.isArray(ver.steps_snapshot)) {
820
+ steps = ver.steps_snapshot;
821
+ } else {
822
+ // Fallback: load current steps from workflow_steps table
823
+ const {
824
+ data: stepData
825
+ } = await supabase.from("workflow_steps").select("id, step_key, name, type, config, position_x, position_y, depends_on").eq("workflow_id", ver.workflow_id).order("step_order", {
826
+ ascending: true
827
+ });
828
+ steps = stepData || [];
355
829
  }
356
- case "analytics": {
357
- const { data, error } = await supabase.rpc("get_workflow_analytics", {
358
- p_store_id: sid, p_days: args.days || 30,
830
+ return {
831
+ success: true,
832
+ data: {
833
+ ...ver,
834
+ steps
835
+ }
836
+ };
837
+ }
838
+ case "rollback":
839
+ {
840
+ if (!args.workflow_id || !args.version_id) return {
841
+ success: false,
842
+ error: "workflow_id and version_id required"
843
+ };
844
+ // Verify workflow belongs to this store
845
+ const {
846
+ data: rbWfCheck
847
+ } = await supabase.from("workflows").select("id").eq("id", args.workflow_id).eq("store_id", sid).single();
848
+ if (!rbWfCheck) return {
849
+ success: false,
850
+ error: "Workflow not found or access denied"
851
+ };
852
+ // Verify version belongs to this workflow
853
+ const {
854
+ data: version,
855
+ error: verErr
856
+ } = await supabase.from("workflow_versions").select("id, version, steps_snapshot").eq("id", args.version_id).eq("workflow_id", args.workflow_id).single();
857
+ if (verErr || !version) return {
858
+ success: false,
859
+ error: "Version not found for this workflow"
860
+ };
861
+
862
+ // P1 FIX: Restore steps from snapshot — not just the version pointer
863
+ if (version.steps_snapshot && Array.isArray(version.steps_snapshot)) {
864
+ // Delete current steps
865
+ await supabase.from("workflow_steps").delete().eq("workflow_id", args.workflow_id);
866
+ // Upsert from snapshot
867
+ const stepsToInsert = version.steps_snapshot.map(s => ({
868
+ ...s,
869
+ workflow_id: args.workflow_id
870
+ }));
871
+ if (stepsToInsert.length > 0) {
872
+ await supabase.from("workflow_steps").upsert(stepsToInsert, {
873
+ onConflict: "id"
359
874
  });
360
- return error ? { success: false, error: error.message } : { success: true, data };
361
- }
362
- case "create_webhook": {
363
- // P1 FIX: Ensure slug is globally unique to prevent cross-store webhook interception.
364
- // handleWebhookIngestion queries by slug without store_id, so duplicate slugs across
365
- // stores would cause one store to intercept another's webhooks.
366
- const { data: existingSlug } = await supabase
367
- .from("webhook_endpoints")
368
- .select("id")
369
- .eq("slug", args.slug)
370
- .eq("is_active", true)
371
- .limit(1);
372
- if (existingSlug?.length) {
373
- return { success: false, error: "Webhook slug already in use. Choose a different slug." };
374
- }
375
- const { data, error } = await supabase.from("webhook_endpoints").insert({
376
- store_id: sid, name: args.name, description: args.description || null,
377
- slug: args.slug, workflow_id: args.workflow_id,
378
- verify_signature: args.verify_signature ?? true,
379
- max_requests_per_minute: args.max_requests_per_minute || 60,
380
- payload_transform: args.payload_transform || null,
381
- sync_response: args.sync_response ?? false,
382
- sync_timeout_seconds: args.sync_timeout_seconds || 30,
383
- }).select().single();
384
- if (error)
385
- return { success: false, error: error.message };
386
- return { success: true, data: { ...data, webhook_url: `https://whale-agent.fly.dev/webhooks/${data.slug}` } };
875
+ }
387
876
  }
388
- case "list_webhooks": {
389
- const { data, error } = await supabase.from("webhook_endpoints")
390
- .select("id, name, description, slug, workflow_id, is_active, verify_signature, sync_response, last_received_at, total_received, created_at")
391
- .eq("store_id", sid).order("created_at", { ascending: false });
392
- return error ? { success: false, error: error.message } : { success: true, data };
877
+ const {
878
+ error
879
+ } = await supabase.from("workflows").update({
880
+ published_version_id: version.id
881
+ }).eq("id", args.workflow_id).eq("store_id", sid);
882
+ return error ? {
883
+ success: false,
884
+ error: error.message
885
+ } : {
886
+ success: true,
887
+ data: {
888
+ rolled_back_to: version.version,
889
+ version_id: version.id,
890
+ steps_restored: !!version.steps_snapshot
891
+ }
892
+ };
893
+ }
894
+
895
+ // ================================================================
896
+ // PHASE 5: Template actions
897
+ // ================================================================
898
+
899
+ case "list_templates":
900
+ {
901
+ let q = supabase.from("workflows").select("id, name, description, icon, trigger_type, template_category, template_tags, clone_count, created_at").eq("is_template", true).order("clone_count", {
902
+ ascending: false
903
+ });
904
+ if (args.category) q = q.eq("template_category", args.category);
905
+ const {
906
+ data,
907
+ error
908
+ } = await q.limit(args.limit || 50);
909
+ return error ? {
910
+ success: false,
911
+ error: error.message
912
+ } : {
913
+ success: true,
914
+ data
915
+ };
916
+ }
917
+ case "clone_template":
918
+ {
919
+ if (!args.template_id) return {
920
+ success: false,
921
+ error: "template_id required"
922
+ };
923
+ const {
924
+ data,
925
+ error
926
+ } = await supabase.rpc("clone_workflow_template", {
927
+ p_template_id: args.template_id,
928
+ p_store_id: sid,
929
+ p_name: args.name || null
930
+ });
931
+ if (error) return {
932
+ success: false,
933
+ error: error.message
934
+ };
935
+ return data?.success ? {
936
+ success: true,
937
+ data
938
+ } : {
939
+ success: false,
940
+ error: data?.error || "Failed"
941
+ };
942
+ }
943
+
944
+ // ================================================================
945
+ // Checkpoint / replay — time-travel debugging
946
+ // ================================================================
947
+
948
+ case "checkpoints":
949
+ {
950
+ if (!args.run_id) return {
951
+ success: false,
952
+ error: "run_id required"
953
+ };
954
+ // IDOR FIX: Verify the run belongs to this store
955
+ const {
956
+ data: cpRunCheck
957
+ } = await supabase.from("workflow_runs").select("id").eq("id", args.run_id).eq("store_id", sid).single();
958
+ if (!cpRunCheck) return {
959
+ success: false,
960
+ error: "Run not found or access denied"
961
+ };
962
+ const {
963
+ data,
964
+ error
965
+ } = await supabase.from("workflow_checkpoints").select("id, step_key, step_run_id, sequence_number, created_at").eq("run_id", args.run_id).order("sequence_number", {
966
+ ascending: true
967
+ });
968
+ return error ? {
969
+ success: false,
970
+ error: error.message
971
+ } : {
972
+ success: true,
973
+ data
974
+ };
975
+ }
976
+ case "replay":
977
+ {
978
+ if (!args.run_id) return {
979
+ success: false,
980
+ error: "run_id required"
981
+ };
982
+ const fromStepKey = args.from_step;
983
+
984
+ // Load the checkpoint to replay from
985
+ let checkpointQuery = supabase.from("workflow_checkpoints").select("*").eq("run_id", args.run_id);
986
+ if (fromStepKey) {
987
+ checkpointQuery = checkpointQuery.eq("step_key", fromStepKey);
988
+ } else {
989
+ // Default: replay from last successful checkpoint
990
+ checkpointQuery = checkpointQuery.order("sequence_number", {
991
+ ascending: false
992
+ }).limit(1);
393
993
  }
394
- case "delete_webhook": {
395
- const { error } = await supabase.from("webhook_endpoints").delete()
396
- .eq("id", args.webhook_id).eq("store_id", sid);
397
- return error ? { success: false, error: error.message } : { success: true, data: { deleted: true } };
398
- }
399
- // ================================================================
400
- // PHASE 2: Approval actions
401
- // ================================================================
402
- case "list_approvals": {
403
- let q = supabase.from("workflow_approval_requests")
404
- .select("id, run_id, step_run_id, workflow_id, title, description, prompt, options, form_schema, assigned_to, assigned_role, status, response_data, responded_by, responded_at, expires_at, timeout_action, created_at")
405
- .eq("store_id", sid).order("created_at", { ascending: false });
406
- if (args.status)
407
- q = q.eq("status", args.status);
408
- if (args.workflow_id)
409
- q = q.eq("workflow_id", args.workflow_id);
410
- if (args.run_id)
411
- q = q.eq("run_id", args.run_id);
412
- const { data, error } = await q.limit(args.limit || 25);
413
- return error ? { success: false, error: error.message } : { success: true, data };
414
- }
415
- case "respond_approval": {
416
- if (!args.approval_id)
417
- return { success: false, error: "approval_id required" };
418
- if (!args.response_status)
419
- return { success: false, error: "response_status required (approved/rejected)" };
420
- // FIX 3: Check expiration before calling the RPC (defense in depth)
421
- const { data: approval, error: approvalErr } = await supabase.from("workflow_approval_requests")
422
- .select("id, expires_at, status")
423
- .eq("id", args.approval_id).eq("store_id", sid).single();
424
- if (approvalErr || !approval)
425
- return { success: false, error: "Approval not found" };
426
- if (approval.status !== "pending")
427
- return { success: false, error: `Approval already ${approval.status}` };
428
- if (approval.expires_at && new Date(approval.expires_at) < new Date()) {
429
- return { success: false, error: "Approval has expired" };
430
- }
431
- const { data, error } = await supabase.rpc("respond_to_approval", {
432
- p_approval_id: args.approval_id,
433
- p_store_id: sid,
434
- p_response: args.response_status,
435
- p_response_data: args.response_data || {},
436
- p_responded_by: args.responded_by || null,
994
+ const {
995
+ data: checkpoint
996
+ } = await checkpointQuery.single();
997
+ if (!checkpoint) return {
998
+ success: false,
999
+ error: "No checkpoint found"
1000
+ };
1001
+
1002
+ // Get original run's workflow (IDOR FIX: scope to store)
1003
+ const {
1004
+ data: origRun
1005
+ } = await supabase.from("workflow_runs").select("workflow_id, trigger_type, trigger_payload, version_id").eq("id", args.run_id).eq("store_id", sid).single();
1006
+ if (!origRun) return {
1007
+ success: false,
1008
+ error: "Original run not found"
1009
+ };
1010
+
1011
+ // Start a new run with checkpoint state pre-loaded
1012
+ const {
1013
+ data: newRun,
1014
+ error: startErr
1015
+ } = await supabase.rpc("start_workflow_run", {
1016
+ p_workflow_id: origRun.workflow_id,
1017
+ p_store_id: sid,
1018
+ p_trigger_type: "replay",
1019
+ p_trigger_payload: checkpoint.trigger_payload || origRun.trigger_payload || {},
1020
+ p_idempotency_key: null
1021
+ });
1022
+ if (startErr || !newRun?.success) return {
1023
+ success: false,
1024
+ error: startErr?.message || "Failed to start replay run"
1025
+ };
1026
+
1027
+ // Pre-load step_outputs from checkpoint
1028
+ await supabase.from("workflow_runs").update({
1029
+ step_outputs: checkpoint.step_outputs,
1030
+ version_id: origRun.version_id
1031
+ }).eq("id", newRun.run_id);
1032
+
1033
+ // Cancel the auto-created entry step runs (we'll create our own starting from the checkpoint step)
1034
+ await supabase.from("workflow_step_runs").update({
1035
+ status: "cancelled"
1036
+ }).eq("run_id", newRun.run_id).eq("status", "pending");
1037
+
1038
+ // Find what step comes AFTER the checkpoint step
1039
+ const {
1040
+ data: checkpointStepDef
1041
+ } = await supabase.from("workflow_steps").select("on_success").eq("workflow_id", origRun.workflow_id).eq("step_key", checkpoint.step_key).single();
1042
+ if (checkpointStepDef?.on_success) {
1043
+ // createNextStepRunByKey is internal to workflow-steps — use direct insert
1044
+ const {
1045
+ data: nextStepDef
1046
+ } = await supabase.from("workflow_steps").select("id, step_key, step_type, max_retries").eq("workflow_id", origRun.workflow_id).eq("step_key", checkpointStepDef.on_success).single();
1047
+ if (nextStepDef) {
1048
+ await supabase.from("workflow_step_runs").insert({
1049
+ run_id: newRun.run_id,
1050
+ step_id: nextStepDef.id,
1051
+ step_key: nextStepDef.step_key,
1052
+ step_type: nextStepDef.step_type,
1053
+ status: "pending",
1054
+ max_attempts: nextStepDef.max_retries ?? 3
437
1055
  });
438
- if (error)
439
- return { success: false, error: error.message };
440
- return data?.success ? { success: true, data } : { success: false, error: data?.error || "Failed" };
441
- }
442
- // ================================================================
443
- // PHASE 4: Versioning actions
444
- // ================================================================
445
- case "publish": {
446
- if (!args.workflow_id)
447
- return { success: false, error: "workflow_id required" };
448
- const { data, error } = await supabase.rpc("publish_workflow_version", {
449
- p_workflow_id: args.workflow_id,
450
- p_store_id: sid,
451
- p_changelog: args.changelog || null,
452
- p_published_by: args.published_by || null,
453
- });
454
- if (error)
455
- return { success: false, error: error.message };
456
- if (!data?.success)
457
- return { success: false, error: data?.error || "Failed" };
458
- // Auto-activate on publish — publishing implies ready to run
459
- await supabase.from("workflows").update({ is_active: true, status: "active" })
460
- .eq("id", args.workflow_id).eq("store_id", sid);
461
- return { success: true, data };
462
- }
463
- case "versions": {
464
- if (!args.workflow_id)
465
- return { success: false, error: "workflow_id required" };
466
- // IDOR FIX: Verify the workflow belongs to this store
467
- const { data: versionsWfCheck } = await supabase.from("workflows")
468
- .select("id").eq("id", args.workflow_id).eq("store_id", sid).single();
469
- if (!versionsWfCheck)
470
- return { success: false, error: "Workflow not found or access denied" };
471
- const { data, error } = await supabase.from("workflow_versions")
472
- .select("id, version, name, description, trigger_type, published_by, published_at, changelog")
473
- .eq("workflow_id", args.workflow_id)
474
- .order("version", { ascending: false })
475
- .limit(args.limit || 25);
476
- return error ? { success: false, error: error.message } : { success: true, data };
477
- }
478
- case "version_detail": {
479
- if (!args.version_id)
480
- return { success: false, error: "version_id required" };
481
- // Load full version row
482
- const { data: ver, error: vErr } = await supabase.from("workflow_versions")
483
- .select("*")
484
- .eq("id", args.version_id)
485
- .single();
486
- if (vErr || !ver)
487
- return { success: false, error: vErr?.message || "Version not found" };
488
- // IDOR FIX: Verify the version's workflow belongs to this store
489
- const { data: versionWfCheck } = await supabase.from("workflows")
490
- .select("id").eq("id", ver.workflow_id).eq("store_id", sid).single();
491
- if (!versionWfCheck)
492
- return { success: false, error: "Version not found or access denied" };
493
- // Also load the workflow steps that existed at publish time
494
- // (stored as a snapshot in the version row, or we load current steps for the workflow)
495
- let steps = [];
496
- if (ver.steps_snapshot && Array.isArray(ver.steps_snapshot)) {
497
- steps = ver.steps_snapshot;
498
- }
499
- else {
500
- // Fallback: load current steps from workflow_steps table
501
- const { data: stepData } = await supabase.from("workflow_steps")
502
- .select("id, step_key, name, type, config, position_x, position_y, depends_on")
503
- .eq("workflow_id", ver.workflow_id)
504
- .order("step_order", { ascending: true });
505
- steps = stepData || [];
506
- }
507
- return { success: true, data: { ...ver, steps } };
508
- }
509
- case "rollback": {
510
- if (!args.workflow_id || !args.version_id)
511
- return { success: false, error: "workflow_id and version_id required" };
512
- // Verify workflow belongs to this store
513
- const { data: rbWfCheck } = await supabase.from("workflows")
514
- .select("id").eq("id", args.workflow_id).eq("store_id", sid).single();
515
- if (!rbWfCheck)
516
- return { success: false, error: "Workflow not found or access denied" };
517
- // Verify version belongs to this workflow
518
- const { data: version, error: verErr } = await supabase.from("workflow_versions")
519
- .select("id, version, steps_snapshot").eq("id", args.version_id)
520
- .eq("workflow_id", args.workflow_id).single();
521
- if (verErr || !version)
522
- return { success: false, error: "Version not found for this workflow" };
523
- // P1 FIX: Restore steps from snapshot — not just the version pointer
524
- if (version.steps_snapshot && Array.isArray(version.steps_snapshot)) {
525
- // Delete current steps
526
- await supabase.from("workflow_steps")
527
- .delete().eq("workflow_id", args.workflow_id);
528
- // Upsert from snapshot
529
- const stepsToInsert = version.steps_snapshot.map((s) => ({
530
- ...s,
531
- workflow_id: args.workflow_id,
532
- }));
533
- if (stepsToInsert.length > 0) {
534
- await supabase.from("workflow_steps").upsert(stepsToInsert, { onConflict: "id" });
535
- }
536
- }
537
- const { error } = await supabase.from("workflows").update({
538
- published_version_id: version.id,
539
- }).eq("id", args.workflow_id).eq("store_id", sid);
540
- return error ? { success: false, error: error.message } : { success: true, data: { rolled_back_to: version.version, version_id: version.id, steps_restored: !!(version.steps_snapshot) } };
1056
+ }
1057
+ try {
1058
+ await executeInlineChain(supabase, newRun.run_id);
1059
+ } catch (err) {
1060
+ console.error("[workflow] Inline chain failed for replay run", newRun.run_id, ":", err.message);
1061
+ }
541
1062
  }
542
- // ================================================================
543
- // PHASE 5: Template actions
544
- // ================================================================
545
- case "list_templates": {
546
- let q = supabase.from("workflows")
547
- .select("id, name, description, icon, trigger_type, template_category, template_tags, clone_count, created_at")
548
- .eq("is_template", true).order("clone_count", { ascending: false });
549
- if (args.category)
550
- q = q.eq("template_category", args.category);
551
- const { data, error } = await q.limit(args.limit || 50);
552
- return error ? { success: false, error: error.message } : { success: true, data };
1063
+ logWorkflowEvent(supabase, newRun.run_id, "run_replayed", {
1064
+ original_run_id: args.run_id,
1065
+ from_step: checkpoint.step_key,
1066
+ checkpoint_id: checkpoint.id
1067
+ });
1068
+ return {
1069
+ success: true,
1070
+ data: {
1071
+ run_id: newRun.run_id,
1072
+ replayed_from: checkpoint.step_key,
1073
+ original_run_id: args.run_id
1074
+ }
1075
+ };
1076
+ }
1077
+
1078
+ // ================================================================
1079
+ // Event journal — time-travel debugging
1080
+ // ================================================================
1081
+
1082
+ case "events":
1083
+ {
1084
+ if (!args.run_id) return {
1085
+ success: false,
1086
+ error: "run_id required"
1087
+ };
1088
+ // IDOR FIX: Verify the run belongs to this store
1089
+ const {
1090
+ data: eventsRunCheck
1091
+ } = await supabase.from("workflow_runs").select("id").eq("id", args.run_id).eq("store_id", sid).single();
1092
+ if (!eventsRunCheck) return {
1093
+ success: false,
1094
+ error: "Run not found or access denied"
1095
+ };
1096
+ const {
1097
+ data,
1098
+ error
1099
+ } = await supabase.from("workflow_events").select("id, event_type, step_run_id, payload, created_at").eq("run_id", args.run_id).order("created_at", {
1100
+ ascending: true
1101
+ }).limit(args.limit || 200);
1102
+ return error ? {
1103
+ success: false,
1104
+ error: error.message
1105
+ } : {
1106
+ success: true,
1107
+ data
1108
+ };
1109
+ }
1110
+
1111
+ // ================================================================
1112
+ // Waitpoint actions
1113
+ // ================================================================
1114
+
1115
+ case "list_waitpoints":
1116
+ {
1117
+ let q = supabase.from("waitpoint_tokens").select("id, token, run_id, step_run_id, label, status, expires_at, completed_at, created_at").eq("store_id", sid).order("created_at", {
1118
+ ascending: false
1119
+ });
1120
+ if (args.run_id) q = q.eq("run_id", args.run_id);
1121
+ if (args.status) q = q.eq("status", args.status);
1122
+ const {
1123
+ data,
1124
+ error
1125
+ } = await q.limit(args.limit || 25);
1126
+ return error ? {
1127
+ success: false,
1128
+ error: error.message
1129
+ } : {
1130
+ success: true,
1131
+ data
1132
+ };
1133
+ }
1134
+ case "complete_waitpoint":
1135
+ {
1136
+ if (!args.token) return {
1137
+ success: false,
1138
+ error: "token required"
1139
+ };
1140
+ // Find the waitpoint
1141
+ const {
1142
+ data: wp,
1143
+ error: wpErr
1144
+ } = await supabase.from("waitpoint_tokens").select("id, run_id, step_run_id, store_id, expires_at, status").eq("token", args.token).eq("store_id", sid).single();
1145
+ if (wpErr || !wp) return {
1146
+ success: false,
1147
+ error: "Waitpoint token not found"
1148
+ };
1149
+ if (wp.status === "completed") return {
1150
+ success: false,
1151
+ error: "Waitpoint already completed"
1152
+ };
1153
+ if (wp.expires_at && new Date(wp.expires_at) < new Date()) return {
1154
+ success: false,
1155
+ error: "Waitpoint expired"
1156
+ };
1157
+ // Mark completed
1158
+ await supabase.from("waitpoint_tokens").update({
1159
+ status: "completed",
1160
+ completion_data: args.data || {},
1161
+ completed_at: new Date().toISOString()
1162
+ }).eq("id", wp.id);
1163
+ // Resume the waiting step
1164
+ await supabase.from("workflow_step_runs").update({
1165
+ status: "pending",
1166
+ input: {
1167
+ waitpoint_completed: true,
1168
+ waitpoint_data: args.data || {}
1169
+ }
1170
+ }).eq("id", wp.step_run_id).eq("status", "waiting");
1171
+ // Inline resume
1172
+ try {
1173
+ await executeInlineChain(supabase, wp.run_id);
1174
+ } catch (err) {
1175
+ console.error("[workflow] Inline chain failed after waitpoint:", err.message);
553
1176
  }
554
- case "clone_template": {
555
- if (!args.template_id)
556
- return { success: false, error: "template_id required" };
557
- const { data, error } = await supabase.rpc("clone_workflow_template", {
558
- p_template_id: args.template_id,
559
- p_store_id: sid,
560
- p_name: args.name || null,
561
- });
562
- if (error)
563
- return { success: false, error: error.message };
564
- return data?.success ? { success: true, data } : { success: false, error: data?.error || "Failed" };
1177
+ return {
1178
+ success: true,
1179
+ data: {
1180
+ completed: true,
1181
+ run_id: wp.run_id
1182
+ }
1183
+ };
1184
+ }
1185
+
1186
+ // ================================================================
1187
+ // Dead Letter Queue actions
1188
+ // ================================================================
1189
+
1190
+ case "dlq":
1191
+ {
1192
+ let q = supabase.from("workflow_dlq").select("id, workflow_id, workflow_name, run_id, error_message, error_step_key, trigger_type, status, run_duration_ms, created_at").eq("store_id", sid).order("created_at", {
1193
+ ascending: false
1194
+ });
1195
+ if (args.status) q = q.eq("status", args.status);
1196
+ if (args.workflow_id) q = q.eq("workflow_id", args.workflow_id);
1197
+ const {
1198
+ data,
1199
+ error
1200
+ } = await q.limit(args.limit || 25);
1201
+ return error ? {
1202
+ success: false,
1203
+ error: error.message
1204
+ } : {
1205
+ success: true,
1206
+ data
1207
+ };
1208
+ }
1209
+ case "dlq_retry":
1210
+ {
1211
+ if (!args.dlq_id) return {
1212
+ success: false,
1213
+ error: "dlq_id required"
1214
+ };
1215
+ const {
1216
+ data: dlqEntry
1217
+ } = await supabase.from("workflow_dlq").select("*").eq("id", args.dlq_id).eq("store_id", sid).single();
1218
+ if (!dlqEntry) return {
1219
+ success: false,
1220
+ error: "DLQ entry not found"
1221
+ };
1222
+ if (dlqEntry.status !== "pending") return {
1223
+ success: false,
1224
+ error: `DLQ entry already ${dlqEntry.status}`
1225
+ };
1226
+
1227
+ // FIX 1: Preserve version_id from the original failed run
1228
+ const {
1229
+ data: originalRun
1230
+ } = await supabase.from("workflow_runs").select("version_id").eq("id", dlqEntry.run_id).single();
1231
+
1232
+ // Start a fresh run of the same workflow with the same trigger
1233
+ const {
1234
+ data: result,
1235
+ error: startErr
1236
+ } = await supabase.rpc("start_workflow_run", {
1237
+ p_workflow_id: dlqEntry.workflow_id,
1238
+ p_store_id: sid,
1239
+ p_trigger_type: dlqEntry.trigger_type || "dlq_retry",
1240
+ p_trigger_payload: {
1241
+ ...(dlqEntry.trigger_payload || {}),
1242
+ dlq_retry: true,
1243
+ original_run_id: dlqEntry.run_id
1244
+ },
1245
+ p_idempotency_key: null
1246
+ });
1247
+ if (startErr || !result?.success) return {
1248
+ success: false,
1249
+ error: startErr?.message || result?.error || "Retry failed"
1250
+ };
1251
+
1252
+ // Set version_id from the original failed run (same pattern as "start" action)
1253
+ if (result.run_id && !result.deduplicated && originalRun?.version_id) {
1254
+ await supabase.from("workflow_runs").update({
1255
+ version_id: originalRun.version_id
1256
+ }).eq("id", result.run_id);
565
1257
  }
566
- // ================================================================
567
- // Checkpoint / replay — time-travel debugging
568
- // ================================================================
569
- case "checkpoints": {
570
- if (!args.run_id)
571
- return { success: false, error: "run_id required" };
572
- // IDOR FIX: Verify the run belongs to this store
573
- const { data: cpRunCheck } = await supabase.from("workflow_runs")
574
- .select("id").eq("id", args.run_id).eq("store_id", sid).single();
575
- if (!cpRunCheck)
576
- return { success: false, error: "Run not found or access denied" };
577
- const { data, error } = await supabase.from("workflow_checkpoints")
578
- .select("id, step_key, step_run_id, sequence_number, created_at")
579
- .eq("run_id", args.run_id)
580
- .order("sequence_number", { ascending: true });
581
- return error ? { success: false, error: error.message } : { success: true, data };
1258
+
1259
+ // Update DLQ entry
1260
+ await supabase.from("workflow_dlq").update({
1261
+ status: "retried",
1262
+ retried_run_id: result.run_id,
1263
+ attempt_count: (dlqEntry.attempt_count || 1) + 1
1264
+ }).eq("id", dlqEntry.id);
1265
+
1266
+ // Inline execution for the retry
1267
+ try {
1268
+ await executeInlineChain(supabase, result.run_id);
1269
+ } catch (err) {
1270
+ console.error("[workflow] Inline chain failed for DLQ retry run", result.run_id, ":", err.message);
582
1271
  }
583
- case "replay": {
584
- if (!args.run_id)
585
- return { success: false, error: "run_id required" };
586
- const fromStepKey = args.from_step;
587
- // Load the checkpoint to replay from
588
- let checkpointQuery = supabase.from("workflow_checkpoints")
589
- .select("*").eq("run_id", args.run_id);
590
- if (fromStepKey) {
591
- checkpointQuery = checkpointQuery.eq("step_key", fromStepKey);
592
- }
593
- else {
594
- // Default: replay from last successful checkpoint
595
- checkpointQuery = checkpointQuery.order("sequence_number", { ascending: false }).limit(1);
596
- }
597
- const { data: checkpoint } = await checkpointQuery.single();
598
- if (!checkpoint)
599
- return { success: false, error: "No checkpoint found" };
600
- // Get original run's workflow (IDOR FIX: scope to store)
601
- const { data: origRun } = await supabase.from("workflow_runs")
602
- .select("workflow_id, trigger_type, trigger_payload, version_id")
603
- .eq("id", args.run_id).eq("store_id", sid).single();
604
- if (!origRun)
605
- return { success: false, error: "Original run not found" };
606
- // Start a new run with checkpoint state pre-loaded
607
- const { data: newRun, error: startErr } = await supabase.rpc("start_workflow_run", {
608
- p_workflow_id: origRun.workflow_id,
609
- p_store_id: sid,
610
- p_trigger_type: "replay",
611
- p_trigger_payload: checkpoint.trigger_payload || origRun.trigger_payload || {},
612
- p_idempotency_key: null,
1272
+ return {
1273
+ success: true,
1274
+ data: {
1275
+ retried: true,
1276
+ new_run_id: result.run_id,
1277
+ dlq_id: dlqEntry.id
1278
+ }
1279
+ };
1280
+ }
1281
+ case "dlq_dismiss":
1282
+ {
1283
+ if (!args.dlq_id) return {
1284
+ success: false,
1285
+ error: "dlq_id required"
1286
+ };
1287
+ const {
1288
+ error
1289
+ } = await supabase.from("workflow_dlq").update({
1290
+ status: "dismissed",
1291
+ dismissed_by: args.dismissed_by || null,
1292
+ dismissed_at: new Date().toISOString(),
1293
+ notes: args.notes || null
1294
+ }).eq("id", args.dlq_id).eq("store_id", sid).eq("status", "pending");
1295
+ return error ? {
1296
+ success: false,
1297
+ error: error.message
1298
+ } : {
1299
+ success: true,
1300
+ data: {
1301
+ dismissed: true
1302
+ }
1303
+ };
1304
+ }
1305
+
1306
+ // ================================================================
1307
+ // Enhanced metrics + DAG visualization
1308
+ // ================================================================
1309
+
1310
+ case "metrics":
1311
+ {
1312
+ const {
1313
+ data,
1314
+ error
1315
+ } = await supabase.rpc("get_workflow_metrics", {
1316
+ p_store_id: sid,
1317
+ p_days: args.days || 30
1318
+ });
1319
+ return error ? {
1320
+ success: false,
1321
+ error: error.message
1322
+ } : {
1323
+ success: true,
1324
+ data
1325
+ };
1326
+ }
1327
+ case "graph":
1328
+ {
1329
+ // Return DAG visualization data (nodes + edges) for a workflow
1330
+ if (!args.workflow_id) return {
1331
+ success: false,
1332
+ error: "workflow_id required"
1333
+ };
1334
+ // P0 FIX: IDOR protection — verify workflow belongs to this store before querying steps
1335
+ const {
1336
+ data: graphWfCheck
1337
+ } = await supabase.from("workflows").select("id").eq("id", args.workflow_id).eq("store_id", sid).single();
1338
+ if (!graphWfCheck) return {
1339
+ success: false,
1340
+ error: "Workflow not found or access denied"
1341
+ };
1342
+ const {
1343
+ data: steps,
1344
+ error: stepsErr
1345
+ } = await supabase.from("workflow_steps").select("id, step_key, step_type, is_entry_point, on_success, on_failure, position_x, position_y, step_config, timeout_seconds, max_retries").eq("workflow_id", args.workflow_id).order("position_y", {
1346
+ ascending: true
1347
+ });
1348
+ if (stepsErr) return {
1349
+ success: false,
1350
+ error: stepsErr.message
1351
+ };
1352
+ const nodes = (steps || []).map(s => ({
1353
+ id: s.step_key,
1354
+ step_id: s.id,
1355
+ // UUID for update_step/delete_step
1356
+ type: s.step_type,
1357
+ label: s.step_key,
1358
+ is_entry_point: s.is_entry_point,
1359
+ on_success: s.on_success || null,
1360
+ on_failure: s.on_failure || null,
1361
+ max_retries: s.max_retries,
1362
+ timeout_seconds: s.timeout_seconds,
1363
+ step_config: s.step_config || {},
1364
+ position: {
1365
+ x: s.position_x || 0,
1366
+ y: s.position_y || 0
1367
+ },
1368
+ config_summary: {
1369
+ timeout: s.timeout_seconds,
1370
+ max_retries: s.max_retries,
1371
+ ...(s.step_type === "condition" ? {
1372
+ expression: s.step_config?.expression
1373
+ } : {}),
1374
+ ...(s.step_type === "tool" ? {
1375
+ tool_name: s.step_config?.tool_name
1376
+ } : {}),
1377
+ ...(s.step_type === "delay" ? {
1378
+ seconds: s.step_config?.seconds
1379
+ } : {})
1380
+ }
1381
+ }));
1382
+ const edges = [];
1383
+ for (const s of steps || []) {
1384
+ if (s.on_success) edges.push({
1385
+ from: s.step_key,
1386
+ to: s.on_success,
1387
+ type: "success"
1388
+ });
1389
+ if (s.on_failure) edges.push({
1390
+ from: s.step_key,
1391
+ to: s.on_failure,
1392
+ type: "failure"
1393
+ });
1394
+ // Handle condition branches
1395
+ const cfg = s.step_config;
1396
+ if (s.step_type === "condition") {
1397
+ if (cfg?.on_true) edges.push({
1398
+ from: s.step_key,
1399
+ to: cfg.on_true,
1400
+ type: "true"
613
1401
  });
614
- if (startErr || !newRun?.success)
615
- return { success: false, error: startErr?.message || "Failed to start replay run" };
616
- // Pre-load step_outputs from checkpoint
617
- await supabase.from("workflow_runs").update({
618
- step_outputs: checkpoint.step_outputs,
619
- version_id: origRun.version_id,
620
- }).eq("id", newRun.run_id);
621
- // Cancel the auto-created entry step runs (we'll create our own starting from the checkpoint step)
622
- await supabase.from("workflow_step_runs").update({ status: "cancelled" })
623
- .eq("run_id", newRun.run_id).eq("status", "pending");
624
- // Find what step comes AFTER the checkpoint step
625
- const { data: checkpointStepDef } = await supabase.from("workflow_steps")
626
- .select("on_success").eq("workflow_id", origRun.workflow_id)
627
- .eq("step_key", checkpoint.step_key).single();
628
- if (checkpointStepDef?.on_success) {
629
- // createNextStepRunByKey is internal to workflow-steps — use direct insert
630
- const { data: nextStepDef } = await supabase.from("workflow_steps")
631
- .select("id, step_key, step_type, max_retries")
632
- .eq("workflow_id", origRun.workflow_id).eq("step_key", checkpointStepDef.on_success).single();
633
- if (nextStepDef) {
634
- await supabase.from("workflow_step_runs").insert({
635
- run_id: newRun.run_id, step_id: nextStepDef.id, step_key: nextStepDef.step_key,
636
- step_type: nextStepDef.step_type, status: "pending", max_attempts: nextStepDef.max_retries ?? 3,
637
- });
638
- }
639
- try {
640
- await executeInlineChain(supabase, newRun.run_id);
641
- }
642
- catch (err) {
643
- console.error("[workflow] Inline chain failed for replay run", newRun.run_id, ":", err.message);
644
- }
645
- }
646
- logWorkflowEvent(supabase, newRun.run_id, "run_replayed", {
647
- original_run_id: args.run_id, from_step: checkpoint.step_key, checkpoint_id: checkpoint.id,
1402
+ if (cfg?.on_false) edges.push({
1403
+ from: s.step_key,
1404
+ to: cfg.on_false,
1405
+ type: "false"
648
1406
  });
649
- return { success: true, data: { run_id: newRun.run_id, replayed_from: checkpoint.step_key, original_run_id: args.run_id } };
650
- }
651
- // ================================================================
652
- // Event journal time-travel debugging
653
- // ================================================================
654
- case "events": {
655
- if (!args.run_id)
656
- return { success: false, error: "run_id required" };
657
- // IDOR FIX: Verify the run belongs to this store
658
- const { data: eventsRunCheck } = await supabase.from("workflow_runs")
659
- .select("id").eq("id", args.run_id).eq("store_id", sid).single();
660
- if (!eventsRunCheck)
661
- return { success: false, error: "Run not found or access denied" };
662
- const { data, error } = await supabase.from("workflow_events")
663
- .select("id, event_type, step_run_id, payload, created_at")
664
- .eq("run_id", args.run_id)
665
- .order("created_at", { ascending: true })
666
- .limit(args.limit || 200);
667
- return error ? { success: false, error: error.message } : { success: true, data };
668
- }
669
- // ================================================================
670
- // Waitpoint actions
671
- // ================================================================
672
- case "list_waitpoints": {
673
- let q = supabase.from("waitpoint_tokens")
674
- .select("id, token, run_id, step_run_id, label, status, expires_at, completed_at, created_at")
675
- .eq("store_id", sid).order("created_at", { ascending: false });
676
- if (args.run_id)
677
- q = q.eq("run_id", args.run_id);
678
- if (args.status)
679
- q = q.eq("status", args.status);
680
- const { data, error } = await q.limit(args.limit || 25);
681
- return error ? { success: false, error: error.message } : { success: true, data };
682
- }
683
- case "complete_waitpoint": {
684
- if (!args.token)
685
- return { success: false, error: "token required" };
686
- // Find the waitpoint
687
- const { data: wp, error: wpErr } = await supabase.from("waitpoint_tokens")
688
- .select("id, run_id, step_run_id, store_id, expires_at, status")
689
- .eq("token", args.token).eq("store_id", sid).single();
690
- if (wpErr || !wp)
691
- return { success: false, error: "Waitpoint token not found" };
692
- if (wp.status === "completed")
693
- return { success: false, error: "Waitpoint already completed" };
694
- if (wp.expires_at && new Date(wp.expires_at) < new Date())
695
- return { success: false, error: "Waitpoint expired" };
696
- // Mark completed
697
- await supabase.from("waitpoint_tokens").update({
698
- status: "completed", completion_data: args.data || {}, completed_at: new Date().toISOString(),
699
- }).eq("id", wp.id);
700
- // Resume the waiting step
701
- await supabase.from("workflow_step_runs").update({
702
- status: "pending", input: { waitpoint_completed: true, waitpoint_data: args.data || {} },
703
- }).eq("id", wp.step_run_id).eq("status", "waiting");
704
- // Inline resume
705
- try {
706
- await executeInlineChain(supabase, wp.run_id);
707
- }
708
- catch (err) {
709
- console.error("[workflow] Inline chain failed after waitpoint:", err.message);
1407
+ }
1408
+ // Handle parallel children
1409
+ const parallelKeys = cfg?.step_keys || cfg?.child_steps;
1410
+ if (s.step_type === "parallel" && Array.isArray(parallelKeys)) {
1411
+ for (const childKey of parallelKeys) {
1412
+ edges.push({
1413
+ from: s.step_key,
1414
+ to: childKey,
1415
+ type: "parallel"
1416
+ });
710
1417
  }
711
- return { success: true, data: { completed: true, run_id: wp.run_id } };
1418
+ }
712
1419
  }
713
- // ================================================================
714
- // Dead Letter Queue actions
715
- // ================================================================
716
- case "dlq": {
717
- let q = supabase.from("workflow_dlq")
718
- .select("id, workflow_id, workflow_name, run_id, error_message, error_step_key, trigger_type, status, run_duration_ms, created_at")
719
- .eq("store_id", sid).order("created_at", { ascending: false });
720
- if (args.status)
721
- q = q.eq("status", args.status);
722
- if (args.workflow_id)
723
- q = q.eq("workflow_id", args.workflow_id);
724
- const { data, error } = await q.limit(args.limit || 25);
725
- return error ? { success: false, error: error.message } : { success: true, data };
1420
+
1421
+ // If run_id provided, overlay live status on nodes
1422
+ let nodeStatus = {};
1423
+ if (args.run_id) {
1424
+ const {
1425
+ data: stepRuns
1426
+ } = await supabase.from("workflow_step_runs").select("step_key, status, duration_ms, error_message").eq("run_id", args.run_id);
1427
+ for (const sr of stepRuns || []) {
1428
+ nodeStatus[sr.step_key] = {
1429
+ status: sr.status,
1430
+ duration_ms: sr.duration_ms,
1431
+ ...(sr.error_message ? {
1432
+ error: sr.error_message
1433
+ } : {})
1434
+ };
1435
+ }
726
1436
  }
727
- case "dlq_retry": {
728
- if (!args.dlq_id)
729
- return { success: false, error: "dlq_id required" };
730
- const { data: dlqEntry } = await supabase.from("workflow_dlq")
731
- .select("*").eq("id", args.dlq_id).eq("store_id", sid).single();
732
- if (!dlqEntry)
733
- return { success: false, error: "DLQ entry not found" };
734
- if (dlqEntry.status !== "pending")
735
- return { success: false, error: `DLQ entry already ${dlqEntry.status}` };
736
- // FIX 1: Preserve version_id from the original failed run
737
- const { data: originalRun } = await supabase.from("workflow_runs")
738
- .select("version_id")
739
- .eq("id", dlqEntry.run_id)
740
- .single();
741
- // Start a fresh run of the same workflow with the same trigger
742
- const { data: result, error: startErr } = await supabase.rpc("start_workflow_run", {
743
- p_workflow_id: dlqEntry.workflow_id,
744
- p_store_id: sid,
745
- p_trigger_type: dlqEntry.trigger_type || "dlq_retry",
746
- p_trigger_payload: { ...(dlqEntry.trigger_payload || {}), dlq_retry: true, original_run_id: dlqEntry.run_id },
747
- p_idempotency_key: null,
748
- });
749
- if (startErr || !result?.success)
750
- return { success: false, error: startErr?.message || result?.error || "Retry failed" };
751
- // Set version_id from the original failed run (same pattern as "start" action)
752
- if (result.run_id && !result.deduplicated && originalRun?.version_id) {
753
- await supabase.from("workflow_runs").update({ version_id: originalRun.version_id }).eq("id", result.run_id);
1437
+ return {
1438
+ success: true,
1439
+ data: {
1440
+ nodes,
1441
+ edges,
1442
+ node_status: Object.keys(nodeStatus).length ? nodeStatus : undefined
1443
+ }
1444
+ };
1445
+ }
1446
+
1447
+ // ================================================================
1448
+ // Schedule management
1449
+ // ================================================================
1450
+
1451
+ case "set_schedule":
1452
+ {
1453
+ if (!args.workflow_id) return {
1454
+ success: false,
1455
+ error: "workflow_id required"
1456
+ };
1457
+ const cronExpr = args.cron_expression;
1458
+ const runAt = args.run_at;
1459
+ if (runAt) {
1460
+ // One-time schedule: run once at a specific datetime
1461
+ const runAtDate = new Date(runAt);
1462
+ if (isNaN(runAtDate.getTime())) return {
1463
+ success: false,
1464
+ error: `Invalid run_at datetime: ${runAt}`
1465
+ };
1466
+ if (runAtDate <= new Date()) return {
1467
+ success: false,
1468
+ error: "run_at must be in the future"
1469
+ };
1470
+ const {
1471
+ error
1472
+ } = await supabase.from("workflows").update({
1473
+ cron_expression: null,
1474
+ next_run_at: runAtDate.toISOString(),
1475
+ trigger_type: "schedule",
1476
+ timezone: args.timezone || "UTC",
1477
+ is_active: true,
1478
+ status: "active"
1479
+ }).eq("id", args.workflow_id).eq("store_id", sid);
1480
+ return error ? {
1481
+ success: false,
1482
+ error: error.message
1483
+ } : {
1484
+ success: true,
1485
+ data: {
1486
+ one_time: true,
1487
+ run_at: runAtDate.toISOString()
754
1488
  }
755
- // Update DLQ entry
756
- await supabase.from("workflow_dlq").update({
757
- status: "retried", retried_run_id: result.run_id,
758
- attempt_count: (dlqEntry.attempt_count || 1) + 1,
759
- }).eq("id", dlqEntry.id);
760
- // Inline execution for the retry
761
- try {
762
- await executeInlineChain(supabase, result.run_id);
1489
+ };
1490
+ } else if (cronExpr) {
1491
+ // Recurring cron schedule
1492
+ const next = getNextCronTime(cronExpr);
1493
+ if (!next) return {
1494
+ success: false,
1495
+ error: `Invalid cron expression: ${cronExpr}`
1496
+ };
1497
+ const {
1498
+ error
1499
+ } = await supabase.from("workflows").update({
1500
+ cron_expression: cronExpr,
1501
+ next_run_at: next.toISOString(),
1502
+ trigger_type: "schedule",
1503
+ timezone: args.timezone || "UTC"
1504
+ }).eq("id", args.workflow_id).eq("store_id", sid);
1505
+ return error ? {
1506
+ success: false,
1507
+ error: error.message
1508
+ } : {
1509
+ success: true,
1510
+ data: {
1511
+ cron: cronExpr,
1512
+ next_run_at: next.toISOString()
763
1513
  }
764
- catch (err) {
765
- console.error("[workflow] Inline chain failed for DLQ retry run", result.run_id, ":", err.message);
1514
+ };
1515
+ } else {
1516
+ // Clear schedule
1517
+ const {
1518
+ error
1519
+ } = await supabase.from("workflows").update({
1520
+ cron_expression: null,
1521
+ next_run_at: null
1522
+ }).eq("id", args.workflow_id).eq("store_id", sid);
1523
+ return error ? {
1524
+ success: false,
1525
+ error: error.message
1526
+ } : {
1527
+ success: true,
1528
+ data: {
1529
+ schedule_cleared: true
766
1530
  }
767
- return { success: true, data: { retried: true, new_run_id: result.run_id, dlq_id: dlqEntry.id } };
768
- }
769
- case "dlq_dismiss": {
770
- if (!args.dlq_id)
771
- return { success: false, error: "dlq_id required" };
772
- const { error } = await supabase.from("workflow_dlq").update({
773
- status: "dismissed",
774
- dismissed_by: args.dismissed_by || null,
775
- dismissed_at: new Date().toISOString(),
776
- notes: args.notes || null,
777
- }).eq("id", args.dlq_id).eq("store_id", sid).eq("status", "pending");
778
- return error ? { success: false, error: error.message } : { success: true, data: { dismissed: true } };
779
- }
780
- // ================================================================
781
- // Enhanced metrics + DAG visualization
782
- // ================================================================
783
- case "metrics": {
784
- const { data, error } = await supabase.rpc("get_workflow_metrics", {
785
- p_store_id: sid, p_days: args.days || 30,
786
- });
787
- return error ? { success: false, error: error.message } : { success: true, data };
788
- }
789
- case "graph": {
790
- // Return DAG visualization data (nodes + edges) for a workflow
791
- if (!args.workflow_id)
792
- return { success: false, error: "workflow_id required" };
793
- // P0 FIX: IDOR protection — verify workflow belongs to this store before querying steps
794
- const { data: graphWfCheck } = await supabase.from("workflows")
795
- .select("id").eq("id", args.workflow_id).eq("store_id", sid).single();
796
- if (!graphWfCheck)
797
- return { success: false, error: "Workflow not found or access denied" };
798
- const { data: steps, error: stepsErr } = await supabase.from("workflow_steps")
799
- .select("id, step_key, step_type, is_entry_point, on_success, on_failure, position_x, position_y, step_config, timeout_seconds, max_retries")
800
- .eq("workflow_id", args.workflow_id)
801
- .order("position_y", { ascending: true });
802
- if (stepsErr)
803
- return { success: false, error: stepsErr.message };
804
- const nodes = (steps || []).map(s => ({
805
- id: s.step_key,
806
- step_id: s.id, // UUID for update_step/delete_step
807
- type: s.step_type,
808
- label: s.step_key,
809
- is_entry_point: s.is_entry_point,
810
- on_success: s.on_success || null,
811
- on_failure: s.on_failure || null,
812
- max_retries: s.max_retries,
813
- timeout_seconds: s.timeout_seconds,
814
- step_config: s.step_config || {},
815
- position: { x: s.position_x || 0, y: s.position_y || 0 },
816
- config_summary: {
817
- timeout: s.timeout_seconds,
818
- max_retries: s.max_retries,
819
- ...(s.step_type === "condition" ? { expression: s.step_config?.expression } : {}),
820
- ...(s.step_type === "tool" ? { tool_name: s.step_config?.tool_name } : {}),
821
- ...(s.step_type === "delay" ? { seconds: s.step_config?.seconds } : {}),
822
- },
823
- }));
824
- const edges = [];
825
- for (const s of steps || []) {
826
- if (s.on_success)
827
- edges.push({ from: s.step_key, to: s.on_success, type: "success" });
828
- if (s.on_failure)
829
- edges.push({ from: s.step_key, to: s.on_failure, type: "failure" });
830
- // Handle condition branches
831
- const cfg = s.step_config;
832
- if (s.step_type === "condition") {
833
- if (cfg?.on_true)
834
- edges.push({ from: s.step_key, to: cfg.on_true, type: "true" });
835
- if (cfg?.on_false)
836
- edges.push({ from: s.step_key, to: cfg.on_false, type: "false" });
837
- }
838
- // Handle parallel children
839
- const parallelKeys = cfg?.step_keys || cfg?.child_steps;
840
- if (s.step_type === "parallel" && Array.isArray(parallelKeys)) {
841
- for (const childKey of parallelKeys) {
842
- edges.push({ from: s.step_key, to: childKey, type: "parallel" });
843
- }
844
- }
845
- }
846
- // If run_id provided, overlay live status on nodes
847
- let nodeStatus = {};
848
- if (args.run_id) {
849
- const { data: stepRuns } = await supabase.from("workflow_step_runs")
850
- .select("step_key, status, duration_ms, error_message")
851
- .eq("run_id", args.run_id);
852
- for (const sr of stepRuns || []) {
853
- nodeStatus[sr.step_key] = { status: sr.status, duration_ms: sr.duration_ms, ...(sr.error_message ? { error: sr.error_message } : {}) };
854
- }
855
- }
856
- return { success: true, data: { nodes, edges, node_status: Object.keys(nodeStatus).length ? nodeStatus : undefined } };
857
- }
858
- // ================================================================
859
- // Schedule management
860
- // ================================================================
861
- case "set_schedule": {
862
- if (!args.workflow_id)
863
- return { success: false, error: "workflow_id required" };
864
- const cronExpr = args.cron_expression;
865
- const runAt = args.run_at;
866
- if (runAt) {
867
- // One-time schedule: run once at a specific datetime
868
- const runAtDate = new Date(runAt);
869
- if (isNaN(runAtDate.getTime()))
870
- return { success: false, error: `Invalid run_at datetime: ${runAt}` };
871
- if (runAtDate <= new Date())
872
- return { success: false, error: "run_at must be in the future" };
873
- const { error } = await supabase.from("workflows").update({
874
- cron_expression: null,
875
- next_run_at: runAtDate.toISOString(),
876
- trigger_type: "schedule",
877
- timezone: args.timezone || "UTC",
878
- is_active: true,
879
- status: "active",
880
- }).eq("id", args.workflow_id).eq("store_id", sid);
881
- return error ? { success: false, error: error.message } : { success: true, data: { one_time: true, run_at: runAtDate.toISOString() } };
882
- }
883
- else if (cronExpr) {
884
- // Recurring cron schedule
885
- const next = getNextCronTime(cronExpr);
886
- if (!next)
887
- return { success: false, error: `Invalid cron expression: ${cronExpr}` };
888
- const { error } = await supabase.from("workflows").update({
889
- cron_expression: cronExpr,
890
- next_run_at: next.toISOString(),
891
- trigger_type: "schedule",
892
- timezone: args.timezone || "UTC",
893
- }).eq("id", args.workflow_id).eq("store_id", sid);
894
- return error ? { success: false, error: error.message } : { success: true, data: { cron: cronExpr, next_run_at: next.toISOString() } };
895
- }
896
- else {
897
- // Clear schedule
898
- const { error } = await supabase.from("workflows").update({
899
- cron_expression: null, next_run_at: null,
900
- }).eq("id", args.workflow_id).eq("store_id", sid);
901
- return error ? { success: false, error: error.message } : { success: true, data: { schedule_cleared: true } };
902
- }
903
- }
904
- // ================================================================
905
- // Event bus — fire events & manage subscriptions
906
- // ================================================================
907
- case "fire_event": {
908
- if (!args.event_type)
909
- return { success: false, error: "event_type required" };
910
- const { data: evtId, error: evtErr } = await supabase.rpc("fire_event", {
911
- p_store_id: sid,
912
- p_event_type: args.event_type,
913
- p_event_payload: args.event_payload || {},
914
- p_source: args.source || "workflow_tool",
915
- });
916
- return evtErr ? { success: false, error: evtErr.message } : { success: true, data: { event_id: evtId } };
917
- }
918
- case "list_subscriptions": {
919
- let q = supabase.from("workflow_event_subscriptions")
920
- .select("id, store_id, workflow_id, event_type, filter_expression, is_active, created_at")
921
- .eq("store_id", sid).order("created_at", { ascending: false });
922
- if (args.event_type)
923
- q = q.eq("event_type", args.event_type);
924
- if (args.workflow_id)
925
- q = q.eq("workflow_id", args.workflow_id);
926
- const { data, error } = await q.limit(args.limit || 50);
927
- return error ? { success: false, error: error.message } : { success: true, data };
928
- }
929
- case "create_subscription": {
930
- if (!args.workflow_id)
931
- return { success: false, error: "workflow_id required" };
932
- if (!args.event_type)
933
- return { success: false, error: "event_type required" };
934
- const { data: sub, error: subErr } = await supabase.from("workflow_event_subscriptions")
935
- .insert({
936
- store_id: sid,
937
- workflow_id: args.workflow_id,
938
- event_type: args.event_type,
939
- filter_expression: args.filter_expression || null,
940
- is_active: args.is_active !== false,
941
- })
942
- .select().single();
943
- return subErr ? { success: false, error: subErr.message } : { success: true, data: sub };
944
- }
945
- case "delete_subscription": {
946
- if (!args.subscription_id)
947
- return { success: false, error: "subscription_id required" };
948
- const { error: delErr } = await supabase.from("workflow_event_subscriptions")
949
- .delete().eq("id", args.subscription_id).eq("store_id", sid);
950
- return delErr ? { success: false, error: delErr.message } : { success: true, data: { deleted: true } };
951
- }
952
- case "list_events": {
953
- let q = supabase.from("automation_events")
954
- .select("id, store_id, event_type, event_payload, source, status, processed_at, error_message, created_at")
955
- .eq("store_id", sid).order("created_at", { ascending: false });
956
- if (args.event_type)
957
- q = q.eq("event_type", args.event_type);
958
- if (args.status)
959
- q = q.eq("status", args.status);
960
- const { data, error } = await q.limit(args.limit || 50);
961
- return error ? { success: false, error: error.message } : { success: true, data };
962
- }
963
- case "list_tools": {
964
- const { data, error } = await supabase.from("ai_tool_registry")
965
- .select("name, description, category, definition, tool_mode, is_read_only, requires_store_id")
966
- .eq("is_active", true)
967
- .or("tool_mode.is.null,tool_mode.neq.code")
968
- .order("category").order("name");
969
- if (error)
970
- return { success: false, error: error.message };
971
- const tools = (data || []).map((row) => {
972
- const def = row.definition;
973
- const inputSchema = def?.input_schema;
974
- const actionProp = inputSchema?.properties?.action;
975
- const actions = actionProp?.enum || [];
976
- return {
977
- name: row.name,
978
- description: row.description || def?.description || "",
979
- category: row.category || "other",
980
- actions,
981
- input_schema: inputSchema || null,
982
- tool_mode: row.tool_mode || null,
983
- is_read_only: row.is_read_only ?? false,
984
- requires_store_id: row.requires_store_id ?? false,
985
- };
986
- });
987
- return { success: true, data: { tools } };
1531
+ };
988
1532
  }
989
- default:
990
- return { success: false, error: `Unknown workflow action: ${action}` };
991
- }
1533
+ }
1534
+
1535
+ // ================================================================
1536
+ // Event bus — fire events & manage subscriptions
1537
+ // ================================================================
1538
+
1539
+ case "fire_event":
1540
+ {
1541
+ if (!args.event_type) return {
1542
+ success: false,
1543
+ error: "event_type required"
1544
+ };
1545
+ const {
1546
+ data: evtId,
1547
+ error: evtErr
1548
+ } = await supabase.rpc("fire_event", {
1549
+ p_store_id: sid,
1550
+ p_event_type: args.event_type,
1551
+ p_event_payload: args.event_payload || {},
1552
+ p_source: args.source || "workflow_tool"
1553
+ });
1554
+ return evtErr ? {
1555
+ success: false,
1556
+ error: evtErr.message
1557
+ } : {
1558
+ success: true,
1559
+ data: {
1560
+ event_id: evtId
1561
+ }
1562
+ };
1563
+ }
1564
+ case "list_subscriptions":
1565
+ {
1566
+ let q = supabase.from("workflow_event_subscriptions").select("id, store_id, workflow_id, event_type, filter_expression, is_active, created_at").eq("store_id", sid).order("created_at", {
1567
+ ascending: false
1568
+ });
1569
+ if (args.event_type) q = q.eq("event_type", args.event_type);
1570
+ if (args.workflow_id) q = q.eq("workflow_id", args.workflow_id);
1571
+ const {
1572
+ data,
1573
+ error
1574
+ } = await q.limit(args.limit || 50);
1575
+ return error ? {
1576
+ success: false,
1577
+ error: error.message
1578
+ } : {
1579
+ success: true,
1580
+ data
1581
+ };
1582
+ }
1583
+ case "create_subscription":
1584
+ {
1585
+ if (!args.workflow_id) return {
1586
+ success: false,
1587
+ error: "workflow_id required"
1588
+ };
1589
+ if (!args.event_type) return {
1590
+ success: false,
1591
+ error: "event_type required"
1592
+ };
1593
+ const {
1594
+ data: sub,
1595
+ error: subErr
1596
+ } = await supabase.from("workflow_event_subscriptions").insert({
1597
+ store_id: sid,
1598
+ workflow_id: args.workflow_id,
1599
+ event_type: args.event_type,
1600
+ filter_expression: args.filter_expression || null,
1601
+ is_active: args.is_active !== false
1602
+ }).select().single();
1603
+ return subErr ? {
1604
+ success: false,
1605
+ error: subErr.message
1606
+ } : {
1607
+ success: true,
1608
+ data: sub
1609
+ };
1610
+ }
1611
+ case "delete_subscription":
1612
+ {
1613
+ if (!args.subscription_id) return {
1614
+ success: false,
1615
+ error: "subscription_id required"
1616
+ };
1617
+ const {
1618
+ error: delErr
1619
+ } = await supabase.from("workflow_event_subscriptions").delete().eq("id", args.subscription_id).eq("store_id", sid);
1620
+ return delErr ? {
1621
+ success: false,
1622
+ error: delErr.message
1623
+ } : {
1624
+ success: true,
1625
+ data: {
1626
+ deleted: true
1627
+ }
1628
+ };
1629
+ }
1630
+ case "list_events":
1631
+ {
1632
+ let q = supabase.from("automation_events").select("id, store_id, event_type, event_payload, source, status, processed_at, error_message, created_at").eq("store_id", sid).order("created_at", {
1633
+ ascending: false
1634
+ });
1635
+ if (args.event_type) q = q.eq("event_type", args.event_type);
1636
+ if (args.status) q = q.eq("status", args.status);
1637
+ const {
1638
+ data,
1639
+ error
1640
+ } = await q.limit(args.limit || 50);
1641
+ return error ? {
1642
+ success: false,
1643
+ error: error.message
1644
+ } : {
1645
+ success: true,
1646
+ data
1647
+ };
1648
+ }
1649
+ case "list_tools":
1650
+ {
1651
+ const {
1652
+ data,
1653
+ error
1654
+ } = await supabase.from("ai_tool_registry").select("name, description, category, definition, tool_mode, is_read_only, requires_store_id").eq("is_active", true).or("tool_mode.is.null,tool_mode.neq.code").order("category").order("name");
1655
+ if (error) return {
1656
+ success: false,
1657
+ error: error.message
1658
+ };
1659
+ const tools = (data || []).map(row => {
1660
+ const def = row.definition;
1661
+ const inputSchema = def?.input_schema;
1662
+ const actionProp = inputSchema?.properties?.action;
1663
+ const actions = actionProp?.enum || [];
1664
+ return {
1665
+ name: row.name,
1666
+ description: row.description || def?.description || "",
1667
+ category: row.category || "other",
1668
+ actions,
1669
+ input_schema: inputSchema || null,
1670
+ tool_mode: row.tool_mode || null,
1671
+ is_read_only: row.is_read_only ?? false,
1672
+ requires_store_id: row.requires_store_id ?? false
1673
+ };
1674
+ });
1675
+ return {
1676
+ success: true,
1677
+ data: {
1678
+ tools
1679
+ }
1680
+ };
1681
+ }
1682
+ default:
1683
+ return {
1684
+ success: false,
1685
+ error: `Unknown workflow action: ${action}`
1686
+ };
1687
+ }
992
1688
  }
1689
+ //# sourceMappingURL=workflows.js.map