upfynai-code 3.0.4 → 3.2.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.
- package/README.md +69 -92
- package/bin/cli.js +191 -0
- package/dist/client/assets/AppContent-M14Au3SB.js +542 -0
- package/{client/dist/assets/BrowserPanel-0TLEl-IC.js → dist/client/assets/BrowserPanel-TFKm2NDJ.js} +2 -2
- package/dist/client/assets/DashboardPanel-C88HjsCh.js +1 -0
- package/dist/client/assets/FileTree-DvO1xnDE.js +1 -0
- package/{client/dist/assets/GitPanel-C_xFM-N2.js → dist/client/assets/GitPanel-D-slVlyy.js} +2 -2
- package/dist/client/assets/LoginModal-Chi4SYcr.js +21 -0
- package/{client/dist/assets/MarkdownPreview-CESjI261.js → dist/client/assets/MarkdownPreview-CuIix2u9.js} +1 -1
- package/dist/client/assets/MermaidBlock-Dq9uFv82.js +2 -0
- package/dist/client/assets/Onboarding-QYXx24dX.js +1 -0
- package/{client/dist/assets/PreviewPanel-CqCa92Tf.js → dist/client/assets/PreviewPanel-Dd8q-jo0.js} +1 -1
- package/dist/client/assets/SetupForm-CrspaUva.js +1 -0
- package/dist/client/assets/WorkflowsPanel-DIlYAdhB.js +1 -0
- package/dist/client/assets/index-CnNNzw9A.css +1 -0
- package/{client/dist/assets/index-HaY-3pK1.js → dist/client/assets/index-rUkK9FDP.js} +26 -26
- package/{client/dist/assets/vendor-codemirror-D2ALgpaX.js → dist/client/assets/vendor-codemirror-jc6nyJQg.js} +1 -1
- package/{client/dist/assets/vendor-diff-DNQpbhrT.js → dist/client/assets/vendor-diff-THJmAcEI.js} +1 -1
- package/{client/dist/assets/vendor-icons-GyYE35HP.js → dist/client/assets/vendor-icons-CfjIpdrD.js} +145 -155
- package/{client/dist/assets/vendor-markdown-CimbIo6Y.js → dist/client/assets/vendor-markdown-Cdm6NEGf.js} +1 -1
- package/dist/client/assets/vendor-mermaid-DTPaBx-U.js +2559 -0
- package/{client/dist/assets/vendor-react-96lCPsRK.js → dist/client/assets/vendor-react-wFkb6mSf.js} +1 -1
- package/{client/dist/assets/vendor-syntax-LS_Nt30I.js → dist/client/assets/vendor-syntax-C_UZR7tc.js} +1 -1
- package/dist/client/favicon.png +0 -0
- package/dist/client/icons/icon-128x128.png +0 -0
- package/dist/client/icons/icon-144x144.png +0 -0
- package/dist/client/icons/icon-152x152.png +0 -0
- package/dist/client/icons/icon-192x192.png +0 -0
- package/dist/client/icons/icon-384x384.png +0 -0
- package/dist/client/icons/icon-512x512.png +0 -0
- package/dist/client/icons/icon-72x72.png +0 -0
- package/dist/client/icons/icon-96x96.png +0 -0
- package/{client/dist → dist/client}/index.html +37 -36
- package/dist/client/logo-128.png +0 -0
- package/dist/client/logo-256.png +0 -0
- package/dist/client/logo-32.png +0 -0
- package/dist/client/logo-512.png +0 -0
- package/dist/client/logo-64.png +0 -0
- package/dist/client/logo.png +0 -0
- package/{client/dist → dist/client}/manifest.json +12 -12
- package/{client/dist → dist/client}/mcp-docs.html +1 -1
- package/{client/dist → dist/client}/sw.js +2 -2
- package/package.json +56 -105
- package/scripts/postinstall.js +9 -0
- package/scripts/prepublish.js +77 -0
- package/src/animation.js +228 -0
- package/src/auth.js +142 -0
- package/src/config.js +40 -0
- package/src/connect.js +416 -0
- package/src/launch.js +81 -0
- package/src/mcp.js +57 -0
- package/src/permissions.js +140 -0
- package/src/persistent-shell.js +261 -0
- package/src/server.js +54 -0
- package/client/dist/assets/AppContent-CwrTP6TW.js +0 -545
- package/client/dist/assets/CanvasFullScreen-D1GWQsGL.js +0 -1
- package/client/dist/assets/CanvasWorkspace-D7ORj358.js +0 -163
- package/client/dist/assets/DashboardPanel-BV7ybUDe.js +0 -1
- package/client/dist/assets/FileTree-5qfhBqdE.js +0 -1
- package/client/dist/assets/LoginModal-CImJHRjX.js +0 -13
- package/client/dist/assets/MermaidBlock-BFM21cwe.js +0 -2
- package/client/dist/assets/Onboarding-B3cteLu2.js +0 -1
- package/client/dist/assets/SetupForm-P6dsYgHO.js +0 -1
- package/client/dist/assets/WorkflowsPanel-CBoN80kc.js +0 -1
- package/client/dist/assets/index-46kkVu2i.css +0 -1
- package/client/dist/assets/pdf-CE_K4jFx.js +0 -12
- package/client/dist/assets/vendor-canvas-BZV40eAE.css +0 -1
- package/client/dist/assets/vendor-canvas-DvHJ_Pn2.js +0 -49
- package/client/dist/assets/vendor-mermaid-DucWyDEe.js +0 -2556
- package/client/dist/favicon.png +0 -0
- package/client/dist/icons/icon-128x128.png +0 -0
- package/client/dist/icons/icon-144x144.png +0 -0
- package/client/dist/icons/icon-152x152.png +0 -0
- package/client/dist/icons/icon-192x192.png +0 -0
- package/client/dist/icons/icon-384x384.png +0 -0
- package/client/dist/icons/icon-512x512.png +0 -0
- package/client/dist/icons/icon-72x72.png +0 -0
- package/client/dist/icons/icon-96x96.png +0 -0
- package/client/dist/logo-128.png +0 -0
- package/client/dist/logo-256.png +0 -0
- package/client/dist/logo-32.png +0 -0
- package/client/dist/logo-512.png +0 -0
- package/client/dist/logo-64.png +0 -0
- package/commands/upfynai-connect.md +0 -59
- package/commands/upfynai-disconnect.md +0 -31
- package/commands/upfynai-doctor.md +0 -99
- package/commands/upfynai-export.md +0 -49
- package/commands/upfynai-local.md +0 -82
- package/commands/upfynai-status.md +0 -75
- package/commands/upfynai-stop.md +0 -49
- package/commands/upfynai-uninstall.md +0 -58
- package/commands/upfynai.md +0 -69
- package/scripts/build-client.js +0 -17
- package/scripts/fix-node-pty.js +0 -67
- package/scripts/install-commands.js +0 -78
- package/server/agent-loop.js +0 -242
- package/server/auto-compact.js +0 -99
- package/server/browser.js +0 -131
- package/server/claude-sdk.js +0 -797
- package/server/cli-ui.js +0 -798
- package/server/cli.js +0 -751
- package/server/constants/config.js +0 -31
- package/server/cursor-cli.js +0 -270
- package/server/database/auth.db +0 -0
- package/server/database/db.js +0 -1547
- package/server/database/init.sql +0 -70
- package/server/index.js +0 -3813
- package/server/load-env.js +0 -26
- package/server/mcp-server.js +0 -621
- package/server/middleware/auth.js +0 -184
- package/server/middleware/relayHelpers.js +0 -44
- package/server/middleware/sandboxRouter.js +0 -174
- package/server/openai-codex.js +0 -403
- package/server/openrouter.js +0 -137
- package/server/projects.js +0 -1807
- package/server/provider-factory.js +0 -174
- package/server/relay-client.js +0 -390
- package/server/routes/agent.js +0 -1234
- package/server/routes/auth.js +0 -559
- package/server/routes/browser.js +0 -419
- package/server/routes/canvas.js +0 -53
- package/server/routes/cli-auth.js +0 -263
- package/server/routes/codex.js +0 -396
- package/server/routes/commands.js +0 -707
- package/server/routes/composio.js +0 -176
- package/server/routes/cursor.js +0 -770
- package/server/routes/dashboard.js +0 -295
- package/server/routes/git.js +0 -1208
- package/server/routes/keys.js +0 -34
- package/server/routes/mcp-utils.js +0 -48
- package/server/routes/mcp.js +0 -661
- package/server/routes/payments.js +0 -227
- package/server/routes/projects.js +0 -754
- package/server/routes/sessions.js +0 -146
- package/server/routes/settings.js +0 -261
- package/server/routes/taskmaster.js +0 -1928
- package/server/routes/user.js +0 -106
- package/server/routes/vapi-chat.js +0 -624
- package/server/routes/voice.js +0 -235
- package/server/routes/webhooks.js +0 -166
- package/server/routes/workflows.js +0 -312
- package/server/sandbox.js +0 -120
- package/server/services/browser-ai.js +0 -154
- package/server/services/composio.js +0 -204
- package/server/services/sessionRegistry.js +0 -139
- package/server/services/whisperService.js +0 -84
- package/server/services/workflowScheduler.js +0 -211
- package/server/tests/relay-flow.test.js +0 -570
- package/server/tests/sessions.test.js +0 -259
- package/server/utils/commandParser.js +0 -303
- package/server/utils/email.js +0 -66
- package/server/utils/gitConfig.js +0 -24
- package/server/utils/mcp-detector.js +0 -198
- package/server/utils/taskmaster-websocket.js +0 -129
- package/shared/integrationCatalog.d.ts +0 -12
- package/shared/integrationCatalog.js +0 -172
- package/shared/modelConstants.js +0 -96
- /package/{shared → dist}/agents/claude.js +0 -0
- /package/{shared → dist}/agents/codex.js +0 -0
- /package/{shared → dist}/agents/cursor.js +0 -0
- /package/{shared → dist}/agents/detect.js +0 -0
- /package/{shared → dist}/agents/exec.js +0 -0
- /package/{shared → dist}/agents/files.js +0 -0
- /package/{shared → dist}/agents/git.js +0 -0
- /package/{shared → dist}/agents/gitagent.js +0 -0
- /package/{shared → dist}/agents/index.js +0 -0
- /package/{shared → dist}/agents/shell.js +0 -0
- /package/{shared → dist}/agents/utils.js +0 -0
- /package/{client/dist → dist/client}/api-docs.html +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- /package/{client/dist → dist/client}/assets/vendor-i18n-DCFGyhQR.js +0 -0
- /package/{client/dist → dist/client}/assets/vendor-xterm-CZq1hqo1.js +0 -0
- /package/{client/dist → dist/client}/assets/vendor-xterm-qxJ8_QYu.css +0 -0
- /package/{client/dist → dist/client}/clear-cache.html +0 -0
- /package/{client/dist → dist/client}/convert-icons.md +0 -0
- /package/{client/dist → dist/client}/favicon.svg +0 -0
- /package/{client/dist → dist/client}/generate-icons.js +0 -0
- /package/{client/dist → dist/client}/icons/claude-ai-icon.svg +0 -0
- /package/{client/dist → dist/client}/icons/codex-white.svg +0 -0
- /package/{client/dist → dist/client}/icons/codex.svg +0 -0
- /package/{client/dist → dist/client}/icons/cursor-white.svg +0 -0
- /package/{client/dist → dist/client}/icons/cursor.svg +0 -0
- /package/{client/dist → dist/client}/icons/icon-128x128.svg +0 -0
- /package/{client/dist → dist/client}/icons/icon-144x144.svg +0 -0
- /package/{client/dist → dist/client}/icons/icon-152x152.svg +0 -0
- /package/{client/dist → dist/client}/icons/icon-192x192.svg +0 -0
- /package/{client/dist → dist/client}/icons/icon-384x384.svg +0 -0
- /package/{client/dist → dist/client}/icons/icon-512x512.svg +0 -0
- /package/{client/dist → dist/client}/icons/icon-72x72.svg +0 -0
- /package/{client/dist → dist/client}/icons/icon-96x96.svg +0 -0
- /package/{client/dist → dist/client}/icons/icon-template.svg +0 -0
- /package/{client/dist → dist/client}/logo.svg +0 -0
- /package/{client/dist → dist/client}/offline.html +0 -0
- /package/{client/dist → dist/client}/screenshots/cli-selection.png +0 -0
- /package/{client/dist → dist/client}/screenshots/desktop-main.png +0 -0
- /package/{client/dist → dist/client}/screenshots/mobile-chat.png +0 -0
- /package/{client/dist → dist/client}/screenshots/tools-modal.png +0 -0
- /package/{shared → dist}/gitagent/index.js +0 -0
- /package/{shared → dist}/gitagent/parser.js +0 -0
- /package/{shared → dist}/gitagent/prompt-builder.js +0 -0
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Composio Service — wraps the Composio SDK for OAuth management + tool execution.
|
|
3
|
-
* Single API key for the app, user isolation via entity mapping.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
let Composio = null;
|
|
8
|
-
let composioClient = null;
|
|
9
|
-
let sdkReady = null; // resolved promise once SDK is loaded
|
|
10
|
-
|
|
11
|
-
// Eagerly attempt to load the SDK — store the promise so getClient can await it
|
|
12
|
-
sdkReady = (async () => {
|
|
13
|
-
try {
|
|
14
|
-
const mod = await import('composio-core');
|
|
15
|
-
Composio = mod.Composio || mod.default;
|
|
16
|
-
} catch {
|
|
17
|
-
// SDK not installed — composio features will be unavailable
|
|
18
|
-
}
|
|
19
|
-
})();
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Lazy-init the Composio client singleton.
|
|
23
|
-
* Returns null if COMPOSIO_API_KEY is not set.
|
|
24
|
-
*/
|
|
25
|
-
async function getClient() {
|
|
26
|
-
if (composioClient) return composioClient;
|
|
27
|
-
if (!process.env.COMPOSIO_API_KEY) return null;
|
|
28
|
-
|
|
29
|
-
// Wait for SDK to finish loading
|
|
30
|
-
await sdkReady;
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
if (!Composio) return null;
|
|
34
|
-
composioClient = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });
|
|
35
|
-
return composioClient;
|
|
36
|
-
} catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Map internal user ID to Composio entity ID.
|
|
43
|
-
*/
|
|
44
|
-
function composioUserId(internalId) {
|
|
45
|
-
return `upfyn_user_${internalId}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Get or create an entity for the given user.
|
|
50
|
-
*/
|
|
51
|
-
async function getEntity(userId) {
|
|
52
|
-
const client = await getClient();
|
|
53
|
-
if (!client) throw new Error('Composio not configured');
|
|
54
|
-
return client.getEntity(composioUserId(userId));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Initiate an OAuth connection for a user.
|
|
59
|
-
* OAuth credentials are managed on the Composio dashboard — not injected here.
|
|
60
|
-
*
|
|
61
|
-
* @param {number} userId - Internal user ID
|
|
62
|
-
* @param {string} appName - Composio app name (e.g., 'GMAIL', 'SLACK')
|
|
63
|
-
* @param {string|null} authConfigId - Optional specific auth config
|
|
64
|
-
* @returns {{ redirectUrl: string, connectedAccountId: string }}
|
|
65
|
-
*/
|
|
66
|
-
async function initiateConnection(userId, appName, authConfigId = null) {
|
|
67
|
-
const entity = await getEntity(userId);
|
|
68
|
-
|
|
69
|
-
const params = { appName };
|
|
70
|
-
if (authConfigId) params.authConfigId = authConfigId;
|
|
71
|
-
|
|
72
|
-
const connectionRequest = await entity.initiateConnection(params);
|
|
73
|
-
return {
|
|
74
|
-
redirectUrl: connectionRequest.redirectUrl,
|
|
75
|
-
connectedAccountId: connectionRequest.connectedAccountId,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Wait/poll for a connection to complete.
|
|
81
|
-
* @param {string} connectedAccountId - The connection ID from initiateConnection
|
|
82
|
-
* @returns {{ status: string, appName: string }}
|
|
83
|
-
*/
|
|
84
|
-
async function waitForConnection(connectedAccountId) {
|
|
85
|
-
const client = await getClient();
|
|
86
|
-
if (!client) throw new Error('Composio not configured');
|
|
87
|
-
|
|
88
|
-
const connection = await client.connectedAccounts.get({ connectedAccountId });
|
|
89
|
-
return {
|
|
90
|
-
status: connection.status,
|
|
91
|
-
appName: connection.appName,
|
|
92
|
-
id: connection.id,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* List connected accounts for a user.
|
|
98
|
-
* @param {number} userId
|
|
99
|
-
* @returns {Array<{ id, appName, status, createdAt }>}
|
|
100
|
-
*/
|
|
101
|
-
async function listConnectedAccounts(userId) {
|
|
102
|
-
const client = await getClient();
|
|
103
|
-
if (!client) throw new Error('Composio not configured');
|
|
104
|
-
|
|
105
|
-
const entityId = composioUserId(userId);
|
|
106
|
-
try {
|
|
107
|
-
const accounts = await client.connectedAccounts.list({ entityId });
|
|
108
|
-
return (accounts.items || accounts || []).map(a => ({
|
|
109
|
-
id: a.id,
|
|
110
|
-
appName: a.appName,
|
|
111
|
-
status: a.status,
|
|
112
|
-
createdAt: a.createdAt,
|
|
113
|
-
}));
|
|
114
|
-
} catch {
|
|
115
|
-
return [];
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get available tools for given toolkits.
|
|
121
|
-
* @param {string[]} apps - e.g., ['GMAIL', 'SLACK']
|
|
122
|
-
* @returns {Array<{ name, description, parameters }>}
|
|
123
|
-
*/
|
|
124
|
-
async function getTools(apps = []) {
|
|
125
|
-
const client = await getClient();
|
|
126
|
-
if (!client) throw new Error('Composio not configured');
|
|
127
|
-
|
|
128
|
-
const tools = await client.actions.list({ apps });
|
|
129
|
-
return (tools.items || tools || []).map(t => ({
|
|
130
|
-
name: t.name,
|
|
131
|
-
displayName: t.displayName || t.name,
|
|
132
|
-
description: t.description,
|
|
133
|
-
parameters: t.parameters,
|
|
134
|
-
appName: t.appName,
|
|
135
|
-
}));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Get schema for a specific tool.
|
|
140
|
-
* @param {string} actionName - e.g., 'GMAIL_SEND_EMAIL'
|
|
141
|
-
* @returns {{ name, description, parameters }}
|
|
142
|
-
*/
|
|
143
|
-
async function getToolSchema(actionName) {
|
|
144
|
-
const client = await getClient();
|
|
145
|
-
if (!client) throw new Error('Composio not configured');
|
|
146
|
-
|
|
147
|
-
const action = await client.actions.get({ actionName });
|
|
148
|
-
return {
|
|
149
|
-
name: action.name,
|
|
150
|
-
displayName: action.displayName || action.name,
|
|
151
|
-
description: action.description,
|
|
152
|
-
parameters: action.parameters,
|
|
153
|
-
appName: action.appName,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Execute a Composio tool action.
|
|
159
|
-
* @param {number} userId - Internal user ID
|
|
160
|
-
* @param {string} actionName - e.g., 'GMAIL_SEND_EMAIL'
|
|
161
|
-
* @param {object} params - Action parameters
|
|
162
|
-
* @returns {{ success, data, error }}
|
|
163
|
-
*/
|
|
164
|
-
async function executeTool(userId, actionName, params = {}) {
|
|
165
|
-
const entity = await getEntity(userId);
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
const result = await entity.execute(actionName, params);
|
|
169
|
-
return { success: true, data: result };
|
|
170
|
-
} catch (err) {
|
|
171
|
-
return { success: false, error: err.message };
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Disconnect an account.
|
|
177
|
-
* @param {string} connectedAccountId
|
|
178
|
-
*/
|
|
179
|
-
async function disconnectAccount(connectedAccountId) {
|
|
180
|
-
const client = await getClient();
|
|
181
|
-
if (!client) throw new Error('Composio not configured');
|
|
182
|
-
|
|
183
|
-
await client.connectedAccounts.delete({ connectedAccountId });
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Check if Composio is available and configured.
|
|
188
|
-
*/
|
|
189
|
-
async function isAvailable() {
|
|
190
|
-
await sdkReady;
|
|
191
|
-
return !!(Composio && process.env.COMPOSIO_API_KEY);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export {
|
|
195
|
-
isAvailable,
|
|
196
|
-
composioUserId,
|
|
197
|
-
initiateConnection,
|
|
198
|
-
waitForConnection,
|
|
199
|
-
listConnectedAccounts,
|
|
200
|
-
getTools,
|
|
201
|
-
getToolSchema,
|
|
202
|
-
executeTool,
|
|
203
|
-
disconnectAccount,
|
|
204
|
-
};
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified Session Registry
|
|
3
|
-
* Wraps per-provider session maps into a single interface for session management.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { getActiveClaudeSDKSessions, abortClaudeSDKSession, isClaudeSDKSessionActive } from '../claude-sdk.js';
|
|
7
|
-
import { getActiveCursorSessions, abortCursorSession, isCursorSessionActive } from '../cursor-cli.js';
|
|
8
|
-
import { getActiveCodexSessions, abortCodexSession, isCodexSessionActive } from '../openai-codex.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Get all active sessions across all providers for a user.
|
|
12
|
-
* Note: current provider maps are global (not per-user), so we return all.
|
|
13
|
-
* When relay-based sessions are added, userId will be used to filter.
|
|
14
|
-
* @returns {Array<{ sessionId, provider, startTime, status, isProcessing }>}
|
|
15
|
-
*/
|
|
16
|
-
export function getAllActiveSessions() {
|
|
17
|
-
const sessions = [];
|
|
18
|
-
|
|
19
|
-
// Claude SDK sessions — returns array of session IDs
|
|
20
|
-
const claudeIds = getActiveClaudeSDKSessions();
|
|
21
|
-
for (const id of claudeIds) {
|
|
22
|
-
sessions.push({
|
|
23
|
-
sessionId: id,
|
|
24
|
-
provider: 'claude',
|
|
25
|
-
status: isClaudeSDKSessionActive(id) ? 'active' : 'idle',
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Cursor sessions — returns array of session IDs
|
|
30
|
-
const cursorIds = getActiveCursorSessions();
|
|
31
|
-
for (const id of cursorIds) {
|
|
32
|
-
sessions.push({
|
|
33
|
-
sessionId: id,
|
|
34
|
-
provider: 'cursor',
|
|
35
|
-
status: isCursorSessionActive(id) ? 'active' : 'idle',
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Codex sessions — returns array of { id, status, startedAt }
|
|
40
|
-
const codexSessions = getActiveCodexSessions();
|
|
41
|
-
for (const s of codexSessions) {
|
|
42
|
-
sessions.push({
|
|
43
|
-
sessionId: s.id,
|
|
44
|
-
provider: 'codex',
|
|
45
|
-
status: s.status === 'running' ? 'active' : s.status,
|
|
46
|
-
startedAt: s.startedAt,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return sessions;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Check if a session is active across any provider.
|
|
55
|
-
* @param {string} sessionId
|
|
56
|
-
* @returns {{ active: boolean, provider: string|null }}
|
|
57
|
-
*/
|
|
58
|
-
export function checkSessionStatus(sessionId) {
|
|
59
|
-
if (isClaudeSDKSessionActive(sessionId)) return { active: true, provider: 'claude' };
|
|
60
|
-
if (isCursorSessionActive(sessionId)) return { active: true, provider: 'cursor' };
|
|
61
|
-
if (isCodexSessionActive(sessionId)) return { active: true, provider: 'codex' };
|
|
62
|
-
return { active: false, provider: null };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Abort a session by ID, auto-detecting or using specified provider.
|
|
67
|
-
* @param {string} sessionId
|
|
68
|
-
* @param {string} [provider] - Optional: 'claude', 'cursor', 'codex'
|
|
69
|
-
* @returns {Promise<{ aborted: boolean, provider: string|null }>}
|
|
70
|
-
*/
|
|
71
|
-
export async function abortSession(sessionId, provider) {
|
|
72
|
-
// If provider specified, abort directly
|
|
73
|
-
if (provider) {
|
|
74
|
-
const result = await abortByProvider(sessionId, provider);
|
|
75
|
-
return { aborted: result, provider: result ? provider : null };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Auto-detect provider
|
|
79
|
-
if (isClaudeSDKSessionActive(sessionId)) {
|
|
80
|
-
const result = await abortClaudeSDKSession(sessionId);
|
|
81
|
-
return { aborted: result, provider: 'claude' };
|
|
82
|
-
}
|
|
83
|
-
if (isCursorSessionActive(sessionId)) {
|
|
84
|
-
const result = abortCursorSession(sessionId);
|
|
85
|
-
return { aborted: result, provider: 'cursor' };
|
|
86
|
-
}
|
|
87
|
-
if (isCodexSessionActive(sessionId)) {
|
|
88
|
-
const result = abortCodexSession(sessionId);
|
|
89
|
-
return { aborted: result, provider: 'codex' };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return { aborted: false, provider: null };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Abort all active sessions.
|
|
97
|
-
* @returns {Promise<Array<{ sessionId, provider, aborted }>>}
|
|
98
|
-
*/
|
|
99
|
-
export async function abortAllSessions() {
|
|
100
|
-
const all = getAllActiveSessions();
|
|
101
|
-
const results = [];
|
|
102
|
-
|
|
103
|
-
for (const s of all) {
|
|
104
|
-
const { aborted } = await abortSession(s.sessionId, s.provider);
|
|
105
|
-
results.push({ sessionId: s.sessionId, provider: s.provider, aborted });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return results;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Get session stats summary.
|
|
113
|
-
* @returns {{ total, byProvider: { claude, cursor, codex }, active }}
|
|
114
|
-
*/
|
|
115
|
-
export function getSessionStats() {
|
|
116
|
-
const sessions = getAllActiveSessions();
|
|
117
|
-
const byProvider = { claude: 0, cursor: 0, codex: 0 };
|
|
118
|
-
|
|
119
|
-
for (const s of sessions) {
|
|
120
|
-
byProvider[s.provider] = (byProvider[s.provider] || 0) + 1;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
total: sessions.length,
|
|
125
|
-
active: sessions.filter(s => s.status === 'active').length,
|
|
126
|
-
byProvider,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// -- Internal --
|
|
131
|
-
|
|
132
|
-
async function abortByProvider(sessionId, provider) {
|
|
133
|
-
switch (provider) {
|
|
134
|
-
case 'claude': return abortClaudeSDKSession(sessionId);
|
|
135
|
-
case 'cursor': return abortCursorSession(sessionId);
|
|
136
|
-
case 'codex': return abortCodexSession(sessionId);
|
|
137
|
-
default: return false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
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 };
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
// node-cron is cloud-only — dynamically imported so local installs don't crash
|
|
2
|
-
let cron = null;
|
|
3
|
-
try { cron = (await import('node-cron')).default; } catch { /* not installed locally */ }
|
|
4
|
-
|
|
5
|
-
import { workflowDb, webhookDb } from '../database/db.js';
|
|
6
|
-
import * as composioService from './composio.js';
|
|
7
|
-
|
|
8
|
-
const activeJobs = new Map(); // workflowId -> cron task
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Execute a single workflow's steps (same logic as the /run endpoint).
|
|
12
|
-
* Returns { success, results, error }
|
|
13
|
-
*/
|
|
14
|
-
async function executeWorkflow(workflow) {
|
|
15
|
-
const steps = typeof workflow.steps === 'string' ? JSON.parse(workflow.steps) : workflow.steps;
|
|
16
|
-
if (!steps.length) return { success: false, error: 'No steps' };
|
|
17
|
-
|
|
18
|
-
const run = await workflowDb.createRun(workflow.id, workflow.user_id, steps.length);
|
|
19
|
-
const results = [];
|
|
20
|
-
let lastOutput = null;
|
|
21
|
-
|
|
22
|
-
for (let i = 0; i < steps.length; i++) {
|
|
23
|
-
const step = steps[i];
|
|
24
|
-
try {
|
|
25
|
-
let stepResult;
|
|
26
|
-
|
|
27
|
-
if (step.type === 'webhook') {
|
|
28
|
-
const webhookId = step.config?.webhookId;
|
|
29
|
-
if (!webhookId) throw new Error('No webhook configured');
|
|
30
|
-
|
|
31
|
-
const webhook = await webhookDb.getById(Number(webhookId), workflow.user_id);
|
|
32
|
-
if (!webhook) throw new Error('Webhook not found');
|
|
33
|
-
|
|
34
|
-
let parsedHeaders = {};
|
|
35
|
-
try { parsedHeaders = JSON.parse(webhook.headers || '{}'); } catch { /* ignore */ }
|
|
36
|
-
|
|
37
|
-
const controller = new AbortController();
|
|
38
|
-
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
39
|
-
|
|
40
|
-
const fetchOptions = {
|
|
41
|
-
method: webhook.method,
|
|
42
|
-
headers: {
|
|
43
|
-
'Content-Type': 'application/json',
|
|
44
|
-
'User-Agent': 'UpfynAI-Scheduler/1.0',
|
|
45
|
-
...parsedHeaders
|
|
46
|
-
},
|
|
47
|
-
signal: controller.signal
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
if (['POST', 'PUT', 'PATCH'].includes(webhook.method)) {
|
|
51
|
-
const payload = {
|
|
52
|
-
workflow_id: workflow.id,
|
|
53
|
-
workflow_name: workflow.name,
|
|
54
|
-
step_index: i,
|
|
55
|
-
step_label: step.label,
|
|
56
|
-
previous_output: lastOutput,
|
|
57
|
-
scheduled: true,
|
|
58
|
-
timestamp: new Date().toISOString()
|
|
59
|
-
};
|
|
60
|
-
if (step.config?.payloadTemplate) {
|
|
61
|
-
try { Object.assign(payload, JSON.parse(step.config.payloadTemplate)); } catch { /* ignore */ }
|
|
62
|
-
}
|
|
63
|
-
fetchOptions.body = JSON.stringify(payload);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const response = await fetch(webhook.url, fetchOptions);
|
|
67
|
-
clearTimeout(timeout);
|
|
68
|
-
|
|
69
|
-
const contentType = response.headers.get('content-type') || '';
|
|
70
|
-
let body;
|
|
71
|
-
if (contentType.includes('application/json')) {
|
|
72
|
-
body = await response.json();
|
|
73
|
-
} else {
|
|
74
|
-
body = await response.text();
|
|
75
|
-
if (body.length > 5000) body = body.slice(0, 5000) + '...';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
await webhookDb.updateLastTriggered(webhook.id);
|
|
79
|
-
stepResult = { status: response.status, body };
|
|
80
|
-
lastOutput = body;
|
|
81
|
-
|
|
82
|
-
} else if (step.type === 'ai-prompt') {
|
|
83
|
-
stepResult = { type: 'ai-prompt', prompt: step.config?.prompt || '', note: 'Scheduled AI prompts require active session' };
|
|
84
|
-
lastOutput = step.config?.prompt;
|
|
85
|
-
|
|
86
|
-
} else if (step.type === 'delay') {
|
|
87
|
-
const seconds = Math.min(step.config?.seconds || 1, 30);
|
|
88
|
-
await new Promise(resolve => setTimeout(resolve, seconds * 1000));
|
|
89
|
-
stepResult = { delayed: seconds };
|
|
90
|
-
|
|
91
|
-
} else if (step.type === 'integration') {
|
|
92
|
-
const toolSlug = step.config?.toolSlug;
|
|
93
|
-
if (!toolSlug) throw new Error('No tool configured for integration step');
|
|
94
|
-
if (!(await composioService.isAvailable())) throw new Error('Composio not configured');
|
|
95
|
-
|
|
96
|
-
// Interpolate {{prev.field}} templates from lastOutput
|
|
97
|
-
let args = { ...(step.config?.arguments || {}) };
|
|
98
|
-
if (lastOutput && typeof lastOutput === 'object') {
|
|
99
|
-
for (const [key, val] of Object.entries(args)) {
|
|
100
|
-
if (typeof val === 'string' && val.includes('{{prev.')) {
|
|
101
|
-
args[key] = val.replace(/\{\{prev\.(\w+)\}\}/g, (_, field) =>
|
|
102
|
-
lastOutput[field] !== undefined ? String(lastOutput[field]) : ''
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const result = await composioService.executeTool(workflow.user_id, toolSlug, args);
|
|
109
|
-
if (!result.success) throw new Error(result.error || 'Integration execution failed');
|
|
110
|
-
stepResult = { toolSlug, ...result };
|
|
111
|
-
lastOutput = result.data || result;
|
|
112
|
-
|
|
113
|
-
} else {
|
|
114
|
-
stepResult = { type: step.type, note: 'Unknown step type' };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
results.push({ step: i, label: step.label, type: step.type, success: true, result: stepResult });
|
|
118
|
-
await workflowDb.updateRun(run.id, { status: 'running', stepsCompleted: i + 1 });
|
|
119
|
-
|
|
120
|
-
} catch (stepError) {
|
|
121
|
-
results.push({ step: i, label: step.label, type: step.type, success: false, error: stepError.message });
|
|
122
|
-
await workflowDb.updateRun(run.id, { status: 'failed', stepsCompleted: i, error: `Step ${i + 1} (${step.label}): ${stepError.message}` });
|
|
123
|
-
await workflowDb.updateLastRun(workflow.id);
|
|
124
|
-
return { success: false, run_id: run.id, results, error: `Step ${i + 1} failed: ${stepError.message}` };
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
await workflowDb.updateRun(run.id, { status: 'completed', stepsCompleted: steps.length, result: results });
|
|
129
|
-
await workflowDb.updateLastRun(workflow.id);
|
|
130
|
-
return { success: true, run_id: run.id, results };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Schedule a single workflow's cron job.
|
|
135
|
-
*/
|
|
136
|
-
function scheduleWorkflow(workflow) {
|
|
137
|
-
if (!cron) return; // node-cron not available (local install)
|
|
138
|
-
|
|
139
|
-
const id = workflow.id;
|
|
140
|
-
|
|
141
|
-
// Stop existing job if any
|
|
142
|
-
if (activeJobs.has(id)) {
|
|
143
|
-
activeJobs.get(id).stop();
|
|
144
|
-
activeJobs.delete(id);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (!workflow.schedule || !workflow.schedule_enabled) return;
|
|
148
|
-
|
|
149
|
-
// Validate cron expression
|
|
150
|
-
if (!cron.validate(workflow.schedule)) {
|
|
151
|
-
console.warn(`[Scheduler] Invalid cron for workflow ${id}: ${workflow.schedule}`);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const task = cron.schedule(workflow.schedule, async () => {
|
|
156
|
-
try {
|
|
157
|
-
await executeWorkflow(workflow);
|
|
158
|
-
} catch {
|
|
159
|
-
// workflow execution error
|
|
160
|
-
}
|
|
161
|
-
}, {
|
|
162
|
-
timezone: workflow.schedule_timezone || 'UTC'
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
activeJobs.set(id, task);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Load all scheduled workflows from DB and start their cron jobs.
|
|
170
|
-
* Call this once at server startup.
|
|
171
|
-
*/
|
|
172
|
-
async function initScheduler() {
|
|
173
|
-
try {
|
|
174
|
-
const workflows = await workflowDb.getScheduled();
|
|
175
|
-
for (const wf of workflows) {
|
|
176
|
-
scheduleWorkflow(wf);
|
|
177
|
-
}
|
|
178
|
-
} catch (err) {
|
|
179
|
-
// scheduler init error — silenced
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Re-sync a specific workflow's schedule (call after create/update).
|
|
185
|
-
*/
|
|
186
|
-
async function refreshWorkflowSchedule(workflowId, userId) {
|
|
187
|
-
try {
|
|
188
|
-
const wf = await workflowDb.getById(workflowId, userId);
|
|
189
|
-
if (wf) {
|
|
190
|
-
scheduleWorkflow(wf);
|
|
191
|
-
} else {
|
|
192
|
-
// Workflow deleted — stop its job
|
|
193
|
-
if (activeJobs.has(workflowId)) {
|
|
194
|
-
activeJobs.get(workflowId).stop();
|
|
195
|
-
activeJobs.delete(workflowId);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
} catch { /* ignore */ }
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Stop a workflow's cron job.
|
|
203
|
-
*/
|
|
204
|
-
function stopWorkflowSchedule(workflowId) {
|
|
205
|
-
if (activeJobs.has(workflowId)) {
|
|
206
|
-
activeJobs.get(workflowId).stop();
|
|
207
|
-
activeJobs.delete(workflowId);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export { initScheduler, refreshWorkflowSchedule, stopWorkflowSchedule, executeWorkflow };
|