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,312 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import { workflowDb, webhookDb, credentialsDb } from '../database/db.js';
|
|
3
|
-
import { refreshWorkflowSchedule, stopWorkflowSchedule, executeWorkflow } from '../services/workflowScheduler.js';
|
|
4
|
-
import { INTEGRATION_CATALOG } from '../../shared/integrationCatalog.js';
|
|
5
|
-
|
|
6
|
-
const router = express.Router();
|
|
7
|
-
|
|
8
|
-
// ── BYOK helpers ────────────────────────────────────────────────────
|
|
9
|
-
async function getUserProviderKey(userId, providerType) {
|
|
10
|
-
if (!userId) return null;
|
|
11
|
-
try {
|
|
12
|
-
const creds = await credentialsDb.getCredentials(userId, providerType);
|
|
13
|
-
const active = creds.find(c => c.is_active);
|
|
14
|
-
return active?.credential_value || null;
|
|
15
|
-
} catch { return null; }
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// ── AI workflow generation prompt ───────────────────────────────────
|
|
19
|
-
function buildWorkflowGenerationPrompt(description, availableWebhooks, connectedIntegrations) {
|
|
20
|
-
const integrationsList = INTEGRATION_CATALOG.map(i => {
|
|
21
|
-
const connected = connectedIntegrations.includes(i.id);
|
|
22
|
-
const actions = i.popularActions.map(a => ` - ${a.slug}: ${a.label} (params: ${a.params.join(', ')})`).join('\n');
|
|
23
|
-
return `${i.name} (${i.id})${connected ? ' [CONNECTED]' : ''}:\n${actions}`;
|
|
24
|
-
}).join('\n\n');
|
|
25
|
-
|
|
26
|
-
const webhooksList = availableWebhooks.length
|
|
27
|
-
? availableWebhooks.map(w => `- ID ${w.id}: "${w.name}" (${w.method} ${w.url})`).join('\n')
|
|
28
|
-
: 'No webhooks configured.';
|
|
29
|
-
|
|
30
|
-
return `You are a workflow automation builder. Given a natural language description, generate a workflow JSON.
|
|
31
|
-
|
|
32
|
-
AVAILABLE STEP TYPES:
|
|
33
|
-
1. "ai-prompt" — Run an AI prompt. Config: { prompt: string }
|
|
34
|
-
2. "webhook" — Call an HTTP endpoint. Config: { webhookId: string, payloadTemplate?: string }
|
|
35
|
-
3. "delay" — Wait N seconds (max 30). Config: { seconds: number }
|
|
36
|
-
4. "condition" — Branch on expression. Config: { expression: string }
|
|
37
|
-
5. "integration" — Use a connected app via Composio. Config: { integrationId: string, toolSlug: string, arguments: { param: value } }
|
|
38
|
-
|
|
39
|
-
AVAILABLE INTEGRATIONS:
|
|
40
|
-
${integrationsList}
|
|
41
|
-
|
|
42
|
-
AVAILABLE WEBHOOKS:
|
|
43
|
-
${webhooksList}
|
|
44
|
-
|
|
45
|
-
RULES:
|
|
46
|
-
- Each step needs: id (unique string), type, label (human-readable), config, order (0-indexed)
|
|
47
|
-
- Step IDs should be like "step_1", "step_2", etc.
|
|
48
|
-
- For integration steps, only use integrations marked [CONNECTED] or suggest connecting them
|
|
49
|
-
- Use {{prev.field}} syntax in config values to reference previous step output
|
|
50
|
-
- If the user mentions a schedule, include schedule (cron) and schedule_enabled: true
|
|
51
|
-
- Keep workflows focused and practical
|
|
52
|
-
|
|
53
|
-
Respond with ONLY valid JSON in this exact format (no markdown, no explanation):
|
|
54
|
-
{
|
|
55
|
-
"name": "Workflow Name",
|
|
56
|
-
"description": "What this workflow does",
|
|
57
|
-
"steps": [...],
|
|
58
|
-
"schedule": null,
|
|
59
|
-
"schedule_enabled": false,
|
|
60
|
-
"schedule_timezone": "UTC"
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
USER REQUEST: ${description}`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ── Routes ──────────────────────────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
// GET /api/workflows — list all workflows for the user
|
|
69
|
-
router.get('/', async (req, res) => {
|
|
70
|
-
try {
|
|
71
|
-
const workflows = await workflowDb.getAll(req.user.id);
|
|
72
|
-
const parsed = workflows.map(w => ({
|
|
73
|
-
...w,
|
|
74
|
-
steps: typeof w.steps === 'string' ? JSON.parse(w.steps) : w.steps
|
|
75
|
-
}));
|
|
76
|
-
res.json({ workflows: parsed });
|
|
77
|
-
} catch (error) {
|
|
78
|
-
res.status(500).json({ error: 'Failed to fetch workflows' });
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// POST /api/workflows — create a workflow
|
|
83
|
-
router.post('/', async (req, res) => {
|
|
84
|
-
try {
|
|
85
|
-
const { name, description, steps, schedule, schedule_enabled, schedule_timezone } = req.body;
|
|
86
|
-
if (!name || !name.trim()) return res.status(400).json({ error: 'Name is required' });
|
|
87
|
-
if (!Array.isArray(steps)) return res.status(400).json({ error: 'Steps must be an array' });
|
|
88
|
-
|
|
89
|
-
const workflow = await workflowDb.create(req.user.id, {
|
|
90
|
-
name: name.trim(),
|
|
91
|
-
description: description?.trim() || null,
|
|
92
|
-
steps,
|
|
93
|
-
schedule: schedule || null,
|
|
94
|
-
schedule_enabled: !!schedule_enabled,
|
|
95
|
-
schedule_timezone: schedule_timezone || 'UTC'
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Sync cron scheduler
|
|
99
|
-
if (workflow.id) refreshWorkflowSchedule(workflow.id, req.user.id);
|
|
100
|
-
|
|
101
|
-
res.json({ success: true, workflow });
|
|
102
|
-
} catch (error) {
|
|
103
|
-
res.status(500).json({ error: 'Failed to create workflow' });
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// POST /api/workflows/generate — AI-powered workflow generation from natural language
|
|
108
|
-
router.post('/generate', async (req, res) => {
|
|
109
|
-
try {
|
|
110
|
-
const { description } = req.body;
|
|
111
|
-
if (!description || !description.trim()) {
|
|
112
|
-
return res.status(400).json({ error: 'Description is required' });
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Get user's API key (try Anthropic first, then OpenRouter)
|
|
116
|
-
let apiKey = await getUserProviderKey(req.user.id, 'anthropic_key');
|
|
117
|
-
let provider = 'anthropic';
|
|
118
|
-
|
|
119
|
-
if (!apiKey) {
|
|
120
|
-
apiKey = await getUserProviderKey(req.user.id, 'openrouter_key');
|
|
121
|
-
provider = 'openrouter';
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Fall back to server key
|
|
125
|
-
if (!apiKey) {
|
|
126
|
-
apiKey = process.env.ANTHROPIC_API_KEY;
|
|
127
|
-
provider = 'anthropic';
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (!apiKey) {
|
|
131
|
-
return res.status(400).json({ error: 'No AI provider key available. Add an API key in Settings > AI Providers.' });
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Get available webhooks and connected integrations for context
|
|
135
|
-
const webhooks = await webhookDb.getAll(req.user.id);
|
|
136
|
-
const connectedIntegrations = []; // Will be populated if Composio is available
|
|
137
|
-
|
|
138
|
-
const prompt = buildWorkflowGenerationPrompt(description.trim(), webhooks, connectedIntegrations);
|
|
139
|
-
|
|
140
|
-
let generatedJson;
|
|
141
|
-
|
|
142
|
-
if (provider === 'anthropic') {
|
|
143
|
-
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
144
|
-
method: 'POST',
|
|
145
|
-
headers: {
|
|
146
|
-
'x-api-key': apiKey,
|
|
147
|
-
'anthropic-version': '2023-06-01',
|
|
148
|
-
'content-type': 'application/json',
|
|
149
|
-
},
|
|
150
|
-
body: JSON.stringify({
|
|
151
|
-
model: 'claude-sonnet-4-20250514',
|
|
152
|
-
max_tokens: 2048,
|
|
153
|
-
messages: [{ role: 'user', content: prompt }],
|
|
154
|
-
}),
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
if (!response.ok) {
|
|
158
|
-
return res.status(502).json({ error: 'AI provider returned an error' });
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const data = await response.json();
|
|
162
|
-
const text = data.content?.[0]?.text || '';
|
|
163
|
-
generatedJson = text.trim();
|
|
164
|
-
} else {
|
|
165
|
-
// OpenRouter
|
|
166
|
-
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
167
|
-
method: 'POST',
|
|
168
|
-
headers: {
|
|
169
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
170
|
-
'Content-Type': 'application/json',
|
|
171
|
-
'HTTP-Referer': 'https://cli.upfyn.com',
|
|
172
|
-
'X-Title': 'Upfyn-Code',
|
|
173
|
-
},
|
|
174
|
-
body: JSON.stringify({
|
|
175
|
-
model: 'anthropic/claude-sonnet-4',
|
|
176
|
-
messages: [{ role: 'user', content: prompt }],
|
|
177
|
-
max_tokens: 2048,
|
|
178
|
-
}),
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
if (!response.ok) {
|
|
182
|
-
return res.status(502).json({ error: 'AI provider returned an error' });
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const data = await response.json();
|
|
186
|
-
generatedJson = data.choices?.[0]?.message?.content?.trim() || '';
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Parse the AI response — strip markdown fences if present
|
|
190
|
-
let cleaned = generatedJson;
|
|
191
|
-
if (cleaned.startsWith('```')) {
|
|
192
|
-
cleaned = cleaned.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
let workflow;
|
|
196
|
-
try {
|
|
197
|
-
workflow = JSON.parse(cleaned);
|
|
198
|
-
} catch {
|
|
199
|
-
return res.status(422).json({ error: 'AI generated invalid workflow format. Try rephrasing your description.' });
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Validate required fields
|
|
203
|
-
if (!workflow.name || !Array.isArray(workflow.steps)) {
|
|
204
|
-
return res.status(422).json({ error: 'AI generated incomplete workflow. Try being more specific.' });
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Ensure step IDs are unique
|
|
208
|
-
workflow.steps = workflow.steps.map((step, i) => ({
|
|
209
|
-
...step,
|
|
210
|
-
id: step.id || `step_${Date.now()}_${i}`,
|
|
211
|
-
order: i,
|
|
212
|
-
}));
|
|
213
|
-
|
|
214
|
-
// Save to Turso
|
|
215
|
-
const saved = await workflowDb.create(req.user.id, {
|
|
216
|
-
name: workflow.name.trim(),
|
|
217
|
-
description: workflow.description?.trim() || description.trim(),
|
|
218
|
-
steps: workflow.steps,
|
|
219
|
-
schedule: workflow.schedule || null,
|
|
220
|
-
schedule_enabled: !!workflow.schedule_enabled,
|
|
221
|
-
schedule_timezone: workflow.schedule_timezone || 'UTC',
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
if (saved.id && workflow.schedule_enabled) {
|
|
225
|
-
refreshWorkflowSchedule(saved.id, req.user.id);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
res.json({
|
|
229
|
-
success: true,
|
|
230
|
-
workflow: {
|
|
231
|
-
...saved,
|
|
232
|
-
steps: workflow.steps,
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
} catch (error) {
|
|
236
|
-
res.status(500).json({ error: 'Failed to generate workflow' });
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// PUT /api/workflows/:id — update a workflow
|
|
241
|
-
router.put('/:id', async (req, res) => {
|
|
242
|
-
try {
|
|
243
|
-
const { name, description, steps, schedule, schedule_enabled, schedule_timezone } = req.body;
|
|
244
|
-
if (!name || !name.trim()) return res.status(400).json({ error: 'Name is required' });
|
|
245
|
-
|
|
246
|
-
const wfId = Number(req.params.id);
|
|
247
|
-
const updated = await workflowDb.update(wfId, req.user.id, {
|
|
248
|
-
name: name.trim(),
|
|
249
|
-
description: description?.trim() || null,
|
|
250
|
-
steps: steps || [],
|
|
251
|
-
schedule: schedule || null,
|
|
252
|
-
schedule_enabled: !!schedule_enabled,
|
|
253
|
-
schedule_timezone: schedule_timezone || 'UTC'
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
if (!updated) return res.status(404).json({ error: 'Workflow not found' });
|
|
257
|
-
|
|
258
|
-
// Sync cron scheduler
|
|
259
|
-
refreshWorkflowSchedule(wfId, req.user.id);
|
|
260
|
-
|
|
261
|
-
res.json({ success: true });
|
|
262
|
-
} catch (error) {
|
|
263
|
-
res.status(500).json({ error: 'Failed to update workflow' });
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// DELETE /api/workflows/:id — delete a workflow
|
|
268
|
-
router.delete('/:id', async (req, res) => {
|
|
269
|
-
try {
|
|
270
|
-
const wfId = Number(req.params.id);
|
|
271
|
-
const deleted = await workflowDb.delete(wfId, req.user.id);
|
|
272
|
-
if (!deleted) return res.status(404).json({ error: 'Workflow not found' });
|
|
273
|
-
|
|
274
|
-
stopWorkflowSchedule(wfId);
|
|
275
|
-
|
|
276
|
-
res.json({ success: true });
|
|
277
|
-
} catch (error) {
|
|
278
|
-
res.status(500).json({ error: 'Failed to delete workflow' });
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// POST /api/workflows/:id/run — execute a workflow (manual trigger)
|
|
283
|
-
router.post('/:id/run', async (req, res) => {
|
|
284
|
-
try {
|
|
285
|
-
const workflow = await workflowDb.getById(Number(req.params.id), req.user.id);
|
|
286
|
-
if (!workflow) return res.status(404).json({ error: 'Workflow not found' });
|
|
287
|
-
|
|
288
|
-
const steps = typeof workflow.steps === 'string' ? JSON.parse(workflow.steps) : workflow.steps;
|
|
289
|
-
if (!steps.length) return res.status(400).json({ error: 'Workflow has no steps' });
|
|
290
|
-
|
|
291
|
-
const result = await executeWorkflow({ ...workflow, steps });
|
|
292
|
-
res.json(result);
|
|
293
|
-
} catch (error) {
|
|
294
|
-
res.status(500).json({ error: 'Failed to run workflow' });
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
// GET /api/workflows/:id/runs — list execution history for a workflow
|
|
299
|
-
router.get('/:id/runs', async (req, res) => {
|
|
300
|
-
try {
|
|
301
|
-
const runs = await workflowDb.getRuns(Number(req.params.id), req.user.id);
|
|
302
|
-
const parsed = runs.map(r => ({
|
|
303
|
-
...r,
|
|
304
|
-
result: typeof r.result === 'string' ? (() => { try { return JSON.parse(r.result); } catch { return r.result; } })() : r.result
|
|
305
|
-
}));
|
|
306
|
-
res.json({ runs: parsed });
|
|
307
|
-
} catch (error) {
|
|
308
|
-
res.status(500).json({ error: 'Failed to fetch workflow runs' });
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
export default router;
|
package/server/sandbox.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sandbox Client — connects the backend to the separate sandbox-service on Railway.
|
|
3
|
-
* All sandbox operations are proxied to the sandbox service via HTTP.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const SANDBOX_SERVICE_URL = process.env.SANDBOX_SERVICE_URL || 'http://localhost:4300';
|
|
7
|
-
const SANDBOX_SERVICE_SECRET = process.env.SANDBOX_SERVICE_SECRET || 'dev-sandbox-secret';
|
|
8
|
-
|
|
9
|
-
async function sandboxFetch(path, userId, body = null) {
|
|
10
|
-
const opts = {
|
|
11
|
-
method: body ? 'POST' : 'GET',
|
|
12
|
-
headers: {
|
|
13
|
-
'Content-Type': 'application/json',
|
|
14
|
-
'x-sandbox-secret': SANDBOX_SERVICE_SECRET,
|
|
15
|
-
'x-user-id': String(userId),
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
if (body) opts.body = JSON.stringify(body);
|
|
19
|
-
|
|
20
|
-
const res = await fetch(`${SANDBOX_SERVICE_URL}${path}`, opts);
|
|
21
|
-
const data = await res.json();
|
|
22
|
-
if (!res.ok) throw new Error(data.error || `Sandbox service error: ${res.status}`);
|
|
23
|
-
return data;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const sandboxClient = {
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Check if the sandbox service is reachable.
|
|
30
|
-
*/
|
|
31
|
-
async isAvailable() {
|
|
32
|
-
try {
|
|
33
|
-
const res = await fetch(`${SANDBOX_SERVICE_URL}/health`, { signal: AbortSignal.timeout(3000) });
|
|
34
|
-
return res.ok;
|
|
35
|
-
} catch {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Initialize a user's sandbox (creates if doesn't exist).
|
|
42
|
-
*/
|
|
43
|
-
async initSandbox(userId) {
|
|
44
|
-
return sandboxFetch('/api/sandbox/init', userId, {});
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get sandbox status.
|
|
49
|
-
*/
|
|
50
|
-
async getStatus(userId) {
|
|
51
|
-
return sandboxFetch('/api/sandbox/status', userId);
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Destroy a user's sandbox.
|
|
56
|
-
*/
|
|
57
|
-
async destroySandbox(userId) {
|
|
58
|
-
const res = await fetch(`${SANDBOX_SERVICE_URL}/api/sandbox`, {
|
|
59
|
-
method: 'DELETE',
|
|
60
|
-
headers: {
|
|
61
|
-
'Content-Type': 'application/json',
|
|
62
|
-
'x-sandbox-secret': SANDBOX_SERVICE_SECRET,
|
|
63
|
-
'x-user-id': String(userId),
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
const data = await res.json();
|
|
67
|
-
if (!res.ok) throw new Error(data.error || 'Failed to destroy sandbox');
|
|
68
|
-
return data;
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Execute a command in the user's sandbox.
|
|
73
|
-
*/
|
|
74
|
-
async exec(userId, command, opts = {}) {
|
|
75
|
-
return sandboxFetch('/api/exec', userId, {
|
|
76
|
-
command,
|
|
77
|
-
cwd: opts.cwd,
|
|
78
|
-
timeout: opts.timeout,
|
|
79
|
-
userKeys: opts.userKeys,
|
|
80
|
-
});
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Read a file from the user's sandbox.
|
|
85
|
-
*/
|
|
86
|
-
async readFile(userId, filePath) {
|
|
87
|
-
return sandboxFetch('/api/file/read', userId, { filePath });
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Write a file to the user's sandbox.
|
|
92
|
-
*/
|
|
93
|
-
async writeFile(userId, filePath, content) {
|
|
94
|
-
return sandboxFetch('/api/file/write', userId, { filePath, content });
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Get file tree from the user's sandbox.
|
|
99
|
-
*/
|
|
100
|
-
async getFileTree(userId, dirPath, depth = 3) {
|
|
101
|
-
return sandboxFetch('/api/file/tree', userId, { dirPath, depth });
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Run a git command in the user's sandbox.
|
|
106
|
-
*/
|
|
107
|
-
async gitOperation(userId, gitCommand, cwd) {
|
|
108
|
-
return sandboxFetch('/api/git', userId, { gitCommand, cwd });
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Get the WebSocket URL for an interactive shell session.
|
|
113
|
-
*/
|
|
114
|
-
getShellWsUrl(userId, sessionId) {
|
|
115
|
-
const wsBase = SANDBOX_SERVICE_URL.replace(/^http/, 'ws');
|
|
116
|
-
return `${wsBase}/shell?secret=${encodeURIComponent(SANDBOX_SERVICE_SECRET)}&userId=${userId}&sessionId=${sessionId || 'default'}`;
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
export { sandboxClient, SANDBOX_SERVICE_URL };
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser AI Service — Stagehand integration for AI-driven browser automation.
|
|
3
|
-
* Lazy-loads @browserbasehq/stagehand (cloud-only dep, installed via nixpacks).
|
|
4
|
-
* Follows the composio.js pattern: lazy SDK, per-session instances, availability check.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
let Stagehand = null;
|
|
8
|
-
let sdkReady = null;
|
|
9
|
-
|
|
10
|
-
// Lazy-load Stagehand SDK (cloud-only dependency)
|
|
11
|
-
sdkReady = (async () => {
|
|
12
|
-
try {
|
|
13
|
-
const mod = await import('@browserbasehq/stagehand');
|
|
14
|
-
Stagehand = mod.Stagehand || mod.default;
|
|
15
|
-
} catch {
|
|
16
|
-
// SDK not installed — browser AI features unavailable (local installs)
|
|
17
|
-
}
|
|
18
|
-
})();
|
|
19
|
-
|
|
20
|
-
// Per-session Stagehand instance cache
|
|
21
|
-
const instances = new Map();
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Check if the Stagehand SDK is available.
|
|
25
|
-
*/
|
|
26
|
-
async function isAvailable() {
|
|
27
|
-
await sdkReady;
|
|
28
|
-
return !!Stagehand;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Get or create a Stagehand instance for a session.
|
|
33
|
-
*/
|
|
34
|
-
async function getOrCreate(sessionId, cdpUrl) {
|
|
35
|
-
if (instances.has(sessionId)) return instances.get(sessionId);
|
|
36
|
-
|
|
37
|
-
await sdkReady;
|
|
38
|
-
if (!Stagehand) throw new Error('Browser AI not available — Stagehand SDK not installed');
|
|
39
|
-
|
|
40
|
-
const stagehand = new Stagehand({
|
|
41
|
-
env: 'LOCAL',
|
|
42
|
-
enableCaching: true,
|
|
43
|
-
localBrowserLaunchOptions: {
|
|
44
|
-
cdpUrl,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
await stagehand.init();
|
|
48
|
-
|
|
49
|
-
instances.set(sessionId, stagehand);
|
|
50
|
-
return stagehand;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Execute a single AI action on the page.
|
|
55
|
-
* Chat mode: user says "click the login button" → this executes it.
|
|
56
|
-
*/
|
|
57
|
-
async function act(sessionId, cdpUrl, instruction) {
|
|
58
|
-
const stagehand = await getOrCreate(sessionId, cdpUrl);
|
|
59
|
-
const page = stagehand.page;
|
|
60
|
-
const result = await page.act({ action: instruction });
|
|
61
|
-
return { success: true, result, url: page.url() };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Extract structured data from the current page.
|
|
66
|
-
*/
|
|
67
|
-
async function extract(sessionId, cdpUrl, instruction, schema) {
|
|
68
|
-
const stagehand = await getOrCreate(sessionId, cdpUrl);
|
|
69
|
-
const page = stagehand.page;
|
|
70
|
-
const opts = { instruction };
|
|
71
|
-
if (schema) opts.schema = schema;
|
|
72
|
-
const result = await page.extract(opts);
|
|
73
|
-
return { success: true, data: result };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Observe the current page — returns available actions/elements.
|
|
78
|
-
*/
|
|
79
|
-
async function observe(sessionId, cdpUrl, instruction) {
|
|
80
|
-
const stagehand = await getOrCreate(sessionId, cdpUrl);
|
|
81
|
-
const page = stagehand.page;
|
|
82
|
-
const opts = instruction ? { instruction } : {};
|
|
83
|
-
const result = await page.observe(opts);
|
|
84
|
-
return { success: true, observations: result };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Run an autonomous agent that pursues a goal across multiple steps.
|
|
89
|
-
* Streams each step to the onStep callback (for SSE).
|
|
90
|
-
*/
|
|
91
|
-
async function autonomousGoal(sessionId, cdpUrl, goal, maxSteps = 10, onStep) {
|
|
92
|
-
const stagehand = await getOrCreate(sessionId, cdpUrl);
|
|
93
|
-
const page = stagehand.page;
|
|
94
|
-
|
|
95
|
-
for (let step = 0; step < maxSteps; step++) {
|
|
96
|
-
try {
|
|
97
|
-
// Observe current state
|
|
98
|
-
const observations = await page.observe();
|
|
99
|
-
onStep({ step, type: 'observe', data: observations, url: page.url(), timestamp: Date.now() });
|
|
100
|
-
|
|
101
|
-
// Execute next action toward goal
|
|
102
|
-
const actionResult = await page.act({ action: `Working towards this goal: ${goal}` });
|
|
103
|
-
onStep({ step, type: 'act', data: actionResult, url: page.url(), timestamp: Date.now() });
|
|
104
|
-
|
|
105
|
-
// Check if done
|
|
106
|
-
const check = await page.extract({
|
|
107
|
-
instruction: `Has this goal been achieved: "${goal}"? Answer with done=true or done=false and a brief reason.`,
|
|
108
|
-
});
|
|
109
|
-
onStep({ step, type: 'check', data: check, timestamp: Date.now() });
|
|
110
|
-
|
|
111
|
-
if (check && check.done) break;
|
|
112
|
-
} catch (err) {
|
|
113
|
-
onStep({ step, type: 'error', message: err.message, timestamp: Date.now() });
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get console errors from the browser via CDP.
|
|
121
|
-
*/
|
|
122
|
-
async function getConsoleErrors(sessionId, cdpUrl) {
|
|
123
|
-
const stagehand = await getOrCreate(sessionId, cdpUrl);
|
|
124
|
-
const page = stagehand.page;
|
|
125
|
-
|
|
126
|
-
// Collect console errors using page.evaluate
|
|
127
|
-
const errors = await page.evaluate(() => {
|
|
128
|
-
// Check for any errors the page might have stored
|
|
129
|
-
return (window.__upfynErrors || []).slice(-50);
|
|
130
|
-
}).catch(() => []);
|
|
131
|
-
|
|
132
|
-
return errors;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Release a Stagehand instance for a session.
|
|
137
|
-
*/
|
|
138
|
-
async function release(sessionId) {
|
|
139
|
-
const instance = instances.get(sessionId);
|
|
140
|
-
if (instance) {
|
|
141
|
-
try { await instance.close(); } catch { /* ignore */ }
|
|
142
|
-
instances.delete(sessionId);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export {
|
|
147
|
-
isAvailable,
|
|
148
|
-
act,
|
|
149
|
-
extract,
|
|
150
|
-
observe,
|
|
151
|
-
autonomousGoal,
|
|
152
|
-
getConsoleErrors,
|
|
153
|
-
release,
|
|
154
|
-
};
|