upfynai-code 2.4.1 → 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 (147) 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 +1 -1
  110. package/server/database/auth.db +0 -0
  111. package/server/database/db.js +203 -1
  112. package/server/index.js +111 -18
  113. package/server/middleware/auth.js +11 -6
  114. package/server/projects.js +95 -202
  115. package/server/relay-client.js +47 -10
  116. package/server/routes/auth.js +6 -0
  117. package/server/routes/dashboard.js +52 -0
  118. package/server/routes/projects.js +38 -35
  119. package/server/routes/voice.js +198 -0
  120. package/server/routes/webhooks.js +166 -0
  121. package/server/routes/workflows.js +118 -0
  122. package/server/services/whisperService.js +84 -0
  123. package/server/services/workflowScheduler.js +186 -0
  124. package/client/dist/assets/AppContent-DTZ2FbvM.js +0 -513
  125. package/client/dist/assets/CanvasPanel-DlTW6Jh6.js +0 -6
  126. package/client/dist/assets/CanvasPanel-q4HEqNtV.css +0 -1
  127. package/client/dist/assets/LoginModal-CWoFm0au.js +0 -19
  128. package/client/dist/assets/az-AZ-76LH7QW2-CDdeucRZ.js +0 -1
  129. package/client/dist/assets/channel-O3ovC0x9.js +0 -1
  130. package/client/dist/assets/classDiagram-70f12bd4-D0lhAcxU.js +0 -2
  131. package/client/dist/assets/classDiagram-v2-f2320105-BuwUsF3F.js +0 -2
  132. package/client/dist/assets/clone-BG9u7vLi.js +0 -1
  133. package/client/dist/assets/flowDiagram-v2-96b9c2cf-Cd0Iascd.js +0 -1
  134. package/client/dist/assets/ganttDiagram-c361ad54-B8HJQqjt.js +0 -257
  135. package/client/dist/assets/index-B8wwD_Xo.css +0 -1
  136. package/client/dist/assets/kaa-6HZHGXH3-fwOleoQB.js +0 -1
  137. package/client/dist/assets/kk-KZ-P5N5QNE5-zpl7uvyF.js +0 -1
  138. package/client/dist/assets/my-MM-5M5IBNSE-kZQURVIi.js +0 -1
  139. package/client/dist/assets/sankeyDiagram-04a897e0-CsFqOQZN.js +0 -8
  140. package/client/dist/assets/si-LK-N5RQ5JYF-BBjcNYQh.js +0 -1
  141. package/client/dist/assets/stateDiagram-587899a1-BHoy9LtD.js +0 -1
  142. package/client/dist/assets/stateDiagram-v2-d93cdb3a-BvMUA6bS.js +0 -1
  143. package/client/dist/assets/subset-worker.chunk-BMx1eyv3.js +0 -1
  144. package/client/dist/assets/th-TH-HPSO5L25-CFNnJwSv.js +0 -2
  145. package/client/dist/assets/vendor-codemirror-langs-BH1ZcKHY.js +0 -20
  146. package/client/dist/assets/vendor-codemirror-rix45NST.js +0 -16
  147. package/client/dist/assets/zh-HK-E62DVLB3-zx9CvERq.js +0 -1
@@ -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 };