upfynai-code 2.4.0 → 2.5.0

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 (150) hide show
  1. package/client/dist/assets/AppContent-CRld2UWX.js +513 -0
  2. package/client/dist/assets/CanvasPanel-CB4sweQq.js +34 -0
  3. package/client/dist/assets/CanvasPanel-WhZulBJw.css +1 -0
  4. package/client/dist/assets/DashboardPanel-BXaA-b9z.js +1 -0
  5. package/client/dist/assets/LoginModal-BwkvjfPR.js +19 -0
  6. package/client/dist/assets/{Onboarding-CtIoXiTp.js → Onboarding-2A_5fPxy.js} +1 -1
  7. package/client/dist/assets/{SetupForm-B4p8im5O.js → SetupForm-CH5EA5W0.js} +1 -1
  8. package/client/dist/assets/WorkflowsPanel-CO5g5yGG.js +1 -0
  9. package/client/dist/assets/{ar-SA-G6X2FPQ2-2gfmdvHk.js → ar-SA-G6X2FPQ2-DoJuo98H.js} +2 -2
  10. package/client/dist/assets/{arc-DCZSHhoJ.js → arc-B0wBaTeh.js} +1 -1
  11. package/client/dist/assets/az-AZ-76LH7QW2-xdrt1Z13.js +1 -0
  12. package/client/dist/assets/{bg-BG-XCXSNQG7-D6__XtOK.js → bg-BG-XCXSNQG7-D8NAiF6Y.js} +2 -2
  13. package/client/dist/assets/{blockDiagram-38ab4fdb-Cfbaeyp6.js → blockDiagram-38ab4fdb-DSnyKzK4.js} +2 -2
  14. package/client/dist/assets/{bn-BD-2XOGV67Q-DHNJw3OG.js → bn-BD-2XOGV67Q-B0qWv8_J.js} +2 -2
  15. package/client/dist/assets/{c4Diagram-3d4e48cf-BBCnjOTy.js → c4Diagram-3d4e48cf-DoZJ13XA.js} +2 -2
  16. package/client/dist/assets/{ca-ES-6MX7JW3Y-r5g4o3zQ.js → ca-ES-6MX7JW3Y-RgLhfbZZ.js} +3 -3
  17. package/client/dist/assets/channel-BmO6nY0W.js +1 -0
  18. package/client/dist/assets/classDiagram-70f12bd4-GNyDrRCk.js +2 -0
  19. package/client/dist/assets/classDiagram-v2-f2320105-CxdGhHm2.js +2 -0
  20. package/client/dist/assets/clone-xuHMqFoD.js +1 -0
  21. package/client/dist/assets/{createText-2e5e7dd3-B8jCDmF_.js → createText-2e5e7dd3-DiPywQOa.js} +1 -1
  22. package/client/dist/assets/{cs-CZ-2BRQDIVT-p08jRLRC.js → cs-CZ-2BRQDIVT-BAjmnuoC.js} +2 -2
  23. package/client/dist/assets/{da-DK-5WZEPLOC-CnhOImFf.js → da-DK-5WZEPLOC-JxKVGt8o.js} +2 -2
  24. package/client/dist/assets/{de-DE-XR44H4JA-BunSXZ-Y.js → de-DE-XR44H4JA-CrnRlt4z.js} +2 -2
  25. package/client/dist/assets/{edges-e0da2a9e-CGBBhG8k.js → edges-e0da2a9e-DDsXzXLJ.js} +1 -1
  26. package/client/dist/assets/{el-GR-BZB4AONW-D4wv1oIz.js → el-GR-BZB4AONW-DQd8iogq.js} +2 -2
  27. package/client/dist/assets/{erDiagram-9861fffd-CYaF3q1I.js → erDiagram-9861fffd-CBiCC4rl.js} +2 -2
  28. package/client/dist/assets/{es-ES-U4NZUMDT-CGeTKXgd.js → es-ES-U4NZUMDT-vvUblc5i.js} +2 -2
  29. package/client/dist/assets/{eu-ES-A7QVB2H4-Cayx1TxR.js → eu-ES-A7QVB2H4-De4NNCc1.js} +2 -2
  30. package/client/dist/assets/{fa-IR-HGAKTJCU-CmUg8pmw.js → fa-IR-HGAKTJCU-DFBXqIqq.js} +2 -2
  31. package/client/dist/assets/{fi-FI-Z5N7JZ37-xvHcPhsU.js → fi-FI-Z5N7JZ37-DV9zESPg.js} +2 -2
  32. package/client/dist/assets/{flowDb-956e92f1-C-_LFz70.js → flowDb-956e92f1-BhdSHbdO.js} +1 -1
  33. package/client/dist/assets/{flowDiagram-66a62f08-C1sHdSjn.js → flowDiagram-66a62f08-M-fp1_Ie.js} +2 -2
  34. package/client/dist/assets/flowDiagram-v2-96b9c2cf-C5eiN8Pg.js +1 -0
  35. package/client/dist/assets/{flowchart-elk-definition-4a651766-CNGfpudb.js → flowchart-elk-definition-4a651766-Bp0SonQx.js} +2 -2
  36. package/client/dist/assets/{fr-FR-RHASNOE6-DBoHEcNj.js → fr-FR-RHASNOE6-CKTMXuGk.js} +2 -2
  37. package/client/dist/assets/ganttDiagram-c361ad54-iA737GUS.js +257 -0
  38. package/client/dist/assets/{gitGraphDiagram-72cf32ee-DojCDvlS.js → gitGraphDiagram-72cf32ee-BX-wj-PV.js} +2 -2
  39. package/client/dist/assets/{gl-ES-HMX3MZ6V-p6hrn2cN.js → gl-ES-HMX3MZ6V-Cdiqq4jY.js} +2 -2
  40. package/client/dist/assets/{graph-DXM7lcy1.js → graph-Rxkx3sEa.js} +1 -1
  41. package/client/dist/assets/{he-IL-6SHJWFNN-y2jEX6-0.js → he-IL-6SHJWFNN-gYmR5_KT.js} +2 -2
  42. package/client/dist/assets/{hi-IN-IWLTKZ5I-99pNfyWr.js → hi-IN-IWLTKZ5I-pyqK94AR.js} +2 -2
  43. package/client/dist/assets/{hu-HU-A5ZG7DT2-hygceGMS.js → hu-HU-A5ZG7DT2-DpacJgJy.js} +2 -2
  44. package/client/dist/assets/{id-ID-SAP4L64H-CyIqi1hv.js → id-ID-SAP4L64H-CAvIX-mj.js} +2 -2
  45. package/client/dist/assets/{index-3862675e-4idOQN2N.js → index-3862675e-BX3Fpn6V.js} +1 -1
  46. package/client/dist/assets/{index-BHZfFT_V.js → index-BBlwbHq_.js} +4 -4
  47. package/client/dist/assets/{index-BGmwbRlb.js → index-ClfzLIqY.js} +6 -6
  48. package/client/dist/assets/index-Td4UdtLF.css +1 -0
  49. package/client/dist/assets/{infoDiagram-f8f76790-CFLrHqtc.js → infoDiagram-f8f76790-Ckv8imiv.js} +2 -2
  50. package/client/dist/assets/{it-IT-JPQ66NNP-DzVvVdQI.js → it-IT-JPQ66NNP-BtpNRSce.js} +2 -2
  51. package/client/dist/assets/{ja-JP-DBVTYXUO-BI4fPexV.js → ja-JP-DBVTYXUO-CwJRyY6M.js} +2 -2
  52. package/client/dist/assets/{journeyDiagram-49397b02-C3CFDo8z.js → journeyDiagram-49397b02-DWWZssji.js} +2 -2
  53. package/client/dist/assets/kaa-6HZHGXH3-DIWQEb4A.js +1 -0
  54. package/client/dist/assets/{kab-KAB-ZGHBKWFO-DBI_ri48.js → kab-KAB-ZGHBKWFO-DjGbqhUg.js} +2 -2
  55. package/client/dist/assets/kk-KZ-P5N5QNE5-B_VzJdWf.js +1 -0
  56. package/client/dist/assets/{km-KH-HSX4SM5Z-DOMFSres.js → km-KH-HSX4SM5Z-DUD5mi0o.js} +2 -2
  57. package/client/dist/assets/{ko-KR-MTYHY66A-tb08hXzd.js → ko-KR-MTYHY66A--sDB10db.js} +3 -3
  58. package/client/dist/assets/{ku-TR-6OUDTVRD-DlIQCCY4.js → ku-TR-6OUDTVRD-CKvKrkcX.js} +2 -2
  59. package/client/dist/assets/{layout-B_11mCXA.js → layout-CkB7sSeq.js} +1 -1
  60. package/client/dist/assets/{line-B-qmK_vI.js → line-DC7MA9qY.js} +1 -1
  61. package/client/dist/assets/{linear-Ph6uuYcX.js → linear-C1lBBthf.js} +1 -1
  62. package/client/dist/assets/{lt-LT-XHIRWOB4--qWy24_Z.js → lt-LT-XHIRWOB4-MSZf7xYG.js} +2 -2
  63. package/client/dist/assets/{lv-LV-5QDEKY6T-Bnd_1GDb.js → lv-LV-5QDEKY6T-C-gvvmBB.js} +2 -2
  64. package/client/dist/assets/{mindmap-definition-fc14e90a-Do79tIc0.js → mindmap-definition-fc14e90a-B3O7hztq.js} +2 -2
  65. package/client/dist/assets/{mr-IN-CRQNXWMA-BsV6HaD9.js → mr-IN-CRQNXWMA-XHtBUWQH.js} +2 -2
  66. package/client/dist/assets/my-MM-5M5IBNSE-D9eD2edL.js +1 -0
  67. package/client/dist/assets/{nb-NO-T6EIAALU-Cvf9FdSF.js → nb-NO-T6EIAALU-BlImC6gp.js} +3 -3
  68. package/client/dist/assets/{nl-NL-IS3SIHDZ-DA1yqpXw.js → nl-NL-IS3SIHDZ-CPFhnaSP.js} +2 -2
  69. package/client/dist/assets/{nn-NO-6E72VCQL-89lm3vku.js → nn-NO-6E72VCQL-BMvoJSKQ.js} +2 -2
  70. package/client/dist/assets/{oc-FR-POXYY2M6-BsrjTJQh.js → oc-FR-POXYY2M6-Buye63LS.js} +2 -2
  71. package/client/dist/assets/{pa-IN-N4M65BXN-CczefYaj.js → pa-IN-N4M65BXN-D9uQ3niy.js} +2 -2
  72. package/client/dist/assets/{percentages-BXMCSKIN-Be6p9phi.js → percentages-BXMCSKIN-BzXIakGM.js} +7 -7
  73. package/client/dist/assets/{pieDiagram-8a3498a8-CfblQHdm.js → pieDiagram-8a3498a8-BU38mzx-.js} +3 -3
  74. package/client/dist/assets/{pl-PL-T2D74RX3-DdhH-zcK.js → pl-PL-T2D74RX3-BqM4xdcg.js} +2 -2
  75. package/client/dist/assets/{pt-BR-5N22H2LF-gpwlheL6.js → pt-BR-5N22H2LF-rAjrxGyI.js} +2 -2
  76. package/client/dist/assets/{pt-PT-UZXXM6DQ-Cs87vICi.js → pt-PT-UZXXM6DQ-DXsqcwLt.js} +2 -2
  77. package/client/dist/assets/{quadrantDiagram-120e2f19-CRMSamSP.js → quadrantDiagram-120e2f19-HhK4H1WU.js} +2 -2
  78. package/client/dist/assets/{requirementDiagram-deff3bca-D3LBN016.js → requirementDiagram-deff3bca-aDrcyj-A.js} +2 -2
  79. package/client/dist/assets/{ro-RO-JPDTUUEW-CWTSJ1Dt.js → ro-RO-JPDTUUEW-D_F9UKer.js} +2 -2
  80. package/client/dist/assets/{ru-RU-B4JR7IUQ-Bq7aN2ep.js → ru-RU-B4JR7IUQ-MirqN29p.js} +2 -2
  81. package/client/dist/assets/sankeyDiagram-04a897e0-C6ij7qbQ.js +8 -0
  82. package/client/dist/assets/{sequenceDiagram-704730f1-BRYXVDGX.js → sequenceDiagram-704730f1-C0EKO3th.js} +2 -2
  83. package/client/dist/assets/si-LK-N5RQ5JYF-DyZC3mkC.js +1 -0
  84. package/client/dist/assets/{sk-SK-C5VTKIMK-ByjKQzUb.js → sk-SK-C5VTKIMK-D-ksz-WY.js} +2 -2
  85. package/client/dist/assets/{sl-SI-NN7IZMDC-B8WCyMBU.js → sl-SI-NN7IZMDC-CknuYoQ1.js} +2 -2
  86. package/client/dist/assets/stateDiagram-587899a1-CYoq2VjL.js +1 -0
  87. package/client/dist/assets/stateDiagram-v2-d93cdb3a-C5lbp5px.js +1 -0
  88. package/client/dist/assets/{styles-6aaf32cf-Dr-lfIOW.js → styles-6aaf32cf-Dkfsk8gt.js} +1 -1
  89. package/client/dist/assets/{styles-9a916d00-DS4wRpL7.js → styles-9a916d00-CMYqtcEN.js} +1 -1
  90. package/client/dist/assets/{styles-c10674c1-nKRF6NrH.js → styles-c10674c1-Bp-5OlRU.js} +1 -1
  91. package/client/dist/assets/{subset-shared.chunk-KT79s7KG.js → subset-shared.chunk-kfIB1Zam.js} +3 -3
  92. package/client/dist/assets/subset-worker.chunk-DwQBgc4z.js +1 -0
  93. package/client/dist/assets/{sv-SE-XGPEYMSR-BiIPUVbv.js → sv-SE-XGPEYMSR-DwN13se1.js} +2 -2
  94. package/client/dist/assets/{svgDrawCommon-08f97a94-C3uP9PYr.js → svgDrawCommon-08f97a94-CEgCMqs4.js} +1 -1
  95. package/client/dist/assets/{ta-IN-2NMHFXQM-Cidadso2.js → ta-IN-2NMHFXQM-ejDfFhwa.js} +2 -2
  96. package/client/dist/assets/th-TH-HPSO5L25-Bqc90ZNn.js +2 -0
  97. package/client/dist/assets/{timeline-definition-85554ec2-BSsLsIgF.js → timeline-definition-85554ec2-BmGdKqG0.js} +2 -2
  98. package/client/dist/assets/{tr-TR-DEFEU3FU-DaFcI-KL.js → tr-TR-DEFEU3FU-CJvlPbcW.js} +2 -2
  99. package/client/dist/assets/{uk-UA-QMV73CPH-DkBW36St.js → uk-UA-QMV73CPH-D26-cbWL.js} +3 -3
  100. package/client/dist/assets/vendor-codemirror-D_s0aGBu.js +35 -0
  101. package/client/dist/assets/{vendor-icons-Dh9m_Ydt.js → vendor-icons-aNdOvTr_.js} +159 -119
  102. package/client/dist/assets/{vi-VN-M7AON7JQ-KrtfxOzl.js → vi-VN-M7AON7JQ-MbqIIwYM.js} +2 -2
  103. package/client/dist/assets/{xychartDiagram-e933f94c-CgNgZ4pp.js → xychartDiagram-e933f94c-gfcTauxU.js} +2 -2
  104. package/client/dist/assets/{zh-CN-LNUGB5OW-BQu12RoD.js → zh-CN-LNUGB5OW-BZSmhUdL.js} +3 -3
  105. package/client/dist/assets/zh-HK-E62DVLB3-BJqejpiX.js +1 -0
  106. package/client/dist/assets/{zh-TW-RAJ6MFWO-ffJWgVxn.js → zh-TW-RAJ6MFWO-BBXtV-Uz.js} +2 -2
  107. package/client/dist/index.html +3 -3
  108. package/package.json +5 -2
  109. package/server/cli.js +64 -5
  110. package/server/constants/config.js +29 -3
  111. package/server/database/auth.db +0 -0
  112. package/server/database/db.js +203 -1
  113. package/server/index.js +348 -48
  114. package/server/mcp-server.js +2 -1
  115. package/server/middleware/auth.js +20 -9
  116. package/server/projects.js +95 -202
  117. package/server/relay-client.js +205 -11
  118. package/server/routes/auth.js +6 -0
  119. package/server/routes/commands.js +1 -1
  120. package/server/routes/dashboard.js +52 -0
  121. package/server/routes/projects.js +38 -35
  122. package/server/routes/voice.js +198 -0
  123. package/server/routes/webhooks.js +166 -0
  124. package/server/routes/workflows.js +118 -0
  125. package/server/services/whisperService.js +84 -0
  126. package/server/services/workflowScheduler.js +186 -0
  127. package/client/dist/assets/AppContent-DTZ2FbvM.js +0 -513
  128. package/client/dist/assets/CanvasPanel-DlTW6Jh6.js +0 -6
  129. package/client/dist/assets/CanvasPanel-q4HEqNtV.css +0 -1
  130. package/client/dist/assets/LoginModal-CWoFm0au.js +0 -19
  131. package/client/dist/assets/az-AZ-76LH7QW2-CDdeucRZ.js +0 -1
  132. package/client/dist/assets/channel-O3ovC0x9.js +0 -1
  133. package/client/dist/assets/classDiagram-70f12bd4-D0lhAcxU.js +0 -2
  134. package/client/dist/assets/classDiagram-v2-f2320105-BuwUsF3F.js +0 -2
  135. package/client/dist/assets/clone-BG9u7vLi.js +0 -1
  136. package/client/dist/assets/flowDiagram-v2-96b9c2cf-Cd0Iascd.js +0 -1
  137. package/client/dist/assets/ganttDiagram-c361ad54-B8HJQqjt.js +0 -257
  138. package/client/dist/assets/index-B8wwD_Xo.css +0 -1
  139. package/client/dist/assets/kaa-6HZHGXH3-fwOleoQB.js +0 -1
  140. package/client/dist/assets/kk-KZ-P5N5QNE5-zpl7uvyF.js +0 -1
  141. package/client/dist/assets/my-MM-5M5IBNSE-kZQURVIi.js +0 -1
  142. package/client/dist/assets/sankeyDiagram-04a897e0-CsFqOQZN.js +0 -8
  143. package/client/dist/assets/si-LK-N5RQ5JYF-BBjcNYQh.js +0 -1
  144. package/client/dist/assets/stateDiagram-587899a1-BHoy9LtD.js +0 -1
  145. package/client/dist/assets/stateDiagram-v2-d93cdb3a-BvMUA6bS.js +0 -1
  146. package/client/dist/assets/subset-worker.chunk-BMx1eyv3.js +0 -1
  147. package/client/dist/assets/th-TH-HPSO5L25-CFNnJwSv.js +0 -2
  148. package/client/dist/assets/vendor-codemirror-langs-BH1ZcKHY.js +0 -20
  149. package/client/dist/assets/vendor-codemirror-rix45NST.js +0 -16
  150. package/client/dist/assets/zh-HK-E62DVLB3-zx9CvERq.js +0 -1
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Local Whisper STT service using nodejs-whisper.
3
+ * Used as a fallback when no OpenAI API key is configured (local mode).
4
+ * Requires ffmpeg to be installed on the system.
5
+ */
6
+
7
+ let whisperAvailable = null; // null = unchecked, true/false
8
+ let modelReady = false;
9
+
10
+ /**
11
+ * Check if nodejs-whisper and ffmpeg are available.
12
+ */
13
+ async function isWhisperAvailable() {
14
+ if (whisperAvailable !== null) return whisperAvailable;
15
+
16
+ try {
17
+ // Check if nodejs-whisper can be imported
18
+ await import('nodejs-whisper');
19
+
20
+ // Check if ffmpeg is available
21
+ const { execSync } = await import('child_process');
22
+ execSync('ffmpeg -version', { stdio: 'pipe', timeout: 5000 });
23
+
24
+ whisperAvailable = true;
25
+ } catch {
26
+ whisperAvailable = false;
27
+ }
28
+
29
+ return whisperAvailable;
30
+ }
31
+
32
+ /**
33
+ * Ensure the whisper model is downloaded.
34
+ * Downloads the tiny.en model (~75MB) on first use.
35
+ */
36
+ async function ensureWhisperModel() {
37
+ if (modelReady) return true;
38
+
39
+ const available = await isWhisperAvailable();
40
+ if (!available) return false;
41
+
42
+ try {
43
+ const { nodeWhisper } = await import('nodejs-whisper');
44
+ // Attempt to download model if not present
45
+ await nodeWhisper.downloadModel('tiny.en');
46
+ modelReady = true;
47
+ return true;
48
+ } catch (err) {
49
+ console.warn('[WhisperService] Failed to download model:', err.message);
50
+ return false;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Transcribe audio using local nodejs-whisper.
56
+ * @param {string} audioFilePath - Path to the audio file (WAV, MP3, etc.)
57
+ * @returns {Promise<string|null>} Transcribed text, or null if failed
58
+ */
59
+ async function transcribeLocal(audioFilePath) {
60
+ const ready = await ensureWhisperModel();
61
+ if (!ready) return null;
62
+
63
+ try {
64
+ const { nodeWhisper } = await import('nodejs-whisper');
65
+ const result = await nodeWhisper(audioFilePath, {
66
+ modelName: 'tiny.en',
67
+ autoDownloadModelName: 'tiny.en',
68
+ whisperOptions: {
69
+ outputInText: true,
70
+ language: 'en',
71
+ }
72
+ });
73
+
74
+ // nodejs-whisper returns array of segments or text
75
+ if (typeof result === 'string') return result.trim();
76
+ if (Array.isArray(result)) return result.map(s => s.speech || s.text || '').join(' ').trim();
77
+ return null;
78
+ } catch (err) {
79
+ console.error('[WhisperService] Transcription error:', err.message);
80
+ return null;
81
+ }
82
+ }
83
+
84
+ export { isWhisperAvailable, ensureWhisperModel, transcribeLocal };
@@ -0,0 +1,186 @@
1
+ import cron from 'node-cron';
2
+ import { workflowDb, webhookDb } from '../database/db.js';
3
+
4
+ const activeJobs = new Map(); // workflowId -> cron task
5
+
6
+ /**
7
+ * Execute a single workflow's steps (same logic as the /run endpoint).
8
+ * Returns { success, results, error }
9
+ */
10
+ async function executeWorkflow(workflow) {
11
+ const steps = typeof workflow.steps === 'string' ? JSON.parse(workflow.steps) : workflow.steps;
12
+ if (!steps.length) return { success: false, error: 'No steps' };
13
+
14
+ const run = await workflowDb.createRun(workflow.id, workflow.user_id, steps.length);
15
+ const results = [];
16
+ let lastOutput = null;
17
+
18
+ for (let i = 0; i < steps.length; i++) {
19
+ const step = steps[i];
20
+ try {
21
+ let stepResult;
22
+
23
+ if (step.type === 'webhook') {
24
+ const webhookId = step.config?.webhookId;
25
+ if (!webhookId) throw new Error('No webhook configured');
26
+
27
+ const webhook = await webhookDb.getById(Number(webhookId), workflow.user_id);
28
+ if (!webhook) throw new Error('Webhook not found');
29
+
30
+ let parsedHeaders = {};
31
+ try { parsedHeaders = JSON.parse(webhook.headers || '{}'); } catch { /* ignore */ }
32
+
33
+ const controller = new AbortController();
34
+ const timeout = setTimeout(() => controller.abort(), 15000);
35
+
36
+ const fetchOptions = {
37
+ method: webhook.method,
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ 'User-Agent': 'UpfynAI-Scheduler/1.0',
41
+ ...parsedHeaders
42
+ },
43
+ signal: controller.signal
44
+ };
45
+
46
+ if (['POST', 'PUT', 'PATCH'].includes(webhook.method)) {
47
+ const payload = {
48
+ workflow_id: workflow.id,
49
+ workflow_name: workflow.name,
50
+ step_index: i,
51
+ step_label: step.label,
52
+ previous_output: lastOutput,
53
+ scheduled: true,
54
+ timestamp: new Date().toISOString()
55
+ };
56
+ if (step.config?.payloadTemplate) {
57
+ try { Object.assign(payload, JSON.parse(step.config.payloadTemplate)); } catch { /* ignore */ }
58
+ }
59
+ fetchOptions.body = JSON.stringify(payload);
60
+ }
61
+
62
+ const response = await fetch(webhook.url, fetchOptions);
63
+ clearTimeout(timeout);
64
+
65
+ const contentType = response.headers.get('content-type') || '';
66
+ let body;
67
+ if (contentType.includes('application/json')) {
68
+ body = await response.json();
69
+ } else {
70
+ body = await response.text();
71
+ if (body.length > 5000) body = body.slice(0, 5000) + '...';
72
+ }
73
+
74
+ await webhookDb.updateLastTriggered(webhook.id);
75
+ stepResult = { status: response.status, body };
76
+ lastOutput = body;
77
+
78
+ } else if (step.type === 'ai-prompt') {
79
+ stepResult = { type: 'ai-prompt', prompt: step.config?.prompt || '', note: 'Scheduled AI prompts require active session' };
80
+ lastOutput = step.config?.prompt;
81
+
82
+ } else if (step.type === 'delay') {
83
+ const seconds = Math.min(step.config?.seconds || 1, 30);
84
+ await new Promise(resolve => setTimeout(resolve, seconds * 1000));
85
+ stepResult = { delayed: seconds };
86
+
87
+ } else {
88
+ stepResult = { type: step.type, note: 'Unknown step type' };
89
+ }
90
+
91
+ results.push({ step: i, label: step.label, type: step.type, success: true, result: stepResult });
92
+ await workflowDb.updateRun(run.id, { status: 'running', stepsCompleted: i + 1 });
93
+
94
+ } catch (stepError) {
95
+ results.push({ step: i, label: step.label, type: step.type, success: false, error: stepError.message });
96
+ await workflowDb.updateRun(run.id, { status: 'failed', stepsCompleted: i, error: `Step ${i + 1} (${step.label}): ${stepError.message}` });
97
+ await workflowDb.updateLastRun(workflow.id);
98
+ return { success: false, run_id: run.id, results, error: `Step ${i + 1} failed: ${stepError.message}` };
99
+ }
100
+ }
101
+
102
+ await workflowDb.updateRun(run.id, { status: 'completed', stepsCompleted: steps.length, result: results });
103
+ await workflowDb.updateLastRun(workflow.id);
104
+ return { success: true, run_id: run.id, results };
105
+ }
106
+
107
+ /**
108
+ * Schedule a single workflow's cron job.
109
+ */
110
+ function scheduleWorkflow(workflow) {
111
+ const id = workflow.id;
112
+
113
+ // Stop existing job if any
114
+ if (activeJobs.has(id)) {
115
+ activeJobs.get(id).stop();
116
+ activeJobs.delete(id);
117
+ }
118
+
119
+ if (!workflow.schedule || !workflow.schedule_enabled) return;
120
+
121
+ // Validate cron expression
122
+ if (!cron.validate(workflow.schedule)) {
123
+ console.warn(`[Scheduler] Invalid cron for workflow ${id}: ${workflow.schedule}`);
124
+ return;
125
+ }
126
+
127
+ const task = cron.schedule(workflow.schedule, async () => {
128
+ console.log(`[Scheduler] Running workflow ${id}: ${workflow.name}`);
129
+ try {
130
+ await executeWorkflow(workflow);
131
+ } catch (err) {
132
+ console.error(`[Scheduler] Workflow ${id} execution error:`, err.message);
133
+ }
134
+ }, {
135
+ timezone: workflow.schedule_timezone || 'UTC'
136
+ });
137
+
138
+ activeJobs.set(id, task);
139
+ console.log(`[Scheduler] Scheduled workflow ${id} (${workflow.name}): ${workflow.schedule}`);
140
+ }
141
+
142
+ /**
143
+ * Load all scheduled workflows from DB and start their cron jobs.
144
+ * Call this once at server startup.
145
+ */
146
+ async function initScheduler() {
147
+ try {
148
+ const workflows = await workflowDb.getScheduled();
149
+ console.log(`[Scheduler] Found ${workflows.length} scheduled workflow(s)`);
150
+ for (const wf of workflows) {
151
+ scheduleWorkflow(wf);
152
+ }
153
+ } catch (err) {
154
+ console.error('[Scheduler] Init error:', err.message);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Re-sync a specific workflow's schedule (call after create/update).
160
+ */
161
+ async function refreshWorkflowSchedule(workflowId, userId) {
162
+ try {
163
+ const wf = await workflowDb.getById(workflowId, userId);
164
+ if (wf) {
165
+ scheduleWorkflow(wf);
166
+ } else {
167
+ // Workflow deleted — stop its job
168
+ if (activeJobs.has(workflowId)) {
169
+ activeJobs.get(workflowId).stop();
170
+ activeJobs.delete(workflowId);
171
+ }
172
+ }
173
+ } catch { /* ignore */ }
174
+ }
175
+
176
+ /**
177
+ * Stop a workflow's cron job.
178
+ */
179
+ function stopWorkflowSchedule(workflowId) {
180
+ if (activeJobs.has(workflowId)) {
181
+ activeJobs.get(workflowId).stop();
182
+ activeJobs.delete(workflowId);
183
+ }
184
+ }
185
+
186
+ export { initScheduler, refreshWorkflowSchedule, stopWorkflowSchedule, executeWorkflow };