whale-code 6.5.5 → 6.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (847) hide show
  1. package/README.md +39 -31
  2. package/bin/{swagmanager-mcp.js → whale-code.js} +17 -2
  3. package/dist/cli/app.js +148 -72
  4. package/dist/cli/app.js.map +1 -0
  5. package/dist/cli/chat/AgentSelector.js +105 -10
  6. package/dist/cli/chat/AgentSelector.js.map +1 -0
  7. package/dist/cli/chat/ChatApp.d.ts +31 -0
  8. package/dist/cli/chat/ChatApp.js +539 -286
  9. package/dist/cli/chat/ChatApp.js.map +1 -0
  10. package/dist/cli/chat/ChatInput.js +1088 -770
  11. package/dist/cli/chat/ChatInput.js.map +1 -0
  12. package/dist/cli/chat/MarkdownText.js +39 -14
  13. package/dist/cli/chat/MarkdownText.js.map +1 -0
  14. package/dist/cli/chat/MemoryManager.js +181 -46
  15. package/dist/cli/chat/MemoryManager.js.map +1 -0
  16. package/dist/cli/chat/MessageList.d.ts +2 -3
  17. package/dist/cli/chat/MessageList.js +186 -45
  18. package/dist/cli/chat/MessageList.js.map +1 -0
  19. package/dist/cli/chat/ModelSelector.js +282 -63
  20. package/dist/cli/chat/ModelSelector.js.map +1 -0
  21. package/dist/cli/chat/NodeManager.js +165 -75
  22. package/dist/cli/chat/NodeManager.js.map +1 -0
  23. package/dist/cli/chat/NodeSelector.js +171 -30
  24. package/dist/cli/chat/NodeSelector.js.map +1 -0
  25. package/dist/cli/chat/PlanApproval.js +281 -57
  26. package/dist/cli/chat/PlanApproval.js.map +1 -0
  27. package/dist/cli/chat/RewindViewer.js +559 -144
  28. package/dist/cli/chat/RewindViewer.js.map +1 -0
  29. package/dist/cli/chat/SessionManager.js +137 -30
  30. package/dist/cli/chat/SessionManager.js.map +1 -0
  31. package/dist/cli/chat/SlashMenu.js +293 -164
  32. package/dist/cli/chat/SlashMenu.js.map +1 -0
  33. package/dist/cli/chat/StatusBar.js +172 -9
  34. package/dist/cli/chat/StatusBar.js.map +1 -0
  35. package/dist/cli/chat/StoreSelector.js +147 -18
  36. package/dist/cli/chat/StoreSelector.js.map +1 -0
  37. package/dist/cli/chat/StreamingText.d.ts +1 -5
  38. package/dist/cli/chat/StreamingText.js +22 -7
  39. package/dist/cli/chat/StreamingText.js.map +1 -0
  40. package/dist/cli/chat/SubagentPanel.d.ts +1 -2
  41. package/dist/cli/chat/SubagentPanel.js +612 -72
  42. package/dist/cli/chat/SubagentPanel.js.map +1 -0
  43. package/dist/cli/chat/TeamPanel.d.ts +1 -0
  44. package/dist/cli/chat/TeamPanel.js +230 -30
  45. package/dist/cli/chat/TeamPanel.js.map +1 -0
  46. package/dist/cli/chat/ThemeSelector.js +84 -24
  47. package/dist/cli/chat/ThemeSelector.js.map +1 -0
  48. package/dist/cli/chat/ToolIndicator.js +1476 -371
  49. package/dist/cli/chat/ToolIndicator.js.map +1 -0
  50. package/dist/cli/chat/hooks/useAgentLoop.d.ts +1 -0
  51. package/dist/cli/chat/hooks/useAgentLoop.js +481 -367
  52. package/dist/cli/chat/hooks/useAgentLoop.js.map +1 -0
  53. package/dist/cli/chat/hooks/useSlashCommands.d.ts +3 -14
  54. package/dist/cli/chat/hooks/useSlashCommands.js +744 -572
  55. package/dist/cli/chat/hooks/useSlashCommands.js.map +1 -0
  56. package/dist/cli/commands/config-cmd.js +56 -57
  57. package/dist/cli/commands/config-cmd.js.map +1 -0
  58. package/dist/cli/commands/db.js +184 -169
  59. package/dist/cli/commands/db.js.map +1 -0
  60. package/dist/cli/commands/doctor.js +212 -122
  61. package/dist/cli/commands/doctor.js.map +1 -0
  62. package/dist/cli/commands/init.js +211 -244
  63. package/dist/cli/commands/init.js.map +1 -0
  64. package/dist/cli/commands/mcp.js +127 -122
  65. package/dist/cli/commands/mcp.js.map +1 -0
  66. package/dist/cli/login/LoginApp.js +355 -141
  67. package/dist/cli/login/LoginApp.js.map +1 -0
  68. package/dist/cli/print-mode.js +196 -177
  69. package/dist/cli/print-mode.js.map +1 -0
  70. package/dist/cli/serve-mode.js +615 -530
  71. package/dist/cli/serve-mode.js.map +1 -0
  72. package/dist/cli/services/agent-config.d.ts +5 -1
  73. package/dist/cli/services/agent-config.js +66 -36
  74. package/dist/cli/services/agent-config.js.map +1 -0
  75. package/dist/cli/services/agent-definitions.d.ts +4 -1
  76. package/dist/cli/services/agent-definitions.js +97 -56
  77. package/dist/cli/services/agent-definitions.js.map +1 -0
  78. package/dist/cli/services/agent-events.js +225 -162
  79. package/dist/cli/services/agent-events.js.map +1 -0
  80. package/dist/cli/services/agent-loop.js +976 -688
  81. package/dist/cli/services/agent-loop.js.map +1 -0
  82. package/dist/cli/services/agent-worker-base.d.ts +35 -5
  83. package/dist/cli/services/agent-worker-base.js +337 -153
  84. package/dist/cli/services/agent-worker-base.js.map +1 -0
  85. package/dist/cli/services/api-retry.js +69 -64
  86. package/dist/cli/services/api-retry.js.map +1 -0
  87. package/dist/cli/services/auth-service.d.ts +3 -3
  88. package/dist/cli/services/auth-service.js +209 -132
  89. package/dist/cli/services/auth-service.js.map +1 -0
  90. package/dist/cli/services/background-processes.js +343 -267
  91. package/dist/cli/services/background-processes.js.map +1 -0
  92. package/dist/cli/services/browser-auth.d.ts +2 -2
  93. package/dist/cli/services/browser-auth.js +159 -118
  94. package/dist/cli/services/browser-auth.js.map +1 -0
  95. package/dist/cli/services/claude-md-loader.js +40 -36
  96. package/dist/cli/services/claude-md-loader.js.map +1 -0
  97. package/dist/cli/services/config-store.d.ts +9 -4
  98. package/dist/cli/services/config-store.js +164 -117
  99. package/dist/cli/services/config-store.js.map +1 -0
  100. package/dist/cli/services/debug-log.d.ts +1 -1
  101. package/dist/cli/services/debug-log.js +34 -35
  102. package/dist/cli/services/debug-log.js.map +1 -0
  103. package/dist/cli/services/env-detect.d.ts +7 -0
  104. package/dist/cli/services/env-detect.js +9 -0
  105. package/dist/cli/services/env-detect.js.map +1 -0
  106. package/dist/cli/services/error-logger.js +187 -169
  107. package/dist/cli/services/error-logger.js.map +1 -0
  108. package/dist/cli/services/file-history.d.ts +1 -1
  109. package/dist/cli/services/file-history.js +50 -54
  110. package/dist/cli/services/file-history.js.map +1 -0
  111. package/dist/cli/services/format-server-response.js +332 -372
  112. package/dist/cli/services/format-server-response.js.map +1 -0
  113. package/dist/cli/services/git-context.js +61 -45
  114. package/dist/cli/services/git-context.js.map +1 -0
  115. package/dist/cli/services/hooks.d.ts +2 -2
  116. package/dist/cli/services/hooks.js +195 -180
  117. package/dist/cli/services/hooks.js.map +1 -0
  118. package/dist/cli/services/ink-incremental.d.ts +19 -0
  119. package/dist/cli/services/ink-incremental.js +59 -0
  120. package/dist/cli/services/ink-incremental.js.map +1 -0
  121. package/dist/cli/services/ink-resize-fix.js +54 -44
  122. package/dist/cli/services/ink-resize-fix.js.map +1 -0
  123. package/dist/cli/services/ink-sync-output.d.ts +12 -0
  124. package/dist/cli/services/ink-sync-output.js +16 -0
  125. package/dist/cli/services/ink-sync-output.js.map +1 -0
  126. package/dist/cli/services/interactive-tools.js +268 -212
  127. package/dist/cli/services/interactive-tools.js.map +1 -0
  128. package/dist/cli/services/keybinding-manager.d.ts +11 -1
  129. package/dist/cli/services/keybinding-manager.js +126 -63
  130. package/dist/cli/services/keybinding-manager.js.map +1 -0
  131. package/dist/cli/services/local-tools.d.ts +1 -1
  132. package/dist/cli/services/local-tools.js +939 -656
  133. package/dist/cli/services/local-tools.js.map +1 -0
  134. package/dist/cli/services/lsp-manager.js +757 -594
  135. package/dist/cli/services/lsp-manager.js.map +1 -0
  136. package/dist/cli/services/mcp-client.d.ts +1 -1
  137. package/dist/cli/services/mcp-client.js +173 -134
  138. package/dist/cli/services/mcp-client.js.map +1 -0
  139. package/dist/cli/services/memory-manager.js +53 -40
  140. package/dist/cli/services/memory-manager.js.map +1 -0
  141. package/dist/cli/services/model-manager.js +55 -40
  142. package/dist/cli/services/model-manager.js.map +1 -0
  143. package/dist/cli/services/model-router.js +115 -85
  144. package/dist/cli/services/model-router.js.map +1 -0
  145. package/dist/cli/services/paths.d.ts +30 -0
  146. package/dist/cli/services/paths.js +81 -0
  147. package/dist/cli/services/paths.js.map +1 -0
  148. package/dist/cli/services/permission-modes.js +32 -25
  149. package/dist/cli/services/permission-modes.js.map +1 -0
  150. package/dist/cli/services/rewind.js +182 -168
  151. package/dist/cli/services/rewind.js.map +1 -0
  152. package/dist/cli/services/ripgrep.js +115 -115
  153. package/dist/cli/services/ripgrep.js.map +1 -0
  154. package/dist/cli/services/sandbox.d.ts +1 -1
  155. package/dist/cli/services/sandbox.js +58 -37
  156. package/dist/cli/services/sandbox.js.map +1 -0
  157. package/dist/cli/services/server-tools.js +738 -565
  158. package/dist/cli/services/server-tools.js.map +1 -0
  159. package/dist/cli/services/session-persistence.js +69 -74
  160. package/dist/cli/services/session-persistence.js.map +1 -0
  161. package/dist/cli/services/subagent-worker.js +42 -27
  162. package/dist/cli/services/subagent-worker.js.map +1 -0
  163. package/dist/cli/services/subagent.d.ts +2 -0
  164. package/dist/cli/services/subagent.js +605 -433
  165. package/dist/cli/services/subagent.js.map +1 -0
  166. package/dist/cli/services/system-prompt.js +86 -78
  167. package/dist/cli/services/system-prompt.js.map +1 -0
  168. package/dist/cli/services/task-decomposer.d.ts +1 -1
  169. package/dist/cli/services/task-decomposer.js +172 -139
  170. package/dist/cli/services/task-decomposer.js.map +1 -0
  171. package/dist/cli/services/team-lead.d.ts +2 -2
  172. package/dist/cli/services/team-lead.js +727 -529
  173. package/dist/cli/services/team-lead.js.map +1 -0
  174. package/dist/cli/services/team-state.js +319 -319
  175. package/dist/cli/services/team-state.js.map +1 -0
  176. package/dist/cli/services/teammate.d.ts +8 -2
  177. package/dist/cli/services/teammate.js +857 -569
  178. package/dist/cli/services/teammate.js.map +1 -0
  179. package/dist/cli/services/telemetry.d.ts +6 -1
  180. package/dist/cli/services/telemetry.js +180 -157
  181. package/dist/cli/services/telemetry.js.map +1 -0
  182. package/dist/cli/services/tools/agent-tools.d.ts +3 -3
  183. package/dist/cli/services/tools/agent-tools.js +480 -322
  184. package/dist/cli/services/tools/agent-tools.js.map +1 -0
  185. package/dist/cli/services/tools/file-ops.js +563 -450
  186. package/dist/cli/services/tools/file-ops.js.map +1 -0
  187. package/dist/cli/services/tools/search-tools.js +231 -162
  188. package/dist/cli/services/tools/search-tools.js.map +1 -0
  189. package/dist/cli/services/tools/shell-exec.js +197 -151
  190. package/dist/cli/services/tools/shell-exec.js.map +1 -0
  191. package/dist/cli/services/tools/task-manager.js +206 -173
  192. package/dist/cli/services/tools/task-manager.js.map +1 -0
  193. package/dist/cli/services/tools/web-tools.js +388 -341
  194. package/dist/cli/services/tools/web-tools.js.map +1 -0
  195. package/dist/cli/setup/SetupApp.d.ts +2 -2
  196. package/dist/cli/setup/SetupApp.js +608 -160
  197. package/dist/cli/setup/SetupApp.js.map +1 -0
  198. package/dist/cli/shared/ErrorBoundary.d.ts +22 -0
  199. package/dist/cli/shared/ErrorBoundary.js +73 -0
  200. package/dist/cli/shared/ErrorBoundary.js.map +1 -0
  201. package/dist/cli/shared/MatrixIntro.js +66 -69
  202. package/dist/cli/shared/MatrixIntro.js.map +1 -0
  203. package/dist/cli/shared/SpinnerSlot.d.ts +14 -0
  204. package/dist/cli/shared/SpinnerSlot.js +63 -0
  205. package/dist/cli/shared/SpinnerSlot.js.map +1 -0
  206. package/dist/cli/shared/Theme.d.ts +1 -1
  207. package/dist/cli/shared/Theme.js +136 -92
  208. package/dist/cli/shared/Theme.js.map +1 -0
  209. package/dist/cli/shared/WhaleBanner.js +99 -11
  210. package/dist/cli/shared/WhaleBanner.js.map +1 -0
  211. package/dist/cli/shared/markdown.d.ts +3 -1
  212. package/dist/cli/shared/markdown.js +736 -674
  213. package/dist/cli/shared/markdown.js.map +1 -0
  214. package/dist/cli/shared/marked-terminal.d.js +2 -0
  215. package/dist/cli/shared/marked-terminal.d.js.map +1 -0
  216. package/dist/cli/shared/theme-manager.js +99 -90
  217. package/dist/cli/shared/theme-manager.js.map +1 -0
  218. package/dist/cli/shared/theme-presets.js +256 -254
  219. package/dist/cli/shared/theme-presets.js.map +1 -0
  220. package/dist/cli/status/StatusApp.js +235 -86
  221. package/dist/cli/status/StatusApp.js.map +1 -0
  222. package/dist/cli/stores/StoreApp.js +275 -65
  223. package/dist/cli/stores/StoreApp.js.map +1 -0
  224. package/dist/index.d.ts +2 -2
  225. package/dist/index.js +509 -396
  226. package/dist/index.js.map +1 -0
  227. package/dist/local-agent/connection.d.ts +2 -2
  228. package/dist/local-agent/connection.js +352 -293
  229. package/dist/local-agent/connection.js.map +1 -0
  230. package/dist/local-agent/discovery.js +259 -122
  231. package/dist/local-agent/discovery.js.map +1 -0
  232. package/dist/local-agent/executor.js +216 -193
  233. package/dist/local-agent/executor.js.map +1 -0
  234. package/dist/local-agent/index.d.ts +2 -2
  235. package/dist/local-agent/index.js +156 -156
  236. package/dist/local-agent/index.js.map +1 -0
  237. package/dist/node/adapters/base.js +18 -8
  238. package/dist/node/adapters/base.js.map +1 -0
  239. package/dist/node/adapters/discord.js +286 -275
  240. package/dist/node/adapters/discord.js.map +1 -0
  241. package/dist/node/adapters/email.js +189 -202
  242. package/dist/node/adapters/email.js.map +1 -0
  243. package/dist/node/adapters/imessage.js +145 -142
  244. package/dist/node/adapters/imessage.js.map +1 -0
  245. package/dist/node/adapters/slack.js +237 -236
  246. package/dist/node/adapters/slack.js.map +1 -0
  247. package/dist/node/adapters/sms.js +149 -151
  248. package/dist/node/adapters/sms.js.map +1 -0
  249. package/dist/node/adapters/telegram.js +88 -92
  250. package/dist/node/adapters/telegram.js.map +1 -0
  251. package/dist/node/adapters/webchat.js +160 -136
  252. package/dist/node/adapters/webchat.js.map +1 -0
  253. package/dist/node/adapters/whatsapp.js +212 -215
  254. package/dist/node/adapters/whatsapp.js.map +1 -0
  255. package/dist/node/cli.js +884 -653
  256. package/dist/node/cli.js.map +1 -0
  257. package/dist/node/config.js +20 -18
  258. package/dist/node/config.js.map +1 -0
  259. package/dist/node/gateway-client.js +191 -181
  260. package/dist/node/gateway-client.js.map +1 -0
  261. package/dist/node/portal/clipboard.js +161 -130
  262. package/dist/node/portal/clipboard.js.map +1 -0
  263. package/dist/node/portal/discovery.js +51 -45
  264. package/dist/node/portal/discovery.js.map +1 -0
  265. package/dist/node/portal/forward.js +64 -58
  266. package/dist/node/portal/forward.js.map +1 -0
  267. package/dist/node/portal/index.js +246 -221
  268. package/dist/node/portal/index.js.map +1 -0
  269. package/dist/node/portal/multiplexer.js +192 -182
  270. package/dist/node/portal/multiplexer.js.map +1 -0
  271. package/dist/node/portal/permissions.js +102 -70
  272. package/dist/node/portal/permissions.js.map +1 -0
  273. package/dist/node/portal/protocol.js +153 -116
  274. package/dist/node/portal/protocol.js.map +1 -0
  275. package/dist/node/portal/screen.js +80 -69
  276. package/dist/node/portal/screen.js.map +1 -0
  277. package/dist/node/portal/session.js +124 -117
  278. package/dist/node/portal/session.js.map +1 -0
  279. package/dist/node/portal/shell.js +140 -113
  280. package/dist/node/portal/shell.js.map +1 -0
  281. package/dist/node/portal/stream.js +77 -75
  282. package/dist/node/portal/stream.js.map +1 -0
  283. package/dist/node/portal/transfer.js +190 -167
  284. package/dist/node/portal/transfer.js.map +1 -0
  285. package/dist/node/portal/ui.js +124 -99
  286. package/dist/node/portal/ui.js.map +1 -0
  287. package/dist/node/remote-desktop/compile-helper.js +50 -45
  288. package/dist/node/remote-desktop/compile-helper.js.map +1 -0
  289. package/dist/node/remote-desktop/index.js +215 -187
  290. package/dist/node/remote-desktop/index.js.map +1 -0
  291. package/dist/node/remote-desktop/protocol.js +45 -29
  292. package/dist/node/remote-desktop/protocol.js.map +1 -0
  293. package/dist/node/runtime.js +493 -410
  294. package/dist/node/runtime.js.map +1 -0
  295. package/dist/server/handlers/__test-utils__/test-db.js +39 -89
  296. package/dist/server/handlers/__test-utils__/test-db.js.map +1 -0
  297. package/dist/server/handlers/analytics.js +467 -261
  298. package/dist/server/handlers/analytics.js.map +1 -0
  299. package/dist/server/handlers/api-docs.js +1030 -895
  300. package/dist/server/handlers/api-docs.js.map +1 -0
  301. package/dist/server/handlers/api-keys.js +291 -242
  302. package/dist/server/handlers/api-keys.js.map +1 -0
  303. package/dist/server/handlers/billing.js +330 -239
  304. package/dist/server/handlers/billing.js.map +1 -0
  305. package/dist/server/handlers/browser.js +468 -395
  306. package/dist/server/handlers/browser.js.map +1 -0
  307. package/dist/server/handlers/catalog.js +1377 -978
  308. package/dist/server/handlers/catalog.js.map +1 -0
  309. package/dist/server/handlers/clickhouse.js +157 -109
  310. package/dist/server/handlers/clickhouse.js.map +1 -0
  311. package/dist/server/handlers/comms.js +1439 -984
  312. package/dist/server/handlers/comms.js.map +1 -0
  313. package/dist/server/handlers/creations.js +461 -394
  314. package/dist/server/handlers/creations.js.map +1 -0
  315. package/dist/server/handlers/crm.js +1082 -791
  316. package/dist/server/handlers/crm.js.map +1 -0
  317. package/dist/server/handlers/discovery.js +251 -232
  318. package/dist/server/handlers/discovery.js.map +1 -0
  319. package/dist/server/handlers/embeddings.js +241 -164
  320. package/dist/server/handlers/embeddings.js.map +1 -0
  321. package/dist/server/handlers/enrichment.js +887 -718
  322. package/dist/server/handlers/enrichment.js.map +1 -0
  323. package/dist/server/handlers/image-gen.js +467 -376
  324. package/dist/server/handlers/image-gen.js.map +1 -0
  325. package/dist/server/handlers/inventory.js +797 -424
  326. package/dist/server/handlers/inventory.js.map +1 -0
  327. package/dist/server/handlers/kali.js +272 -230
  328. package/dist/server/handlers/kali.js.map +1 -0
  329. package/dist/server/handlers/llm-providers.js +803 -580
  330. package/dist/server/handlers/llm-providers.js.map +1 -0
  331. package/dist/server/handlers/local-agent.js +133 -105
  332. package/dist/server/handlers/local-agent.js.map +1 -0
  333. package/dist/server/handlers/media.js +1179 -857
  334. package/dist/server/handlers/media.js.map +1 -0
  335. package/dist/server/handlers/meta-ads.js +2669 -2093
  336. package/dist/server/handlers/meta-ads.js.map +1 -0
  337. package/dist/server/handlers/nodes.js +1321 -913
  338. package/dist/server/handlers/nodes.js.map +1 -0
  339. package/dist/server/handlers/operations.js +183 -157
  340. package/dist/server/handlers/operations.js.map +1 -0
  341. package/dist/server/handlers/platform.js +346 -210
  342. package/dist/server/handlers/platform.js.map +1 -0
  343. package/dist/server/handlers/remove-bg.js +118 -86
  344. package/dist/server/handlers/remove-bg.js.map +1 -0
  345. package/dist/server/handlers/storefront.js +586 -446
  346. package/dist/server/handlers/storefront.js.map +1 -0
  347. package/dist/server/handlers/supply-chain.js +546 -326
  348. package/dist/server/handlers/supply-chain.js.map +1 -0
  349. package/dist/server/handlers/transcription.js +106 -97
  350. package/dist/server/handlers/transcription.js.map +1 -0
  351. package/dist/server/handlers/video-gen.js +593 -424
  352. package/dist/server/handlers/video-gen.js.map +1 -0
  353. package/dist/server/handlers/voice.js +1458 -1039
  354. package/dist/server/handlers/voice.js.map +1 -0
  355. package/dist/server/handlers/workflow-steps.js +2837 -2116
  356. package/dist/server/handlers/workflow-steps.js.map +1 -0
  357. package/dist/server/handlers/workflows.js +1630 -933
  358. package/dist/server/handlers/workflows.js.map +1 -0
  359. package/dist/server/index.js +3167 -2422
  360. package/dist/server/index.js.map +1 -0
  361. package/dist/server/lib/batch-client.js +471 -409
  362. package/dist/server/lib/batch-client.js.map +1 -0
  363. package/dist/server/lib/clickhouse-buffer.js +118 -104
  364. package/dist/server/lib/clickhouse-buffer.js.map +1 -0
  365. package/dist/server/lib/clickhouse-client.js +107 -107
  366. package/dist/server/lib/clickhouse-client.js.map +1 -0
  367. package/dist/server/lib/coa-renderer.js +1786 -356
  368. package/dist/server/lib/coa-renderer.js.map +1 -0
  369. package/dist/server/lib/code-worker-pool.js +227 -177
  370. package/dist/server/lib/code-worker-pool.js.map +1 -0
  371. package/dist/server/lib/code-worker.js +174 -164
  372. package/dist/server/lib/code-worker.js.map +1 -0
  373. package/dist/server/lib/compaction-service.d.ts +2 -12
  374. package/dist/server/lib/compaction-service.js +74 -184
  375. package/dist/server/lib/compaction-service.js.map +1 -0
  376. package/dist/server/lib/logger.js +36 -24
  377. package/dist/server/lib/logger.js.map +1 -0
  378. package/dist/server/lib/otel.js +101 -80
  379. package/dist/server/lib/otel.js.map +1 -0
  380. package/dist/server/lib/pdf-renderer.js +952 -788
  381. package/dist/server/lib/pdf-renderer.js.map +1 -0
  382. package/dist/server/lib/prompt-sanitizer.js +188 -108
  383. package/dist/server/lib/prompt-sanitizer.js.map +1 -0
  384. package/dist/server/lib/provider-capabilities.js +136 -138
  385. package/dist/server/lib/provider-capabilities.js.map +1 -0
  386. package/dist/server/lib/provider-failover.js +190 -168
  387. package/dist/server/lib/provider-failover.js.map +1 -0
  388. package/dist/server/lib/rate-limiter.js +186 -117
  389. package/dist/server/lib/rate-limiter.js.map +1 -0
  390. package/dist/server/lib/react-pdf-layout.js +551 -382
  391. package/dist/server/lib/react-pdf-layout.js.map +1 -0
  392. package/dist/server/lib/server-agent-loop.d.ts +4 -1
  393. package/dist/server/lib/server-agent-loop.js +906 -634
  394. package/dist/server/lib/server-agent-loop.js.map +1 -0
  395. package/dist/server/lib/server-subagent.js +260 -164
  396. package/dist/server/lib/server-subagent.js.map +1 -0
  397. package/dist/server/lib/session-checkpoint.js +105 -96
  398. package/dist/server/lib/session-checkpoint.js.map +1 -0
  399. package/dist/server/lib/ssrf-guard.js +193 -184
  400. package/dist/server/lib/ssrf-guard.js.map +1 -0
  401. package/dist/server/lib/supabase-client.js +94 -82
  402. package/dist/server/lib/supabase-client.js.map +1 -0
  403. package/dist/server/lib/template-resolver.js +154 -176
  404. package/dist/server/lib/template-resolver.js.map +1 -0
  405. package/dist/server/lib/utils.js +242 -133
  406. package/dist/server/lib/utils.js.map +1 -0
  407. package/dist/server/local-agent-gateway.d.ts +2 -2
  408. package/dist/server/local-agent-gateway.js +785 -627
  409. package/dist/server/local-agent-gateway.js.map +1 -0
  410. package/dist/server/providers/anthropic.js +250 -172
  411. package/dist/server/providers/anthropic.js.map +1 -0
  412. package/dist/server/providers/bedrock.js +217 -158
  413. package/dist/server/providers/bedrock.js.map +1 -0
  414. package/dist/server/providers/gemini.js +548 -418
  415. package/dist/server/providers/gemini.js.map +1 -0
  416. package/dist/server/providers/openai.js +571 -437
  417. package/dist/server/providers/openai.js.map +1 -0
  418. package/dist/server/providers/registry.js +23 -18
  419. package/dist/server/providers/registry.js.map +1 -0
  420. package/dist/server/providers/shared.js +123 -95
  421. package/dist/server/providers/shared.js.map +1 -0
  422. package/dist/server/providers/types.js +1 -11
  423. package/dist/server/providers/types.js.map +1 -0
  424. package/dist/server/proxy-handlers.js +209 -165
  425. package/dist/server/proxy-handlers.js.map +1 -0
  426. package/dist/server/tool-router.js +959 -599
  427. package/dist/server/tool-router.js.map +1 -0
  428. package/dist/server/validation.js +248 -188
  429. package/dist/server/validation.js.map +1 -0
  430. package/dist/server/worker.js +202 -133
  431. package/dist/server/worker.js.map +1 -0
  432. package/dist/setup.d.ts +2 -2
  433. package/dist/setup.js +151 -147
  434. package/dist/setup.js.map +1 -0
  435. package/dist/shared/agent-core.d.ts +115 -26
  436. package/dist/shared/agent-core.js +956 -522
  437. package/dist/shared/agent-core.js.map +1 -0
  438. package/dist/shared/anthropic-types.js +1 -6
  439. package/dist/shared/anthropic-types.js.map +1 -0
  440. package/dist/shared/api-client.d.ts +16 -9
  441. package/dist/shared/api-client.js +419 -327
  442. package/dist/shared/api-client.js.map +1 -0
  443. package/dist/shared/compaction.d.ts +36 -0
  444. package/dist/shared/compaction.js +138 -0
  445. package/dist/shared/compaction.js.map +1 -0
  446. package/dist/shared/constants.js +67 -64
  447. package/dist/shared/constants.js.map +1 -0
  448. package/dist/shared/sse-parser.js +221 -219
  449. package/dist/shared/sse-parser.js.map +1 -0
  450. package/dist/shared/tool-dispatch.d.ts +4 -0
  451. package/dist/shared/tool-dispatch.js +226 -165
  452. package/dist/shared/tool-dispatch.js.map +1 -0
  453. package/dist/shared/types.js +1 -6
  454. package/dist/shared/types.js.map +1 -0
  455. package/dist/types/cli-highlight.d.js +2 -0
  456. package/dist/types/cli-highlight.d.js.map +1 -0
  457. package/dist/types/diff.d.js +2 -0
  458. package/dist/types/diff.d.js.map +1 -0
  459. package/dist/types/pdf-parse.d.js +2 -0
  460. package/dist/types/pdf-parse.d.js.map +1 -0
  461. package/dist/updater.d.ts +1 -1
  462. package/dist/updater.js +118 -92
  463. package/dist/updater.js.map +1 -0
  464. package/dist/webchat/widget.js +227 -380
  465. package/dist/webchat/widget.js.map +1 -0
  466. package/package.json +22 -10
  467. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  468. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  469. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  470. package/vendor/ink/build/apply-styles.js +175 -0
  471. package/vendor/ink/build/build-layout.js +77 -0
  472. package/vendor/ink/build/calculate-wrapped-text.js +53 -0
  473. package/vendor/ink/build/colorize.d.ts +3 -0
  474. package/vendor/ink/build/colorize.js +48 -0
  475. package/vendor/ink/build/colorize.js.map +1 -0
  476. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  477. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  478. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  479. package/vendor/ink/build/components/App.d.ts +18 -0
  480. package/vendor/ink/build/components/App.js +351 -0
  481. package/vendor/ink/build/components/App.js.map +1 -0
  482. package/vendor/ink/build/components/AppContext.d.ts +15 -0
  483. package/vendor/ink/build/components/AppContext.js +11 -0
  484. package/vendor/ink/build/components/AppContext.js.map +1 -0
  485. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  486. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  487. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  488. package/vendor/ink/build/components/Box.d.ts +117 -0
  489. package/vendor/ink/build/components/Box.js +34 -0
  490. package/vendor/ink/build/components/Box.js.map +1 -0
  491. package/vendor/ink/build/components/Color.js +62 -0
  492. package/vendor/ink/build/components/Cursor.d.ts +83 -0
  493. package/vendor/ink/build/components/Cursor.js +53 -0
  494. package/vendor/ink/build/components/Cursor.js.map +1 -0
  495. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  496. package/vendor/ink/build/components/CursorContext.js +8 -0
  497. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  498. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  499. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  500. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  501. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  502. package/vendor/ink/build/components/ErrorOverview.js +84 -0
  503. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  504. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  505. package/vendor/ink/build/components/FocusContext.js +17 -0
  506. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  507. package/vendor/ink/build/components/Newline.d.ts +13 -0
  508. package/vendor/ink/build/components/Newline.js +8 -0
  509. package/vendor/ink/build/components/Newline.js.map +1 -0
  510. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  511. package/vendor/ink/build/components/Spacer.js +11 -0
  512. package/vendor/ink/build/components/Spacer.js.map +1 -0
  513. package/vendor/ink/build/components/Static.d.ts +24 -0
  514. package/vendor/ink/build/components/Static.js +28 -0
  515. package/vendor/ink/build/components/Static.js.map +1 -0
  516. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  517. package/vendor/ink/build/components/StderrContext.js +13 -0
  518. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  519. package/vendor/ink/build/components/StdinContext.d.ts +22 -0
  520. package/vendor/ink/build/components/StdinContext.js +19 -0
  521. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  522. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  523. package/vendor/ink/build/components/StdoutContext.js +13 -0
  524. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  525. package/vendor/ink/build/components/Text.d.ts +55 -0
  526. package/vendor/ink/build/components/Text.js +50 -0
  527. package/vendor/ink/build/components/Text.js.map +1 -0
  528. package/vendor/ink/build/components/Transform.d.ts +16 -0
  529. package/vendor/ink/build/components/Transform.js +15 -0
  530. package/vendor/ink/build/components/Transform.js.map +1 -0
  531. package/vendor/ink/build/cursor-helpers.d.ts +38 -0
  532. package/vendor/ink/build/cursor-helpers.js +56 -0
  533. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  534. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  535. package/vendor/ink/build/devtools-window-polyfill.js +65 -0
  536. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  537. package/vendor/ink/build/devtools.d.ts +1 -0
  538. package/vendor/ink/build/devtools.js +11 -0
  539. package/vendor/ink/build/devtools.js.map +1 -0
  540. package/vendor/ink/build/dom.d.ts +56 -0
  541. package/vendor/ink/build/dom.js +124 -0
  542. package/vendor/ink/build/dom.js.map +1 -0
  543. package/vendor/ink/build/experimental/apply-style.js +140 -0
  544. package/vendor/ink/build/experimental/dom.js +123 -0
  545. package/vendor/ink/build/experimental/output.js +91 -0
  546. package/vendor/ink/build/experimental/reconciler.js +141 -0
  547. package/vendor/ink/build/experimental/renderer.js +81 -0
  548. package/vendor/ink/build/get-max-width.d.ts +3 -0
  549. package/vendor/ink/build/get-max-width.js +10 -0
  550. package/vendor/ink/build/get-max-width.js.map +1 -0
  551. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  552. package/vendor/ink/build/hooks/use-app.js +8 -0
  553. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  554. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  555. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  556. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  557. package/vendor/ink/build/hooks/use-focus-manager.d.ts +28 -0
  558. package/vendor/ink/build/hooks/use-focus-manager.js +17 -0
  559. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  560. package/vendor/ink/build/hooks/use-focus.d.ts +29 -0
  561. package/vendor/ink/build/hooks/use-focus.js +42 -0
  562. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  563. package/vendor/ink/build/hooks/use-input.d.ts +131 -0
  564. package/vendor/ink/build/hooks/use-input.js +124 -0
  565. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  566. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +5 -0
  567. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +11 -0
  568. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  569. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  570. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  571. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  572. package/vendor/ink/build/hooks/use-stdin.d.ts +5 -0
  573. package/vendor/ink/build/hooks/use-stdin.js +8 -0
  574. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  575. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  576. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  577. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  578. package/vendor/ink/build/hooks/useInput.js +38 -0
  579. package/vendor/ink/build/index.d.ts +34 -0
  580. package/vendor/ink/build/index.js +20 -0
  581. package/vendor/ink/build/index.js.map +1 -0
  582. package/vendor/ink/build/ink.d.ts +90 -0
  583. package/vendor/ink/build/ink.js +654 -0
  584. package/vendor/ink/build/ink.js.map +1 -0
  585. package/vendor/ink/build/input-parser.d.ts +7 -0
  586. package/vendor/ink/build/input-parser.js +154 -0
  587. package/vendor/ink/build/input-parser.js.map +1 -0
  588. package/vendor/ink/build/instance.js +205 -0
  589. package/vendor/ink/build/instances.d.ts +3 -0
  590. package/vendor/ink/build/instances.js +8 -0
  591. package/vendor/ink/build/instances.js.map +1 -0
  592. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  593. package/vendor/ink/build/kitty-keyboard.js +32 -0
  594. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  595. package/vendor/ink/build/layout.d.ts +7 -0
  596. package/vendor/ink/build/layout.js +33 -0
  597. package/vendor/ink/build/layout.js.map +1 -0
  598. package/vendor/ink/build/log-update.d.ts +19 -0
  599. package/vendor/ink/build/log-update.js +243 -0
  600. package/vendor/ink/build/log-update.js.map +1 -0
  601. package/vendor/ink/build/measure-element.d.ts +16 -0
  602. package/vendor/ink/build/measure-element.js +9 -0
  603. package/vendor/ink/build/measure-element.js.map +1 -0
  604. package/vendor/ink/build/measure-text.d.ts +6 -0
  605. package/vendor/ink/build/measure-text.js +21 -0
  606. package/vendor/ink/build/measure-text.js.map +1 -0
  607. package/vendor/ink/build/options.d.ts +52 -0
  608. package/vendor/ink/build/options.js +2 -0
  609. package/vendor/ink/build/options.js.map +1 -0
  610. package/vendor/ink/build/output.d.ts +35 -0
  611. package/vendor/ink/build/output.js +183 -0
  612. package/vendor/ink/build/output.js.map +1 -0
  613. package/vendor/ink/build/parse-keypress.d.ts +22 -0
  614. package/vendor/ink/build/parse-keypress.js +493 -0
  615. package/vendor/ink/build/parse-keypress.js.map +1 -0
  616. package/vendor/ink/build/reconciler.d.ts +4 -0
  617. package/vendor/ink/build/reconciler.js +274 -0
  618. package/vendor/ink/build/reconciler.js.map +1 -0
  619. package/vendor/ink/build/render-background.d.ts +4 -0
  620. package/vendor/ink/build/render-background.js +25 -0
  621. package/vendor/ink/build/render-background.js.map +1 -0
  622. package/vendor/ink/build/render-border.d.ts +4 -0
  623. package/vendor/ink/build/render-border.js +73 -0
  624. package/vendor/ink/build/render-border.js.map +1 -0
  625. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  626. package/vendor/ink/build/render-node-to-output.js +147 -0
  627. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  628. package/vendor/ink/build/render-to-string.d.ts +38 -0
  629. package/vendor/ink/build/render-to-string.js +115 -0
  630. package/vendor/ink/build/render-to-string.js.map +1 -0
  631. package/vendor/ink/build/render.d.ts +121 -0
  632. package/vendor/ink/build/render.js +55 -0
  633. package/vendor/ink/build/render.js.map +1 -0
  634. package/vendor/ink/build/renderer.d.ts +8 -0
  635. package/vendor/ink/build/renderer.js +55 -0
  636. package/vendor/ink/build/renderer.js.map +1 -0
  637. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  638. package/vendor/ink/build/sanitize-ansi.js +27 -0
  639. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  640. package/vendor/ink/build/screen-reader-update.d.ts +13 -0
  641. package/vendor/ink/build/screen-reader-update.js +38 -0
  642. package/vendor/ink/build/screen-reader-update.js.map +1 -0
  643. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  644. package/vendor/ink/build/squash-text-nodes.js +36 -0
  645. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  646. package/vendor/ink/build/styles.d.ts +240 -0
  647. package/vendor/ink/build/styles.js +232 -0
  648. package/vendor/ink/build/styles.js.map +1 -0
  649. package/vendor/ink/build/utils.d.ts +2 -0
  650. package/vendor/ink/build/utils.js +4 -0
  651. package/vendor/ink/build/utils.js.map +1 -0
  652. package/vendor/ink/build/wrap-text.d.ts +3 -0
  653. package/vendor/ink/build/wrap-text.js +31 -0
  654. package/vendor/ink/build/wrap-text.js.map +1 -0
  655. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  656. package/vendor/ink/build/write-synchronized.js +7 -0
  657. package/vendor/ink/build/write-synchronized.js.map +1 -0
  658. package/vendor/ink/license +10 -0
  659. package/vendor/ink/node_modules/@types/node/LICENSE +21 -0
  660. package/vendor/ink/node_modules/@types/node/README.md +15 -0
  661. package/vendor/ink/node_modules/@types/node/assert/strict.d.ts +105 -0
  662. package/vendor/ink/node_modules/@types/node/assert.d.ts +955 -0
  663. package/vendor/ink/node_modules/@types/node/async_hooks.d.ts +623 -0
  664. package/vendor/ink/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  665. package/vendor/ink/node_modules/@types/node/buffer.d.ts +1810 -0
  666. package/vendor/ink/node_modules/@types/node/child_process.d.ts +1428 -0
  667. package/vendor/ink/node_modules/@types/node/cluster.d.ts +486 -0
  668. package/vendor/ink/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  669. package/vendor/ink/node_modules/@types/node/console.d.ts +151 -0
  670. package/vendor/ink/node_modules/@types/node/constants.d.ts +20 -0
  671. package/vendor/ink/node_modules/@types/node/crypto.d.ts +4065 -0
  672. package/vendor/ink/node_modules/@types/node/dgram.d.ts +564 -0
  673. package/vendor/ink/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  674. package/vendor/ink/node_modules/@types/node/dns/promises.d.ts +503 -0
  675. package/vendor/ink/node_modules/@types/node/dns.d.ts +922 -0
  676. package/vendor/ink/node_modules/@types/node/domain.d.ts +166 -0
  677. package/vendor/ink/node_modules/@types/node/events.d.ts +1054 -0
  678. package/vendor/ink/node_modules/@types/node/fs/promises.d.ts +1329 -0
  679. package/vendor/ink/node_modules/@types/node/fs.d.ts +4676 -0
  680. package/vendor/ink/node_modules/@types/node/globals.d.ts +150 -0
  681. package/vendor/ink/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  682. package/vendor/ink/node_modules/@types/node/http.d.ts +2167 -0
  683. package/vendor/ink/node_modules/@types/node/http2.d.ts +2480 -0
  684. package/vendor/ink/node_modules/@types/node/https.d.ts +405 -0
  685. package/vendor/ink/node_modules/@types/node/index.d.ts +115 -0
  686. package/vendor/ink/node_modules/@types/node/inspector/promises.d.ts +41 -0
  687. package/vendor/ink/node_modules/@types/node/inspector.d.ts +224 -0
  688. package/vendor/ink/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  689. package/vendor/ink/node_modules/@types/node/module.d.ts +819 -0
  690. package/vendor/ink/node_modules/@types/node/net.d.ts +933 -0
  691. package/vendor/ink/node_modules/@types/node/os.d.ts +507 -0
  692. package/vendor/ink/node_modules/@types/node/package.json +155 -0
  693. package/vendor/ink/node_modules/@types/node/path/posix.d.ts +8 -0
  694. package/vendor/ink/node_modules/@types/node/path/win32.d.ts +8 -0
  695. package/vendor/ink/node_modules/@types/node/path.d.ts +187 -0
  696. package/vendor/ink/node_modules/@types/node/perf_hooks.d.ts +643 -0
  697. package/vendor/ink/node_modules/@types/node/process.d.ts +2156 -0
  698. package/vendor/ink/node_modules/@types/node/punycode.d.ts +117 -0
  699. package/vendor/ink/node_modules/@types/node/querystring.d.ts +152 -0
  700. package/vendor/ink/node_modules/@types/node/quic.d.ts +910 -0
  701. package/vendor/ink/node_modules/@types/node/readline/promises.d.ts +161 -0
  702. package/vendor/ink/node_modules/@types/node/readline.d.ts +541 -0
  703. package/vendor/ink/node_modules/@types/node/repl.d.ts +415 -0
  704. package/vendor/ink/node_modules/@types/node/sea.d.ts +162 -0
  705. package/vendor/ink/node_modules/@types/node/sqlite.d.ts +955 -0
  706. package/vendor/ink/node_modules/@types/node/stream/consumers.d.ts +38 -0
  707. package/vendor/ink/node_modules/@types/node/stream/promises.d.ts +211 -0
  708. package/vendor/ink/node_modules/@types/node/stream/web.d.ts +296 -0
  709. package/vendor/ink/node_modules/@types/node/stream.d.ts +1760 -0
  710. package/vendor/ink/node_modules/@types/node/string_decoder.d.ts +67 -0
  711. package/vendor/ink/node_modules/@types/node/test/reporters.d.ts +96 -0
  712. package/vendor/ink/node_modules/@types/node/test.d.ts +2240 -0
  713. package/vendor/ink/node_modules/@types/node/timers/promises.d.ts +108 -0
  714. package/vendor/ink/node_modules/@types/node/timers.d.ts +159 -0
  715. package/vendor/ink/node_modules/@types/node/tls.d.ts +1198 -0
  716. package/vendor/ink/node_modules/@types/node/trace_events.d.ts +197 -0
  717. package/vendor/ink/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  718. package/vendor/ink/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  719. package/vendor/ink/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  720. package/vendor/ink/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  721. package/vendor/ink/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  722. package/vendor/ink/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  723. package/vendor/ink/node_modules/@types/node/tty.d.ts +250 -0
  724. package/vendor/ink/node_modules/@types/node/url.d.ts +519 -0
  725. package/vendor/ink/node_modules/@types/node/util/types.d.ts +558 -0
  726. package/vendor/ink/node_modules/@types/node/util.d.ts +1662 -0
  727. package/vendor/ink/node_modules/@types/node/v8.d.ts +983 -0
  728. package/vendor/ink/node_modules/@types/node/vm.d.ts +1208 -0
  729. package/vendor/ink/node_modules/@types/node/wasi.d.ts +202 -0
  730. package/vendor/ink/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  731. package/vendor/ink/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  732. package/vendor/ink/node_modules/@types/node/web-globals/console.d.ts +9 -0
  733. package/vendor/ink/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  734. package/vendor/ink/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  735. package/vendor/ink/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  736. package/vendor/ink/node_modules/@types/node/web-globals/events.d.ts +106 -0
  737. package/vendor/ink/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
  738. package/vendor/ink/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  739. package/vendor/ink/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  740. package/vendor/ink/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  741. package/vendor/ink/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  742. package/vendor/ink/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  743. package/vendor/ink/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  744. package/vendor/ink/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  745. package/vendor/ink/node_modules/@types/node/web-globals/url.d.ts +24 -0
  746. package/vendor/ink/node_modules/@types/node/worker_threads.d.ts +717 -0
  747. package/vendor/ink/node_modules/@types/node/zlib.d.ts +618 -0
  748. package/vendor/ink/node_modules/node-pty/LICENSE +69 -0
  749. package/vendor/ink/node_modules/node-pty/README.md +164 -0
  750. package/vendor/ink/node_modules/node-pty/binding.gyp +150 -0
  751. package/vendor/ink/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  752. package/vendor/ink/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  753. package/vendor/ink/node_modules/node-pty/lib/index.js +52 -0
  754. package/vendor/ink/node_modules/node-pty/lib/interfaces.js +7 -0
  755. package/vendor/ink/node_modules/node-pty/lib/shared/conout.js +11 -0
  756. package/vendor/ink/node_modules/node-pty/lib/terminal.js +190 -0
  757. package/vendor/ink/node_modules/node-pty/lib/types.js +7 -0
  758. package/vendor/ink/node_modules/node-pty/lib/unixTerminal.js +349 -0
  759. package/vendor/ink/node_modules/node-pty/lib/utils.js +39 -0
  760. package/vendor/ink/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  761. package/vendor/ink/node_modules/node-pty/lib/windowsPtyAgent.js +287 -0
  762. package/vendor/ink/node_modules/node-pty/lib/windowsTerminal.js +201 -0
  763. package/vendor/ink/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  764. package/vendor/ink/node_modules/node-pty/package.json +65 -0
  765. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  766. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  767. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  768. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  769. package/vendor/ink/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  770. package/vendor/ink/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  771. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  772. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  773. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  774. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  775. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  776. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  777. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  778. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  779. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  780. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  781. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  782. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  783. package/vendor/ink/node_modules/node-pty/scripts/post-install.js +76 -0
  784. package/vendor/ink/node_modules/node-pty/scripts/prebuild.js +34 -0
  785. package/vendor/ink/node_modules/node-pty/src/unix/pty.cc +875 -0
  786. package/vendor/ink/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  787. package/vendor/ink/node_modules/node-pty/src/win/conpty.cc +582 -0
  788. package/vendor/ink/node_modules/node-pty/src/win/conpty.h +41 -0
  789. package/vendor/ink/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  790. package/vendor/ink/node_modules/node-pty/src/win/path_util.cc +95 -0
  791. package/vendor/ink/node_modules/node-pty/src/win/path_util.h +26 -0
  792. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  793. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  794. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  795. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  796. package/vendor/ink/node_modules/node-pty/typings/node-pty.d.ts +215 -0
  797. package/vendor/ink/node_modules/undici-types/LICENSE +21 -0
  798. package/vendor/ink/node_modules/undici-types/README.md +6 -0
  799. package/vendor/ink/node_modules/undici-types/agent.d.ts +32 -0
  800. package/vendor/ink/node_modules/undici-types/api.d.ts +43 -0
  801. package/vendor/ink/node_modules/undici-types/balanced-pool.d.ts +30 -0
  802. package/vendor/ink/node_modules/undici-types/cache-interceptor.d.ts +173 -0
  803. package/vendor/ink/node_modules/undici-types/cache.d.ts +36 -0
  804. package/vendor/ink/node_modules/undici-types/client-stats.d.ts +15 -0
  805. package/vendor/ink/node_modules/undici-types/client.d.ts +108 -0
  806. package/vendor/ink/node_modules/undici-types/connector.d.ts +34 -0
  807. package/vendor/ink/node_modules/undici-types/content-type.d.ts +21 -0
  808. package/vendor/ink/node_modules/undici-types/cookies.d.ts +30 -0
  809. package/vendor/ink/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  810. package/vendor/ink/node_modules/undici-types/dispatcher.d.ts +276 -0
  811. package/vendor/ink/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  812. package/vendor/ink/node_modules/undici-types/errors.d.ts +161 -0
  813. package/vendor/ink/node_modules/undici-types/eventsource.d.ts +66 -0
  814. package/vendor/ink/node_modules/undici-types/fetch.d.ts +211 -0
  815. package/vendor/ink/node_modules/undici-types/formdata.d.ts +108 -0
  816. package/vendor/ink/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  817. package/vendor/ink/node_modules/undici-types/global-origin.d.ts +7 -0
  818. package/vendor/ink/node_modules/undici-types/h2c-client.d.ts +73 -0
  819. package/vendor/ink/node_modules/undici-types/handlers.d.ts +15 -0
  820. package/vendor/ink/node_modules/undici-types/header.d.ts +160 -0
  821. package/vendor/ink/node_modules/undici-types/index.d.ts +88 -0
  822. package/vendor/ink/node_modules/undici-types/interceptors.d.ts +73 -0
  823. package/vendor/ink/node_modules/undici-types/mock-agent.d.ts +68 -0
  824. package/vendor/ink/node_modules/undici-types/mock-call-history.d.ts +111 -0
  825. package/vendor/ink/node_modules/undici-types/mock-client.d.ts +27 -0
  826. package/vendor/ink/node_modules/undici-types/mock-errors.d.ts +12 -0
  827. package/vendor/ink/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  828. package/vendor/ink/node_modules/undici-types/mock-pool.d.ts +27 -0
  829. package/vendor/ink/node_modules/undici-types/package.json +55 -0
  830. package/vendor/ink/node_modules/undici-types/patch.d.ts +29 -0
  831. package/vendor/ink/node_modules/undici-types/pool-stats.d.ts +19 -0
  832. package/vendor/ink/node_modules/undici-types/pool.d.ts +41 -0
  833. package/vendor/ink/node_modules/undici-types/proxy-agent.d.ts +29 -0
  834. package/vendor/ink/node_modules/undici-types/readable.d.ts +68 -0
  835. package/vendor/ink/node_modules/undici-types/retry-agent.d.ts +8 -0
  836. package/vendor/ink/node_modules/undici-types/retry-handler.d.ts +125 -0
  837. package/vendor/ink/node_modules/undici-types/round-robin-pool.d.ts +41 -0
  838. package/vendor/ink/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  839. package/vendor/ink/node_modules/undici-types/util.d.ts +18 -0
  840. package/vendor/ink/node_modules/undici-types/utility.d.ts +7 -0
  841. package/vendor/ink/node_modules/undici-types/webidl.d.ts +341 -0
  842. package/vendor/ink/node_modules/undici-types/websocket.d.ts +186 -0
  843. package/vendor/ink/package.json +201 -0
  844. package/vendor/ink/readme.md +2636 -0
  845. package/bin/swag-agent.js +0 -9
  846. package/dist/server/lib/pg-rate-limiter.d.ts +0 -21
  847. package/dist/server/lib/pg-rate-limiter.js +0 -86
@@ -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